8375050: Simplify process management in jpackage tests

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2026-01-13 13:36:44 +00:00
parent f7be1dcf29
commit 47029ccfec
8 changed files with 111 additions and 170 deletions

View File

@ -35,6 +35,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.spi.ToolProvider;
import java.util.stream.IntStream;
@ -109,15 +110,6 @@ public final class Executor extends CommandArguments<Executor> {
return this;
}
public Executor setWinRunWithEnglishOutput(boolean value) {
if (!TKit.isWindows()) {
throw new UnsupportedOperationException(
"setWinRunWithEnglishOutput is only valid on Windows platform");
}
winEnglishOutput = value;
return this;
}
public Executor setWindowsTmpDir(String tmp) {
if (!TKit.isWindows()) {
throw new UnsupportedOperationException(
@ -195,6 +187,11 @@ public final class Executor extends CommandArguments<Executor> {
return storeOutputInFiles(true);
}
public Executor processListener(Consumer<Process> v) {
commandOutputControl.processListener(v);
return this;
}
public record Result(CommandOutputControl.Result base) {
public Result {
Objects.requireNonNull(base);
@ -310,11 +307,6 @@ public final class Executor extends CommandArguments<Executor> {
"Can't change directory when using tool provider");
}
if (toolProvider != null && winEnglishOutput) {
throw new IllegalArgumentException(
"Can't change locale when using tool provider");
}
return ThrowingSupplier.toSupplier(() -> {
if (toolProvider != null) {
return runToolProvider();
@ -434,17 +426,8 @@ public final class Executor extends CommandArguments<Executor> {
return executable.toAbsolutePath();
}
private List<String> prefixCommandLineArgs() {
if (winEnglishOutput) {
return List.of("cmd.exe", "/c", "chcp", "437", ">nul", "2>&1", "&&");
} else {
return List.of();
}
}
private Result runExecutable() throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.addAll(prefixCommandLineArgs());
command.add(executablePath().toString());
command.addAll(args);
ProcessBuilder builder = new ProcessBuilder(command);
@ -522,8 +505,7 @@ public final class Executor extends CommandArguments<Executor> {
exec = executablePath().toString();
}
var cmdline = Stream.of(prefixCommandLineArgs(), List.of(exec), args).flatMap(
List::stream).toList();
var cmdline = Stream.of(List.of(exec), args).flatMap(List::stream).toList();
return String.format(format, CommandLineFormat.DEFAULT.apply(cmdline), cmdline.size());
}
@ -559,6 +541,5 @@ public final class Executor extends CommandArguments<Executor> {
private Path directory;
private Set<String> removeEnvVars = new HashSet<>();
private Map<String, String> setEnvVars = new HashMap<>();
private boolean winEnglishOutput;
private String winTmpDir = null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,6 +35,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Matcher;
@ -315,37 +316,33 @@ public final class HelloApp {
public static void executeLauncherAndVerifyOutput(JPackageCommand cmd,
String... args) {
AppOutputVerifier av = assertMainLauncher(cmd, args);
if (av != null) {
assertMainLauncher(cmd, args).ifPresent(av -> {
av.executeAndVerifyOutput(args);
}
});
}
public static Executor.Result executeLauncher(JPackageCommand cmd,
String... args) {
AppOutputVerifier av = assertMainLauncher(cmd, args);
if (av != null) {
return assertMainLauncher(cmd, args).map(av -> {
return av.saveOutput(true).execute(args);
} else {
return null;
}
}).orElseThrow();
}
public static AppOutputVerifier assertMainLauncher(JPackageCommand cmd,
public static Optional<AppOutputVerifier> assertMainLauncher(JPackageCommand cmd,
String... args) {
final Path launcherPath = cmd.appLauncherPath();
if (!cmd.canRunLauncher(String.format("Not running [%s] launcher",
launcherPath))) {
return null;
return Optional.empty();
}
return assertApp(launcherPath)
return Optional.of(assertApp(launcherPath)
.addDefaultArguments(Optional
.ofNullable(cmd.getAllArgumentValues("--arguments"))
.orElseGet(() -> new String[0]))
.addJavaOptions(Optional
.ofNullable(cmd.getAllArgumentValues("--java-options"))
.orElseGet(() -> new String[0]));
.orElseGet(() -> new String[0])));
}
@ -426,6 +423,11 @@ public final class HelloApp {
.collect(Collectors.toList()));
}
public AppOutputVerifier processListener(Consumer<Process> v) {
processListener = v;
return this;
}
public void verifyOutput(String... args) {
final List<String> launcherArgs = List.of(args);
final List<String> appArgs;
@ -479,6 +481,7 @@ public final class HelloApp {
.saveOutput(saveOutput)
.dumpOutput()
.setExecutable(executablePath)
.processListener(processListener)
.addArguments(List.of(args));
env.forEach((envVarName, envVarValue) -> {
@ -493,6 +496,7 @@ public final class HelloApp {
private final Path launcherPath;
private Path outputFilePath;
private int expectedExitCode;
private Consumer<Process> processListener;
private final List<String> defaultLauncherArgs;
private final Map<String, String> params;
private final Map<String, String> env;

View File

@ -42,8 +42,6 @@ import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.function.ThrowingRunnable;
import jdk.jpackage.test.PackageTest.PackageHandlers;
@ -306,91 +304,6 @@ public class WindowsHelper {
"Failed to get file description of [%s]", pathToExeFile));
}
public static void killProcess(long pid) {
Executor.of("taskkill", "/F", "/PID", Long.toString(pid)).dumpOutput(true).execute();
}
public static void killAppLauncherProcess(JPackageCommand cmd,
String launcherName, int expectedCount) {
var pids = findAppLauncherPIDs(cmd, launcherName);
try {
TKit.assertEquals(expectedCount, pids.length, String.format(
"Check [%d] %s app launcher processes found running",
expectedCount, Optional.ofNullable(launcherName).map(
str -> "[" + str + "]").orElse("<main>")));
} finally {
if (pids.length != 0) {
killProcess(pids[0]);
}
}
}
private static long[] findAppLauncherPIDs(JPackageCommand cmd, String launcherName) {
// Get the list of PIDs and PPIDs of app launcher processes. Run setWinRunWithEnglishOutput(true) for JDK-8344275.
// powershell -NoLogo -NoProfile -NonInteractive -Command
// "Get-CimInstance Win32_Process -Filter \"Name = 'foo.exe'\" | select ProcessID,ParentProcessID"
String command = "Get-CimInstance Win32_Process -Filter \\\"Name = '"
+ cmd.appLauncherPath(launcherName).getFileName().toString()
+ "'\\\" | select ProcessID,ParentProcessID";
List<String> output = Executor.of("powershell", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command)
.dumpOutput(true).saveOutput().setWinRunWithEnglishOutput(true).executeAndGetOutput();
if (output.size() < 1) {
return new long[0];
}
String[] headers = Stream.of(output.get(1).split("\\s+", 2)).map(
String::trim).map(String::toLowerCase).toArray(String[]::new);
Pattern pattern;
if (headers[0].equals("parentprocessid") && headers[1].equals(
"processid")) {
pattern = Pattern.compile("^\\s+(?<ppid>\\d+)\\s+(?<pid>\\d+)$");
} else if (headers[1].equals("parentprocessid") && headers[0].equals(
"processid")) {
pattern = Pattern.compile("^\\s+(?<pid>\\d+)\\s+(?<ppid>\\d+)$");
} else {
throw new RuntimeException(
"Unrecognizable output of \'Get-CimInstance Win32_Process\' command");
}
List<long[]> processes = output.stream().skip(3).map(line -> {
Matcher m = pattern.matcher(line);
long[] pids = null;
if (m.matches()) {
pids = new long[]{Long.parseLong(m.group("pid")), Long.
parseLong(m.group("ppid"))};
}
return pids;
}).filter(Objects::nonNull).toList();
switch (processes.size()) {
case 2 -> {
final long parentPID;
final long childPID;
if (processes.get(0)[0] == processes.get(1)[1]) {
parentPID = processes.get(0)[0];
childPID = processes.get(1)[0];
} else if (processes.get(1)[0] == processes.get(0)[1]) {
parentPID = processes.get(1)[0];
childPID = processes.get(0)[0];
} else {
TKit.assertUnexpected("App launcher processes unrelated");
return null; // Unreachable
}
return new long[]{parentPID, childPID};
}
case 1 -> {
return new long[]{processes.get(0)[0]};
}
default -> {
TKit.assertUnexpected(String.format(
"Unexpected number of running processes [%d]",
processes.size()));
return null; // Unreachable
}
}
}
static boolean isUserLocalInstall(JPackageCommand cmd) {
return cmd.hasArgument("--win-per-user-install");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -49,11 +49,10 @@ public class ArgumentsFilteringTest {
public void test1() {
JPackageCommand cmd = JPackageCommand.helloAppImage();
cmd.executeAndAssertHelloAppImageCreated();
var appVerifier = HelloApp.assertMainLauncher(cmd);
if (appVerifier != null) {
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
appVerifier.execute("-psn_1_1");
appVerifier.verifyOutput();
}
});
}
@Test
@ -61,10 +60,9 @@ public class ArgumentsFilteringTest {
JPackageCommand cmd = JPackageCommand.helloAppImage()
.addArguments("--arguments", "-psn_2_2");
cmd.executeAndAssertHelloAppImageCreated();
var appVerifier = HelloApp.assertMainLauncher(cmd);
if (appVerifier != null) {
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
appVerifier.execute("-psn_1_1");
appVerifier.verifyOutput("-psn_2_2");
}
});
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -240,8 +240,7 @@ public final class MainClassTest {
cmd.executeAndAssertHelloAppImageCreated();
} else {
cmd.executeAndAssertImageCreated();
var appVerifier = HelloApp.assertMainLauncher(cmd);
if (appVerifier != null) {
HelloApp.assertMainLauncher(cmd).ifPresent(appVerifier -> {
List<String> output = appVerifier
.saveOutput(true)
.expectedExitCode(1)
@ -249,7 +248,7 @@ public final class MainClassTest {
TKit.assertTextStream(String.format(
"Error: Could not find or load main class %s",
nonExistingMainClass)).apply(output);
}
});
}
CfgFile cfg = cmd.readLauncherCfgFile();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,12 +21,14 @@
* questions.
*/
import static jdk.jpackage.test.WindowsHelper.killAppLauncherProcess;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
/**
* Test that terminating of the parent app launcher process automatically
@ -46,7 +48,7 @@ import jdk.jpackage.test.JPackageCommand;
public class Win8301247Test {
@Test
public void test() throws InterruptedException {
public void test() throws InterruptedException, ExecutionException {
var cmd = JPackageCommand.helloAppImage().ignoreFakeRuntime();
// Launch the app in a way it doesn't exit to let us trap app laucnher
@ -54,20 +56,41 @@ public class Win8301247Test {
cmd.addArguments("--java-options", "-Djpackage.test.noexit=true");
cmd.executeAndAssertImageCreated();
var f = new CompletableFuture<Process>();
// Launch the app in a separate thread
new Thread(() -> {
HelloApp.executeLauncher(cmd);
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
}).start();
// Wait a bit to let the app start
Thread.sleep(Duration.ofSeconds(10));
var mainLauncherProcess = f.get();
// Find the main app launcher process and kill it
killAppLauncherProcess(cmd, null, 2);
Optional<ProcessHandle> childProcess = Optional.empty();
// Wait a bit and check if child app launcher process is still running (it must NOT)
Thread.sleep(Duration.ofSeconds(5));
try {
// Wait a bit to let the app start
Thread.sleep(Duration.ofSeconds(10));
killAppLauncherProcess(cmd, null, 0);
try (var children = mainLauncherProcess.children()) {
childProcess = children.filter(p -> {
return mainLauncherProcess.info().command().equals(p.info().command());
}).findFirst();
}
TKit.assertTrue(childProcess.isPresent(),
String.format("Check the main launcher process with PID=%d restarted", mainLauncherProcess.pid()));
} finally {
// Kill the main app launcher process
TKit.trace("About to kill the main launcher process...");
mainLauncherProcess.destroyForcibly();
// Wait a bit and check if child app launcher process is still running (it must NOT)
Thread.sleep(Duration.ofSeconds(5));
childProcess.ifPresent(p -> {
TKit.assertTrue(!p.isAlive(), String.format(
"Check restarted main launcher process with PID=%d is not alive", p.pid()));
});
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -44,7 +44,6 @@ import static jdk.jpackage.test.HelloApp.configureAndExecute;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.TKit;
import static jdk.jpackage.test.WindowsHelper.killProcess;
public class WinChildProcessTest {
private static final Path TEST_APP_JAVA = TKit.TEST_SRC_ROOT
@ -52,7 +51,7 @@ public class WinChildProcessTest {
@Test
public static void test() {
long childPid = 0;
Optional<ProcessHandle> child = Optional.empty();
try {
JPackageCommand cmd = JPackageCommand
.helloAppImage(TEST_APP_JAVA + "*Hello")
@ -69,21 +68,18 @@ public class WinChildProcessTest {
String pidStr = output.get(0);
// parse child PID
childPid = Long.parseLong(pidStr.split("=", 2)[1]);
var childPid = Long.parseLong(pidStr.split("=", 2)[1]);
// Check whether the termination of third party application launcher
// also terminating the launched third party application
// If third party application is not terminated the test is
// successful else failure
Optional<ProcessHandle> processHandle = ProcessHandle.of(childPid);
boolean isAlive = processHandle.isPresent()
&& processHandle.get().isAlive();
TKit.assertTrue(isAlive, "Check child process is alive");
child = ProcessHandle.of(childPid);
boolean isAlive = child.map(ProcessHandle::isAlive).orElse(false);
TKit.assertTrue(isAlive, String.format("Check child process with PID=%d is alive", childPid));
} finally {
if (childPid != 0) {
// Kill only a specific child instance
killProcess(childPid);
}
TKit.trace("About to kill the child process...");
child.ifPresent(ProcessHandle::destroyForcibly);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -21,15 +21,18 @@
* questions.
*/
import static jdk.jpackage.test.WindowsHelper.killAppLauncherProcess;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.CfgFile;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
/* @test
* @bug 8340311
@ -47,7 +50,7 @@ import jdk.jpackage.test.JPackageCommand;
public class WinNoRestartTest {
@Test
public static void test() throws InterruptedException, IOException {
public static void test() throws InterruptedException, IOException, ExecutionException {
var cmd = JPackageCommand.helloAppImage().ignoreFakeRuntime();
// Configure test app to launch in a way it will not exit
@ -77,7 +80,7 @@ public class WinNoRestartTest {
private static record NoRerunConfig(NoRerunSectionConfig firstSection,
NoRerunSectionConfig secondSection, boolean expectedNoRestarted) {
void apply(JPackageCommand cmd, CfgFile origCfgFile) throws InterruptedException {
void apply(JPackageCommand cmd, CfgFile origCfgFile) throws InterruptedException, ExecutionException {
// Alter the main launcher .cfg file
var cfgFile = new CfgFile();
if (firstSection != null) {
@ -92,16 +95,40 @@ public class WinNoRestartTest {
// Save updated main launcher .cfg file
cfgFile.save(cmd.appLauncherCfgPath(null));
var f = new CompletableFuture<Process>();
// Launch the app in a separate thread
new Thread(() -> {
HelloApp.executeLauncher(cmd);
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
}).start();
// Wait a bit to let the app start
Thread.sleep(Duration.ofSeconds(10));
var mainLauncherProcess = f.get();
// Find the main app launcher process and kill it
killAppLauncherProcess(cmd, null, expectedNoRestarted ? 1 : 2);
try {
// Wait a bit to let the app start
Thread.sleep(Duration.ofSeconds(10));
try (var children = mainLauncherProcess.children()) {
Optional<String> childPid = children.filter(p -> {
return mainLauncherProcess.info().command().equals(p.info().command());
}).map(ProcessHandle::pid).map(Object::toString).findFirst();
Optional<String> expectedChildPid;
if (expectedNoRestarted) {
expectedChildPid = Optional.empty();
} else {
expectedChildPid = childPid.or(() -> {
return Optional.of("<some>");
});
}
TKit.assertEquals(expectedChildPid, childPid, String.format(
"Check the main launcher process with PID=%d restarted",
mainLauncherProcess.pid()));
}
} finally {
TKit.trace("About to kill the main launcher process...");
mainLauncherProcess.destroyForcibly();
}
}
}