mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8375061: Multiple jpackage tool providers may share the same logging config
Reviewed-by: almatvee
This commit is contained in:
parent
4d0ad0a4a3
commit
9ed0ecbcc1
@ -24,6 +24,7 @@
|
||||
*/
|
||||
package jdk.jpackage.internal;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@ -46,6 +47,18 @@ public final class Globals {
|
||||
return objectFactory(ObjectFactory.build(objectFactory).executorFactory(v).create());
|
||||
}
|
||||
|
||||
Log.Logger logger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public void loggerOutputStreams(PrintWriter out, PrintWriter err) {
|
||||
logger.setPrintWriter(out, err);
|
||||
}
|
||||
|
||||
public void loggerVerbose() {
|
||||
logger.setVerbose();
|
||||
}
|
||||
|
||||
public static int main(Supplier<Integer> mainBody) {
|
||||
if (INSTANCE.isBound()) {
|
||||
return mainBody.get();
|
||||
@ -65,6 +78,7 @@ public final class Globals {
|
||||
}
|
||||
|
||||
private ObjectFactory objectFactory = ObjectFactory.DEFAULT;
|
||||
private final Log.Logger logger = new Log.Logger();
|
||||
|
||||
private static final ScopedValue<Globals> INSTANCE = ScopedValue.newInstance();
|
||||
private static final Globals DEFAULT = new Globals();
|
||||
|
||||
@ -61,16 +61,6 @@ public class Log {
|
||||
this.err = err;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
if (err != null) {
|
||||
err.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void info(String msg) {
|
||||
if (out != null) {
|
||||
out.println(msg);
|
||||
@ -111,46 +101,27 @@ public class Log {
|
||||
}
|
||||
}
|
||||
|
||||
private static final InheritableThreadLocal<Logger> instance =
|
||||
new InheritableThreadLocal<Logger>() {
|
||||
@Override protected Logger initialValue() {
|
||||
return new Logger();
|
||||
}
|
||||
};
|
||||
|
||||
public static void setPrintWriter (PrintWriter out, PrintWriter err) {
|
||||
instance.get().setPrintWriter(out, err);
|
||||
}
|
||||
|
||||
public static void flush() {
|
||||
instance.get().flush();
|
||||
}
|
||||
|
||||
public static void info(String msg) {
|
||||
instance.get().info(msg);
|
||||
Globals.instance().logger().info(msg);
|
||||
}
|
||||
|
||||
public static void fatalError(String msg) {
|
||||
instance.get().fatalError(msg);
|
||||
Globals.instance().logger().fatalError(msg);
|
||||
}
|
||||
|
||||
public static void error(String msg) {
|
||||
instance.get().error(msg);
|
||||
}
|
||||
|
||||
public static void setVerbose() {
|
||||
instance.get().setVerbose();
|
||||
Globals.instance().logger().error(msg);
|
||||
}
|
||||
|
||||
public static boolean isVerbose() {
|
||||
return instance.get().isVerbose();
|
||||
return Globals.instance().logger().isVerbose();
|
||||
}
|
||||
|
||||
public static void verbose(String msg) {
|
||||
instance.get().verbose(msg);
|
||||
Globals.instance().logger().verbose(msg);
|
||||
}
|
||||
|
||||
public static void verbose(Throwable t) {
|
||||
instance.get().verbose(t);
|
||||
Globals.instance().logger().verbose(t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,8 +79,8 @@ public final class Main {
|
||||
|
||||
@Override
|
||||
public int run(PrintStream out, PrintStream err, String... args) {
|
||||
PrintWriter outWriter = new PrintWriter(out, true);
|
||||
PrintWriter errWriter = new PrintWriter(err, true);
|
||||
PrintWriter outWriter = toPrintWriter(out);
|
||||
PrintWriter errWriter = toPrintWriter(err);
|
||||
try {
|
||||
try {
|
||||
return run(outWriter, errWriter, args);
|
||||
@ -98,8 +98,8 @@ public final class Main {
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
var out = new PrintWriter(System.out, true);
|
||||
var err = new PrintWriter(System.err, true);
|
||||
var out = toPrintWriter(System.out);
|
||||
var err = toPrintWriter(System.err);
|
||||
System.exit(run(out, err, args));
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ public final class Main {
|
||||
Objects.requireNonNull(out);
|
||||
Objects.requireNonNull(err);
|
||||
|
||||
Log.setPrintWriter(out, err);
|
||||
Globals.instance().loggerOutputStreams(out, err);
|
||||
|
||||
final var runner = new Runner(t -> {
|
||||
new ErrorReporter(_ -> {
|
||||
@ -179,7 +179,7 @@ public final class Main {
|
||||
}
|
||||
|
||||
if (VERBOSE.containsIn(options)) {
|
||||
Log.setVerbose();
|
||||
Globals.instance().loggerVerbose();
|
||||
}
|
||||
|
||||
final var optionsProcessor = new OptionsProcessor(parsedOptionsBuilder, bundlingEnv);
|
||||
@ -310,6 +310,10 @@ public final class Main {
|
||||
return System.getProperty("java.version");
|
||||
}
|
||||
|
||||
private static PrintWriter toPrintWriter(PrintStream ps) {
|
||||
return new PrintWriter(ps, true, ps.charset());
|
||||
}
|
||||
|
||||
private enum DefaultBundlingEnvironmentLoader implements Supplier<CliBundlingEnvironment> {
|
||||
INSTANCE;
|
||||
|
||||
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jpackage.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.spi.ToolProvider;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class JPackageCommandTest extends JUnitAdapter.TestSrcInitializer {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testUseToolProvider(UseToolProviderTestSpec spec) {
|
||||
// Run the test with the new state to avoid UnsupportedOperationException
|
||||
// that will be thrown if it attempts to alter global variables in the default R/O state.
|
||||
TKit.withNewState(spec::test);
|
||||
}
|
||||
|
||||
private static List<UseToolProviderTestSpec> testUseToolProvider() {
|
||||
|
||||
var testCases = new ArrayList<UseToolProviderTestSpec>();
|
||||
|
||||
for (var globalToolProvider : ExecutableSetterType.values()) {
|
||||
for (var instanceToolProvider : ExecutableSetterType.values()) {
|
||||
testCases.add(new UseToolProviderTestSpec(globalToolProvider, instanceToolProvider));
|
||||
}
|
||||
}
|
||||
|
||||
return testCases;
|
||||
}
|
||||
|
||||
record UseToolProviderTestSpec(ExecutableSetterType globalType, ExecutableSetterType instanceType) {
|
||||
|
||||
UseToolProviderTestSpec {
|
||||
Objects.requireNonNull(globalType);
|
||||
Objects.requireNonNull(instanceType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s, global=%s", instanceType, globalType);
|
||||
}
|
||||
|
||||
void test() {
|
||||
|
||||
final Optional<ToolProvider> global;
|
||||
switch (globalType) {
|
||||
case SET_CUSTOM_TOOL_PROVIDER -> {
|
||||
global = Optional.of(createNewToolProvider("jpackage-mock-global"));
|
||||
JPackageCommand.useToolProviderByDefault(global.get());
|
||||
}
|
||||
case SET_DEFAULT_TOOL_PROVIDER -> {
|
||||
global = Optional.of(JavaTool.JPACKAGE.asToolProvider());
|
||||
JPackageCommand.useToolProviderByDefault();
|
||||
}
|
||||
case SET_PROCESS -> {
|
||||
global = Optional.empty();
|
||||
JPackageCommand.useExecutableByDefault();
|
||||
}
|
||||
case SET_NONE -> {
|
||||
global = Optional.empty();
|
||||
}
|
||||
default -> {
|
||||
throw ExceptionBox.reachedUnreachable();
|
||||
}
|
||||
}
|
||||
|
||||
var cmd = new JPackageCommand();
|
||||
|
||||
final Optional<ToolProvider> instance;
|
||||
switch (instanceType) {
|
||||
case SET_CUSTOM_TOOL_PROVIDER -> {
|
||||
instance = Optional.of(createNewToolProvider("jpackage-mock"));
|
||||
cmd.useToolProvider(instance.get());
|
||||
}
|
||||
case SET_DEFAULT_TOOL_PROVIDER -> {
|
||||
instance = Optional.of(JavaTool.JPACKAGE.asToolProvider());
|
||||
cmd.useToolProvider(true);
|
||||
}
|
||||
case SET_PROCESS -> {
|
||||
instance = Optional.empty();
|
||||
cmd.useToolProvider(false);
|
||||
}
|
||||
case SET_NONE -> {
|
||||
instance = Optional.empty();
|
||||
}
|
||||
default -> {
|
||||
throw ExceptionBox.reachedUnreachable();
|
||||
}
|
||||
}
|
||||
|
||||
var actual = cmd.createExecutor().getToolProvider();
|
||||
|
||||
switch (instanceType) {
|
||||
case SET_CUSTOM_TOOL_PROVIDER -> {
|
||||
assertSame(actual.get(), instance.get());
|
||||
assertTrue(cmd.isWithToolProvider());
|
||||
}
|
||||
case SET_DEFAULT_TOOL_PROVIDER -> {
|
||||
global.ifPresentOrElse(expected -> {
|
||||
assertEquals(expected.name(), actual.orElseThrow().name());
|
||||
}, () -> {
|
||||
assertEquals(instance.get().name(), actual.get().name());
|
||||
});
|
||||
assertTrue(cmd.isWithToolProvider());
|
||||
}
|
||||
case SET_PROCESS -> {
|
||||
assertFalse(actual.isPresent());
|
||||
assertFalse(cmd.isWithToolProvider());
|
||||
}
|
||||
case SET_NONE -> {
|
||||
switch (globalType) {
|
||||
case SET_CUSTOM_TOOL_PROVIDER -> {
|
||||
assertSame(global.get(), actual.get());
|
||||
assertTrue(cmd.isWithToolProvider());
|
||||
}
|
||||
case SET_DEFAULT_TOOL_PROVIDER -> {
|
||||
assertEquals(global.get().name(), actual.orElseThrow().name());
|
||||
assertTrue(cmd.isWithToolProvider());
|
||||
}
|
||||
case SET_PROCESS, SET_NONE -> {
|
||||
assertFalse(actual.isPresent());
|
||||
assertFalse(cmd.isWithToolProvider());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ToolProvider createNewToolProvider(String name) {
|
||||
return new ToolProvider() {
|
||||
@Override
|
||||
public int run(PrintWriter out, PrintWriter err, String... args) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum ExecutableSetterType {
|
||||
SET_DEFAULT_TOOL_PROVIDER,
|
||||
SET_CUSTOM_TOOL_PROVIDER,
|
||||
SET_PROCESS,
|
||||
SET_NONE,
|
||||
;
|
||||
}
|
||||
}
|
||||
@ -63,6 +63,7 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
}
|
||||
|
||||
public Executor() {
|
||||
commandOutputControl.dumpStdout(TKit.state().out()).dumpStderr(TKit.state().err());
|
||||
}
|
||||
|
||||
public Executor setExecutable(String v) {
|
||||
@ -85,6 +86,10 @@ public final class Executor extends CommandArguments<Executor> {
|
||||
return setToolProvider(v.asToolProvider());
|
||||
}
|
||||
|
||||
public Optional<ToolProvider> getToolProvider() {
|
||||
return Optional.ofNullable(toolProvider);
|
||||
}
|
||||
|
||||
public Optional<Path> getExecutable() {
|
||||
return Optional.ofNullable(executable);
|
||||
}
|
||||
|
||||
@ -52,8 +52,6 @@ import java.util.Optional;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@ -63,6 +61,7 @@ import java.util.spi.ToolProvider;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
import jdk.jpackage.internal.util.function.ThrowingConsumer;
|
||||
import jdk.jpackage.internal.util.function.ThrowingFunction;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
@ -77,6 +76,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public JPackageCommand() {
|
||||
toolProviderSource = new ToolProviderSource();
|
||||
prerequisiteActions = new Actions();
|
||||
verifyActions = new Actions();
|
||||
excludeStandardAsserts(StandardAssert.MAIN_LAUNCHER_DESCRIPTION);
|
||||
@ -84,7 +84,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
|
||||
private JPackageCommand(JPackageCommand cmd, boolean immutable) {
|
||||
args.addAll(cmd.args);
|
||||
withToolProvider = cmd.withToolProvider;
|
||||
toolProviderSource = cmd.toolProviderSource.copy();
|
||||
saveConsoleOutput = cmd.saveConsoleOutput;
|
||||
discardStdout = cmd.discardStdout;
|
||||
discardStderr = cmd.discardStderr;
|
||||
@ -770,7 +770,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
|
||||
public static void useToolProviderByDefault(ToolProvider jpackageToolProvider) {
|
||||
defaultToolProvider.set(Optional.of(jpackageToolProvider));
|
||||
TKit.state().setProperty(DefaultToolProviderKey.VALUE, Objects.requireNonNull(jpackageToolProvider));
|
||||
}
|
||||
|
||||
public static void useToolProviderByDefault() {
|
||||
@ -778,45 +778,22 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
|
||||
public static void useExecutableByDefault() {
|
||||
defaultToolProvider.set(Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* In a separate thread calls {@link #useToolProviderByDefault(ToolProvider)}
|
||||
* with the specified {@code jpackageToolProvider} and then calls
|
||||
* {@code workload.run()}. Joins the thread.
|
||||
* <p>
|
||||
* The idea is to run the {@code workload} in the context of the specified
|
||||
* jpackage {@code ToolProvider} without altering the global variable holding
|
||||
* the default jpackage {@code ToolProvider}. The global variable is
|
||||
* thread-local; setting its value in a new thread doesn't alter its copy in the
|
||||
* calling thread.
|
||||
*
|
||||
* @param jpackageToolProvider jpackage {@code ToolProvider}
|
||||
* @param workload the workload to run
|
||||
*/
|
||||
public static void withToolProvider(Runnable workload, ToolProvider jpackageToolProvider) {
|
||||
Objects.requireNonNull(workload);
|
||||
Objects.requireNonNull(jpackageToolProvider);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
var oldValue = defaultToolProvider.get();
|
||||
useToolProviderByDefault(jpackageToolProvider);
|
||||
try {
|
||||
workload.run();
|
||||
} finally {
|
||||
defaultToolProvider.set(oldValue);
|
||||
}
|
||||
// Run the future in a new native thread. Don't run it in a virtual/pooled thread.
|
||||
// Pooled and/or virtual threads are problematic when used with inheritable thread-local variables.
|
||||
// TKit class depends on such a variable, which results in intermittent test failures
|
||||
// if the default executor runs this future.
|
||||
}, Executors.newThreadPerTaskExecutor(Thread.ofPlatform().factory())).join();
|
||||
TKit.state().setProperty(DefaultToolProviderKey.VALUE, null);
|
||||
}
|
||||
|
||||
public JPackageCommand useToolProvider(boolean v) {
|
||||
verifyMutable();
|
||||
withToolProvider = v;
|
||||
if (v) {
|
||||
toolProviderSource.useDefaultToolProvider();
|
||||
} else {
|
||||
toolProviderSource.useProcess();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public JPackageCommand useToolProvider(ToolProvider v) {
|
||||
verifyMutable();
|
||||
toolProviderSource.useToolProvider(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -928,9 +905,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
}
|
||||
|
||||
public boolean isWithToolProvider() {
|
||||
return Optional.ofNullable(withToolProvider).orElseGet(() -> {
|
||||
return defaultToolProvider.get().isPresent();
|
||||
});
|
||||
return toolProviderSource.toolProvider().isPresent();
|
||||
}
|
||||
|
||||
public JPackageCommand executePrerequisiteActions() {
|
||||
@ -938,21 +913,19 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private Executor createExecutor() {
|
||||
Executor createExecutor() {
|
||||
Executor exec = new Executor()
|
||||
.saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
|
||||
.discardStdout(discardStdout).discardStderr(discardStderr)
|
||||
.setDirectory(executeInDirectory)
|
||||
.addArguments(args);
|
||||
|
||||
if (isWithToolProvider()) {
|
||||
exec.setToolProvider(defaultToolProvider.get().orElseGet(JavaTool.JPACKAGE::asToolProvider));
|
||||
} else {
|
||||
exec.setExecutable(JavaTool.JPACKAGE);
|
||||
if (TKit.isWindows()) {
|
||||
exec.setWindowsTmpDir(System.getProperty("java.io.tmpdir"));
|
||||
}
|
||||
}
|
||||
toolProviderSource.toolProvider().ifPresentOrElse(exec::setToolProvider, () -> {
|
||||
exec.setExecutable(JavaTool.JPACKAGE);
|
||||
if (TKit.isWindows()) {
|
||||
exec.setWindowsTmpDir(System.getProperty("java.io.tmpdir"));
|
||||
}
|
||||
});
|
||||
|
||||
return exec;
|
||||
}
|
||||
@ -1731,7 +1704,70 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
private final List<Action> actions;
|
||||
}
|
||||
|
||||
private Boolean withToolProvider;
|
||||
private static final class ToolProviderSource {
|
||||
|
||||
ToolProviderSource copy() {
|
||||
return new ToolProviderSource(this);
|
||||
}
|
||||
|
||||
void useDefaultToolProvider() {
|
||||
customToolProvider = null;
|
||||
mode = Mode.USE_TOOL_PROVIDER;
|
||||
}
|
||||
|
||||
void useToolProvider(ToolProvider tp) {
|
||||
customToolProvider = Objects.requireNonNull(tp);
|
||||
mode = Mode.USE_TOOL_PROVIDER;
|
||||
}
|
||||
|
||||
void useProcess() {
|
||||
customToolProvider = null;
|
||||
mode = Mode.USE_PROCESS;
|
||||
}
|
||||
|
||||
Optional<ToolProvider> toolProvider() {
|
||||
switch (mode) {
|
||||
case USE_PROCESS -> {
|
||||
return Optional.empty();
|
||||
}
|
||||
case USE_TOOL_PROVIDER -> {
|
||||
if (customToolProvider != null) {
|
||||
return Optional.of(customToolProvider);
|
||||
} else {
|
||||
return TKit.state().findProperty(DefaultToolProviderKey.VALUE).map(ToolProvider.class::cast).or(() -> {
|
||||
return Optional.of(JavaTool.JPACKAGE.asToolProvider());
|
||||
});
|
||||
}
|
||||
}
|
||||
case INHERIT_DEFAULTS -> {
|
||||
return TKit.state().findProperty(DefaultToolProviderKey.VALUE).map(ToolProvider.class::cast);
|
||||
}
|
||||
default -> {
|
||||
throw ExceptionBox.reachedUnreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolProviderSource() {
|
||||
mode = Mode.INHERIT_DEFAULTS;
|
||||
}
|
||||
|
||||
private ToolProviderSource(ToolProviderSource other) {
|
||||
this.customToolProvider = other.customToolProvider;
|
||||
this.mode = other.mode;
|
||||
}
|
||||
|
||||
private enum Mode {
|
||||
INHERIT_DEFAULTS,
|
||||
USE_PROCESS,
|
||||
USE_TOOL_PROVIDER
|
||||
}
|
||||
|
||||
private ToolProvider customToolProvider;
|
||||
private Mode mode;
|
||||
}
|
||||
|
||||
private final ToolProviderSource toolProviderSource;
|
||||
private boolean saveConsoleOutput;
|
||||
private boolean discardStdout;
|
||||
private boolean discardStderr;
|
||||
@ -1747,12 +1783,10 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
|
||||
private Set<ReadOnlyPathAssert> readOnlyPathAsserts = Set.of(ReadOnlyPathAssert.values());
|
||||
private Set<StandardAssert> standardAsserts = Set.of(StandardAssert.values());
|
||||
private List<Consumer<Iterator<String>>> outputValidators = new ArrayList<>();
|
||||
private static InheritableThreadLocal<Optional<ToolProvider>> defaultToolProvider = new InheritableThreadLocal<>() {
|
||||
@Override
|
||||
protected Optional<ToolProvider> initialValue() {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
private enum DefaultToolProviderKey {
|
||||
VALUE
|
||||
}
|
||||
|
||||
private static final Map<String, PackageType> PACKAGE_TYPES = Stream.of(PackageType.values()).collect(toMap(PackageType::getType, x -> x));
|
||||
|
||||
|
||||
@ -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
|
||||
@ -27,17 +27,24 @@ import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.util.function.ExceptionBox;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
|
||||
|
||||
public final class Main {
|
||||
@ -47,10 +54,52 @@ public final class Main {
|
||||
}
|
||||
|
||||
public static void main(TestBuilder.Builder builder, String... args) throws Exception {
|
||||
Objects.requireNonNull(builder);
|
||||
|
||||
var argList = List.of(args);
|
||||
|
||||
var ignoreLogfile = argList.contains(CMDLINE_ARG_PREFIX + "ignore-logfile");
|
||||
|
||||
List<String> filteredArgs;
|
||||
if (ignoreLogfile) {
|
||||
filteredArgs = argList.stream().filter(Predicate.isEqual(CMDLINE_ARG_PREFIX + "ignore-logfile").negate()).toList();
|
||||
} else {
|
||||
filteredArgs = argList;
|
||||
}
|
||||
|
||||
ThrowingRunnable<Exception> workload = () -> {
|
||||
run(builder, filteredArgs);
|
||||
};
|
||||
|
||||
try {
|
||||
Optional.ofNullable(TKit.getConfigProperty("logfile")).filter(_ -> {
|
||||
return !ignoreLogfile;
|
||||
}).map(Path::of).ifPresentOrElse(logfile -> {
|
||||
|
||||
try (var out = new PrintStream(
|
||||
Files.newOutputStream(logfile, StandardOpenOption.CREATE, StandardOpenOption.APPEND),
|
||||
true,
|
||||
System.out.charset())) {
|
||||
|
||||
TKit.withOutput(workload, out, out);
|
||||
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
|
||||
}, () -> {
|
||||
ThrowingRunnable.toRunnable(workload).run();
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
throw ExceptionBox.unbox(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void run(TestBuilder.Builder builder, List<String> args) throws Exception {
|
||||
boolean listTests = false;
|
||||
List<TestInstance> tests = new ArrayList<>();
|
||||
try (TestBuilder testBuilder = builder.testConsumer(tests::add).create()) {
|
||||
Deque<String> argsAsList = new ArrayDeque<>(List.of(args));
|
||||
Deque<String> argsAsList = new ArrayDeque<>(args);
|
||||
while (!argsAsList.isEmpty()) {
|
||||
var arg = argsAsList.pop();
|
||||
TestBuilder.trace(String.format("Parsing [%s]...", arg));
|
||||
@ -115,7 +164,7 @@ public final class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
TKit.withExtraLogStream(() -> runTests(orderedTests));
|
||||
runTests(orderedTests);
|
||||
}
|
||||
|
||||
private static void runTests(List<TestInstance> tests) {
|
||||
|
||||
@ -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
|
||||
@ -38,7 +38,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
@ -52,6 +51,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@ -109,56 +109,45 @@ public final class TKit {
|
||||
throw throwUnknownPlatformError();
|
||||
}).get();
|
||||
|
||||
static void withExtraLogStream(ThrowingRunnable<? extends Exception> action) {
|
||||
if (state().extraLogStream != null) {
|
||||
ThrowingRunnable.toRunnable(action).run();
|
||||
} else {
|
||||
try (PrintStream logStream = openLogStream()) {
|
||||
withExtraLogStream(action, logStream);
|
||||
public static void withOutput(ThrowingRunnable<? extends Exception> action, PrintStream out, PrintStream err) {
|
||||
Objects.requireNonNull(action);
|
||||
Objects.requireNonNull(out);
|
||||
Objects.requireNonNull(err);
|
||||
|
||||
try {
|
||||
withState(action, stateBuilder -> {
|
||||
stateBuilder.out(out).err(err);
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
err.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void withExtraLogStream(ThrowingRunnable<? extends Exception> action, PrintStream logStream) {
|
||||
withNewState(action, stateBuilder -> {
|
||||
stateBuilder.extraLogStream(logStream);
|
||||
});
|
||||
}
|
||||
|
||||
public static void withMainLogStream(ThrowingRunnable<? extends Exception> action, PrintStream logStream) {
|
||||
withNewState(action, stateBuilder -> {
|
||||
stateBuilder.mainLogStream(logStream);
|
||||
});
|
||||
}
|
||||
|
||||
public static void withStackTraceStream(ThrowingRunnable<? extends Exception> action, PrintStream logStream) {
|
||||
withNewState(action, stateBuilder -> {
|
||||
stateBuilder.stackTraceStream(logStream);
|
||||
});
|
||||
}
|
||||
|
||||
public static State state() {
|
||||
return STATE.get();
|
||||
}
|
||||
|
||||
public static void state(State v) {
|
||||
STATE.set(Objects.requireNonNull(v));
|
||||
}
|
||||
|
||||
private static void withNewState(ThrowingRunnable<? extends Exception> action, Consumer<State.Builder> stateBuilderMutator) {
|
||||
public static void withState(ThrowingRunnable<? extends Exception> action, Consumer<State.Builder> stateBuilderMutator) {
|
||||
Objects.requireNonNull(action);
|
||||
Objects.requireNonNull(stateBuilderMutator);
|
||||
|
||||
var oldState = state();
|
||||
var builder = oldState.buildCopy();
|
||||
stateBuilderMutator.accept(builder);
|
||||
var newState = builder.create();
|
||||
try {
|
||||
state(newState);
|
||||
ThrowingRunnable.toRunnable(action).run();
|
||||
} finally {
|
||||
state(oldState);
|
||||
}
|
||||
var stateBuilder = state().buildCopy();
|
||||
stateBuilderMutator.accept(stateBuilder);
|
||||
withState(action, stateBuilder.create());
|
||||
}
|
||||
|
||||
public static void withNewState(ThrowingRunnable<? extends Exception> action) {
|
||||
withState(action, _ -> {});
|
||||
}
|
||||
|
||||
public static void withState(ThrowingRunnable<? extends Exception> action, State state) {
|
||||
Objects.requireNonNull(action);
|
||||
Objects.requireNonNull(state);
|
||||
ScopedValue.where(STATE, state).run(ThrowingRunnable.toRunnable(action));
|
||||
}
|
||||
|
||||
public static State state() {
|
||||
return STATE.orElse(DEFAULT_STATE);
|
||||
}
|
||||
|
||||
enum RunTestMode {
|
||||
@ -178,33 +167,19 @@ public final class TKit {
|
||||
throw new IllegalStateException("Unexpected nested Test.run() call");
|
||||
}
|
||||
|
||||
withExtraLogStream(() -> {
|
||||
tests.stream().forEach(test -> {
|
||||
withNewState(() -> {
|
||||
try {
|
||||
if (modes.contains(RunTestMode.FAIL_FAST)) {
|
||||
test.run();
|
||||
} else {
|
||||
ignoreExceptions(test).run();
|
||||
}
|
||||
} finally {
|
||||
Optional.ofNullable(state().extraLogStream).ifPresent(PrintStream::flush);
|
||||
}
|
||||
}, stateBuilder -> {
|
||||
stateBuilder.currentTest(test);
|
||||
});
|
||||
tests.stream().forEach(test -> {
|
||||
withState(() -> {
|
||||
if (modes.contains(RunTestMode.FAIL_FAST)) {
|
||||
test.run();
|
||||
} else {
|
||||
ignoreExceptions(test).run();
|
||||
}
|
||||
}, stateBuilder -> {
|
||||
stateBuilder.currentTest(test);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static <T> T runAdhocTest(ThrowingSupplier<T, ? extends Exception> action) {
|
||||
final List<T> box = new ArrayList<>();
|
||||
runAdhocTest(() -> {
|
||||
box.add(action.get());
|
||||
});
|
||||
return box.getFirst();
|
||||
}
|
||||
|
||||
static void runAdhocTest(ThrowingRunnable<? extends Exception> action) {
|
||||
Objects.requireNonNull(action);
|
||||
|
||||
@ -281,10 +256,7 @@ public final class TKit {
|
||||
static void log(String v) {
|
||||
v = addTimestamp(v);
|
||||
var state = state();
|
||||
state.mainLogStream.println(v);
|
||||
if (state.extraLogStream != null) {
|
||||
state.extraLogStream.println(v);
|
||||
}
|
||||
state.out.println(v);
|
||||
}
|
||||
|
||||
static Path removeRootFromAbsolutePath(Path v) {
|
||||
@ -692,8 +664,7 @@ public final class TKit {
|
||||
|
||||
static void printStackTrace(Throwable throwable) {
|
||||
var state = state();
|
||||
Optional.ofNullable(state.extraLogStream).ifPresent(throwable::printStackTrace);
|
||||
throwable.printStackTrace(state.stackTraceStream);
|
||||
throwable.printStackTrace(state.err);
|
||||
}
|
||||
|
||||
private static String concatMessages(String msg, String msg2) {
|
||||
@ -1255,16 +1226,6 @@ public final class TKit {
|
||||
return new TextStreamVerifier(what);
|
||||
}
|
||||
|
||||
private static PrintStream openLogStream() {
|
||||
return state().logFile.map(logfile -> {
|
||||
try {
|
||||
return Files.newOutputStream(logfile, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}).map(PrintStream::new).orElse(null);
|
||||
}
|
||||
|
||||
public record PathSnapshot(List<String> contentHashes) {
|
||||
public PathSnapshot {
|
||||
contentHashes.forEach(Objects::requireNonNull);
|
||||
@ -1376,25 +1337,23 @@ public final class TKit {
|
||||
public static final class State {
|
||||
|
||||
private State(
|
||||
Optional<Path> logFile,
|
||||
TestInstance currentTest,
|
||||
PrintStream mainLogStream,
|
||||
PrintStream stackTraceStream,
|
||||
PrintStream extraLogStream,
|
||||
PrintStream out,
|
||||
PrintStream err,
|
||||
Map<Object, Object> properties,
|
||||
boolean trace,
|
||||
boolean traceAsserts,
|
||||
boolean verboseJPackage,
|
||||
boolean verboseTestSetup) {
|
||||
|
||||
Objects.requireNonNull(logFile);
|
||||
Objects.requireNonNull(mainLogStream);
|
||||
Objects.requireNonNull(stackTraceStream);
|
||||
Objects.requireNonNull(out);
|
||||
Objects.requireNonNull(err);
|
||||
Objects.requireNonNull(properties);
|
||||
|
||||
this.logFile = logFile;
|
||||
this.currentTest = currentTest;
|
||||
this.mainLogStream = mainLogStream;
|
||||
this.stackTraceStream = stackTraceStream;
|
||||
this.extraLogStream = extraLogStream;
|
||||
this.out = out;
|
||||
this.err = err;
|
||||
this.properties = Collections.synchronizedMap(properties);
|
||||
|
||||
this.trace = trace;
|
||||
this.traceAsserts = traceAsserts;
|
||||
@ -1403,11 +1362,30 @@ public final class TKit {
|
||||
this.verboseTestSetup = verboseTestSetup;
|
||||
}
|
||||
|
||||
|
||||
Builder buildCopy() {
|
||||
return build().initFrom(this);
|
||||
}
|
||||
|
||||
PrintStream out() {
|
||||
return out;
|
||||
}
|
||||
|
||||
PrintStream err() {
|
||||
return err;
|
||||
}
|
||||
|
||||
Optional<Object> findProperty(Object key) {
|
||||
return Optional.ofNullable(properties.get(Objects.requireNonNull(key)));
|
||||
}
|
||||
|
||||
void setProperty(Object key, Object value) {
|
||||
if (value == null) {
|
||||
properties.remove(Objects.requireNonNull(key));
|
||||
} else {
|
||||
properties.put(Objects.requireNonNull(key), value);
|
||||
}
|
||||
}
|
||||
|
||||
static Builder build() {
|
||||
return new Builder();
|
||||
}
|
||||
@ -1416,11 +1394,9 @@ public final class TKit {
|
||||
static final class Builder {
|
||||
|
||||
Builder initDefaults() {
|
||||
logFile = Optional.ofNullable(getConfigProperty("logfile")).map(Path::of);
|
||||
currentTest = null;
|
||||
mainLogStream = System.out;
|
||||
stackTraceStream = System.err;
|
||||
extraLogStream = null;
|
||||
out = System.out;
|
||||
err = System.err;
|
||||
|
||||
var logOptions = tokenizeConfigProperty("suppress-logging");
|
||||
if (logOptions == null) {
|
||||
@ -1444,15 +1420,17 @@ public final class TKit {
|
||||
verboseTestSetup = isNonOf.test(Set.of("init", "i"));
|
||||
}
|
||||
|
||||
mutable = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder initFrom(State state) {
|
||||
logFile = state.logFile;
|
||||
currentTest = state.currentTest;
|
||||
mainLogStream = state.mainLogStream;
|
||||
stackTraceStream = state.stackTraceStream;
|
||||
extraLogStream = state.extraLogStream;
|
||||
out = state.out;
|
||||
err = state.err;
|
||||
properties.clear();
|
||||
properties.putAll(state.properties);
|
||||
|
||||
trace = state.trace;
|
||||
traceAsserts = state.traceAsserts;
|
||||
@ -1463,54 +1441,67 @@ public final class TKit {
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder logFile(Optional<Path> v) {
|
||||
logFile = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder currentTest(TestInstance v) {
|
||||
currentTest = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder mainLogStream(PrintStream v) {
|
||||
mainLogStream = v;
|
||||
Builder out(PrintStream v) {
|
||||
out = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder stackTraceStream(PrintStream v) {
|
||||
stackTraceStream = v;
|
||||
Builder err(PrintStream v) {
|
||||
err = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder extraLogStream(PrintStream v) {
|
||||
extraLogStream = v;
|
||||
Builder property(Object key, Object value) {
|
||||
if (value == null) {
|
||||
properties.remove(Objects.requireNonNull(key));
|
||||
} else {
|
||||
properties.put(Objects.requireNonNull(key), value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder mutable(boolean v) {
|
||||
mutable = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
State create() {
|
||||
return new State(logFile, currentTest, mainLogStream, stackTraceStream, extraLogStream, trace, traceAsserts, verboseJPackage, verboseTestSetup);
|
||||
return new State(
|
||||
currentTest,
|
||||
out,
|
||||
err,
|
||||
mutable ? new HashMap<>(properties) : Map.copyOf(properties),
|
||||
trace,
|
||||
traceAsserts,
|
||||
verboseJPackage,
|
||||
verboseTestSetup);
|
||||
}
|
||||
|
||||
private Optional<Path> logFile;
|
||||
private TestInstance currentTest;
|
||||
private PrintStream mainLogStream;
|
||||
private PrintStream stackTraceStream;
|
||||
private PrintStream extraLogStream;
|
||||
private PrintStream out;
|
||||
private PrintStream err;
|
||||
private Map<Object, Object> properties = new HashMap<>();
|
||||
|
||||
private boolean trace;
|
||||
private boolean traceAsserts;
|
||||
|
||||
private boolean verboseJPackage;
|
||||
private boolean verboseTestSetup;
|
||||
|
||||
private boolean mutable = true;
|
||||
}
|
||||
|
||||
|
||||
private final Optional<Path> logFile;
|
||||
private final TestInstance currentTest;
|
||||
private final PrintStream mainLogStream;
|
||||
private final PrintStream stackTraceStream;
|
||||
private final PrintStream extraLogStream;
|
||||
private final PrintStream out;
|
||||
private final PrintStream err;
|
||||
|
||||
private final Map<Object, Object> properties;
|
||||
|
||||
private final boolean trace;
|
||||
private final boolean traceAsserts;
|
||||
@ -1520,10 +1511,6 @@ public final class TKit {
|
||||
}
|
||||
|
||||
|
||||
private static final InheritableThreadLocal<State> STATE = new InheritableThreadLocal<>() {
|
||||
@Override
|
||||
protected State initialValue() {
|
||||
return State.build().initDefaults().create();
|
||||
}
|
||||
};
|
||||
private static final ScopedValue<State> STATE = ScopedValue.newInstance();
|
||||
private static final State DEFAULT_STATE = State.build().initDefaults().mutable(false).create();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 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
|
||||
@ -200,13 +200,14 @@ public class OptionsValidationFailTest {
|
||||
Stream.of("--jpt-run=ErrorTest")
|
||||
).flatMap(x -> x).toArray(String[]::new)).map(dynamicTest -> {
|
||||
return DynamicTest.dynamicTest(dynamicTest.getDisplayName(), () -> {
|
||||
JPackageCommand.withToolProvider(() -> {
|
||||
TKit.withNewState(() -> {
|
||||
JPackageCommand.useToolProviderByDefault(jpackageToolProviderMock);
|
||||
try {
|
||||
dynamicTest.getExecutable().execute();
|
||||
} catch (Throwable t) {
|
||||
throw ExceptionBox.toUnchecked(ExceptionBox.unbox(t));
|
||||
}
|
||||
}, jpackageToolProviderMock);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 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
|
||||
@ -29,11 +29,11 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jpackage.internal.util.TeeOutputStream;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
@ -70,12 +70,16 @@ public class JUnitAdapter {
|
||||
|
||||
static List<String> captureJPackageTestLog(ThrowingRunnable<? extends Exception> runnable) {
|
||||
final var buf = new ByteArrayOutputStream();
|
||||
try (PrintStream ps = new PrintStream(buf, true, StandardCharsets.UTF_8)) {
|
||||
TKit.withExtraLogStream(runnable, ps);
|
||||
}
|
||||
var ps = new PrintStream(buf, false, TKit.state().out().charset());
|
||||
|
||||
final var out = new PrintStream(new TeeOutputStream(List.of(TKit.state().out(), ps)), true, ps.charset());
|
||||
|
||||
TKit.withOutput(runnable, out, TKit.state().err());
|
||||
|
||||
ps.flush();
|
||||
|
||||
try (final var in = new ByteArrayInputStream(buf.toByteArray());
|
||||
final var reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
final var reader = new InputStreamReader(in, ps.charset());
|
||||
final var bufReader = new BufferedReader(reader)) {
|
||||
return bufReader.lines().map(line -> {
|
||||
// Skip timestamp
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, 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
|
||||
@ -23,7 +23,6 @@
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
@ -34,15 +33,12 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.stream.IntStream;
|
||||
import jdk.jpackage.internal.util.function.ThrowingRunnable;
|
||||
import jdk.jpackage.internal.util.Slot;
|
||||
import jdk.jpackage.test.Annotations.ParameterSupplier;
|
||||
import jdk.jpackage.test.Annotations.Test;
|
||||
import jdk.jpackage.test.HelloApp;
|
||||
import jdk.jpackage.test.JPackageCommand;
|
||||
import jdk.jpackage.test.JavaTool;
|
||||
import jdk.jpackage.test.JavaAppDesc;
|
||||
import jdk.jpackage.test.Main;
|
||||
import jdk.jpackage.test.PackageTest;
|
||||
@ -66,10 +62,10 @@ public class AsyncTest {
|
||||
|
||||
// Create test jar only once.
|
||||
// Besides of saving time, this avoids asynchronous invocations of java tool provider that randomly fail.
|
||||
APP_JAR.set(HelloApp.createBundle(JavaAppDesc.parse("Hello!"), TKit.workDir()));
|
||||
var appJar = HelloApp.createBundle(JavaAppDesc.parse("Hello!"), TKit.workDir());
|
||||
|
||||
//
|
||||
// Run test cases from InternalAsyncTest class asynchronously.
|
||||
// Run test cases from AsyncInnerTest class asynchronously.
|
||||
// Spawn a thread for every test case.
|
||||
// Input data for test cases will be cooked asynchronously but in a safe way because every test case has an isolated work directory.
|
||||
// Multiple jpackage tool provider instances will be invoked asynchronously.
|
||||
@ -79,14 +75,10 @@ public class AsyncTest {
|
||||
|
||||
var testFuncNames = List.of("testAppImage", "testNativeBundle");
|
||||
|
||||
var runArg = String.format("--jpt-run=%s", AsyncInnerTest.class.getName());
|
||||
|
||||
var futures = executor.invokeAll(IntStream.range(0, JOB_COUNT).mapToObj(Integer::toString).<Workload>mapMulti((idx, consumer) -> {
|
||||
for (var testFuncName : testFuncNames) {
|
||||
var id = String.format("%s(%s)", testFuncName, idx);
|
||||
consumer.accept(new Workload(() -> {
|
||||
Main.main(runArg, String.format("--jpt-include=%s", id));
|
||||
}, id));
|
||||
consumer.accept(new Workload(id, appJar));
|
||||
}
|
||||
}).toList());
|
||||
|
||||
@ -99,10 +91,8 @@ public class AsyncTest {
|
||||
|
||||
for (var future : futures) {
|
||||
var result = future.get();
|
||||
TKit.trace(String.format("[%s] STDOUT BEGIN\n%s", result.id(), result.stdoutBuffer()));
|
||||
TKit.trace(String.format("[%s] STDOUT END", result.id()));
|
||||
TKit.trace(String.format("[%s] STDERR BEGIN\n%s", result.id(), result.stderrBuffer()));
|
||||
TKit.trace(String.format("[%s] STDERR END", result.id()));
|
||||
TKit.trace(String.format("[%s] OUTPUT BEGIN\n%s", result.testCaseId(), result.testOutput()));
|
||||
TKit.trace(String.format("[%s] OUTPUT END", result.testCaseId()));
|
||||
result.exception().filter(Predicate.not(TKit::isSkippedException)).ifPresent(fatalError::set);
|
||||
}
|
||||
|
||||
@ -142,80 +132,56 @@ public class AsyncTest {
|
||||
}
|
||||
|
||||
|
||||
private record Result(String stdoutBuffer, String stderrBuffer, String id, Optional<Exception> exception) {
|
||||
private record Result(String testOutput, String testCaseId, Optional<Exception> exception) {
|
||||
|
||||
Result {
|
||||
Objects.requireNonNull(stdoutBuffer);
|
||||
Objects.requireNonNull(stderrBuffer);
|
||||
Objects.requireNonNull(id);
|
||||
Objects.requireNonNull(testOutput);
|
||||
Objects.requireNonNull(testCaseId);
|
||||
Objects.requireNonNull(exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private record Workload(
|
||||
ByteArrayOutputStream stdoutBuffer,
|
||||
ByteArrayOutputStream stderrBuffer,
|
||||
ThrowingRunnable<? extends Exception> runnable,
|
||||
String id) implements Callable<Result> {
|
||||
String testCaseId,
|
||||
ByteArrayOutputStream outputSink,
|
||||
Path appJar) implements Callable<Result> {
|
||||
|
||||
Workload {
|
||||
Objects.requireNonNull(stdoutBuffer);
|
||||
Objects.requireNonNull(stderrBuffer);
|
||||
Objects.requireNonNull(runnable);
|
||||
Objects.requireNonNull(id);
|
||||
Objects.requireNonNull(testCaseId);
|
||||
Objects.requireNonNull(outputSink);
|
||||
Objects.requireNonNull(appJar);
|
||||
}
|
||||
|
||||
Workload(ThrowingRunnable<? extends Exception> runnable, String id) {
|
||||
this(new ByteArrayOutputStream(), new ByteArrayOutputStream(), runnable, id);
|
||||
Workload(String testCaseId, Path appJar) {
|
||||
this(testCaseId, new ByteArrayOutputStream(), appJar);
|
||||
}
|
||||
|
||||
private String stdoutBufferAsString() {
|
||||
return new String(stdoutBuffer.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String stderrBufferAsString() {
|
||||
return new String(stderrBuffer.toByteArray(), StandardCharsets.UTF_8);
|
||||
private String testOutput() {
|
||||
return new String(outputSink.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result call() {
|
||||
// Reset the current test inherited in the state from the parent thread.
|
||||
TKit.state(DEFAULT_STATE);
|
||||
|
||||
var defaultToolProvider = JavaTool.JPACKAGE.asToolProvider();
|
||||
|
||||
JPackageCommand.useToolProviderByDefault(new ToolProvider() {
|
||||
|
||||
@Override
|
||||
public int run(PrintWriter out, PrintWriter err, String... args) {
|
||||
try (var bufOut = new PrintWriter(stdoutBuffer, true, StandardCharsets.UTF_8);
|
||||
var bufErr = new PrintWriter(stderrBuffer, true, StandardCharsets.UTF_8)) {
|
||||
return defaultToolProvider.run(bufOut, bufErr, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return defaultToolProvider.name();
|
||||
}
|
||||
});
|
||||
var runArg = String.format("--jpt-run=%s", AsyncInnerTest.class.getName());
|
||||
|
||||
Optional<Exception> err = Optional.empty();
|
||||
try (var bufOut = new PrintStream(stdoutBuffer, true, StandardCharsets.UTF_8);
|
||||
var bufErr = new PrintStream(stderrBuffer, true, StandardCharsets.UTF_8)) {
|
||||
TKit.withStackTraceStream(() -> {
|
||||
TKit.withMainLogStream(runnable, bufOut);
|
||||
}, bufErr);
|
||||
try {
|
||||
try (var out = new PrintStream(outputSink, false, System.out.charset())) {
|
||||
ScopedValue.where(APP_JAR, appJar).run(() -> {
|
||||
TKit.withOutput(() -> {
|
||||
JPackageCommand.useToolProviderByDefault();
|
||||
Main.main("--jpt-ignore-logfile", runArg, String.format("--jpt-include=%s", testCaseId));
|
||||
}, out, out);
|
||||
});
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
err = Optional.of(ex);
|
||||
}
|
||||
return new Result(stdoutBufferAsString(), stderrBufferAsString(), id, err);
|
||||
return new Result(testOutput(), testCaseId, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final int JOB_COUNT = 30;
|
||||
private static final TKit.State DEFAULT_STATE = TKit.state();
|
||||
private static final InheritableThreadLocal<Path> APP_JAR = new InheritableThreadLocal<>();
|
||||
private static final ScopedValue<Path> APP_JAR = ScopedValue.newInstance();
|
||||
}
|
||||
|
||||
@ -56,11 +56,14 @@ public class Win8301247Test {
|
||||
cmd.addArguments("--java-options", "-Djpackage.test.noexit=true");
|
||||
cmd.executeAndAssertImageCreated();
|
||||
|
||||
var state = TKit.state();
|
||||
var f = new CompletableFuture<Process>();
|
||||
|
||||
// Launch the app in a separate thread
|
||||
new Thread(() -> {
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
TKit.withState(() -> {
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
}, state);
|
||||
}).start();
|
||||
|
||||
var mainLauncherProcess = f.get();
|
||||
|
||||
@ -95,11 +95,14 @@ public class WinNoRestartTest {
|
||||
// Save updated main launcher .cfg file
|
||||
cfgFile.save(cmd.appLauncherCfgPath(null));
|
||||
|
||||
var state = TKit.state();
|
||||
var f = new CompletableFuture<Process>();
|
||||
|
||||
// Launch the app in a separate thread
|
||||
new Thread(() -> {
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
TKit.withState(() -> {
|
||||
HelloApp.assertMainLauncher(cmd).get().processListener(f::complete).execute();
|
||||
}, state);
|
||||
}).start();
|
||||
|
||||
var mainLauncherProcess = f.get();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user