8351372: Improve negative tests coverage of jpackage

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2025-03-17 18:38:28 +00:00
parent 3239919a5a
commit 3aa6d62afe
37 changed files with 1662 additions and 744 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, 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
@ -166,8 +166,7 @@ public class DeployParams {
}
} else {
if (!hasInput && !hasAppImage) {
throw new PackagerException(
"ERR_MissingArgument", "--input");
throw new PackagerException("error.no-input-parameter");
}
}
} else {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -57,9 +57,7 @@ final class DottedVersion {
if (!greedy) {
return null;
} else {
throw new IllegalArgumentException(MessageFormat.format(I18N.
getString("error.version-string-zero-length-component"),
version));
ds.throwException();
}
}
@ -77,8 +75,7 @@ final class DottedVersion {
}).takeWhile(Objects::nonNull).toArray(BigInteger[]::new);
suffix = ds.getUnprocessedString();
if (!suffix.isEmpty() && greedy) {
throw new IllegalArgumentException(MessageFormat.format(I18N.getString(
"error.version-string-invalid-component"), version, suffix));
ds.throwException();
}
}
}
@ -89,7 +86,7 @@ final class DottedVersion {
this.input = input;
}
public String getNextDigits() {
String getNextDigits() {
if (stoped) {
return null;
}
@ -130,10 +127,29 @@ final class DottedVersion {
return sb.toString();
}
public String getUnprocessedString() {
String getUnprocessedString() {
return input.substring(cursor);
}
void throwException() {
final String tail;
if (lastDotPos >= 0) {
tail = input.substring(lastDotPos + 1);
} else {
tail = getUnprocessedString();
}
final String errMessage;
if (tail.isEmpty()) {
errMessage = MessageFormat.format(I18N.getString(
"error.version-string-zero-length-component"), input);
} else {
errMessage = MessageFormat.format(I18N.getString(
"error.version-string-invalid-component"), input, tail);
}
throw new IllegalArgumentException(errMessage);
}
private int cursor;
private int lastDotPos = -1;
private boolean stoped;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2025, 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
@ -185,10 +185,6 @@ final class LauncherData {
}
Path mainJarDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
if (mainJarDir == null && launcherData.qualifiedClassName == null) {
throw new ConfigException(I18N.getString("error.no-input-parameter"),
null);
}
final Path mainJarPath;
if (launcherData.mainJarName != null && mainJarDir != null) {

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2025, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.internal.util;
import static java.util.stream.Collectors.joining;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public final class TokenReplace {
private record TokenCut(String[] main, String[] sub) {
static String[] orderTokens(String... tokens) {
if (tokens.length == 0) {
throw new IllegalArgumentException("Empty token list");
}
final var orderedTokens = Stream.of(tokens)
.sorted(Comparator.<String>naturalOrder().thenComparing(Comparator.comparingInt(String::length)))
.distinct()
.toArray(String[]::new);
if (orderedTokens[0].isEmpty()) {
throw new IllegalArgumentException("Empty token in the list of tokens");
}
return orderedTokens;
}
static TokenCut createFromOrderedTokens(String... tokens) {
final List<Integer> subTokens = new ArrayList<>();
for (var i = 0; i < tokens.length - 1; ++i) {
final var x = tokens[i];
for (var j = i + 1; j < tokens.length; ++j) {
final var y = tokens[j];
if (y.contains(x)) {
subTokens.add(i);
}
}
}
if (subTokens.isEmpty()) {
return new TokenCut(tokens, null);
} else {
final var main = IntStream.range(0, tokens.length)
.mapToObj(Integer::valueOf)
.filter(Predicate.not(subTokens::contains))
.map(i -> {
return tokens[i];
}).toArray(String[]::new);
final var sub = subTokens.stream().map(i -> {
return tokens[i];
}).toArray(String[]::new);
return new TokenCut(main, sub);
}
}
@Override
public String toString() {
return String.format("TokenCut(main=%s, sub=%s)", Arrays.toString(main), Arrays.toString(sub));
}
}
public TokenReplace(String... tokens) {
tokens = TokenCut.orderTokens(tokens);
this.tokens = tokens;
regexps = new ArrayList<>();
for(;;) {
final var tokenCut = TokenCut.createFromOrderedTokens(tokens);
regexps.add(Pattern.compile(Stream.of(tokenCut.main()).map(Pattern::quote).collect(joining("|", "(", ")"))));
if (tokenCut.sub() == null) {
break;
}
tokens = tokenCut.sub();
}
}
public String applyTo(String str, Function<String, Object> tokenValueSupplier) {
Objects.requireNonNull(str);
Objects.requireNonNull(tokenValueSupplier);
for (final var regexp : regexps) {
str = regexp.matcher(str).replaceAll(mr -> {
final var token = mr.group();
return Matcher.quoteReplacement(Objects.requireNonNull(tokenValueSupplier.apply(token), () -> {
return String.format("Null value for token [%s]", token);
}).toString());
});
}
return str;
}
public String recursiveApplyTo(String str, Function<String, Object> tokenValueSupplier) {
String newStr;
int counter = tokens.length + 1;
while (!(newStr = applyTo(str, tokenValueSupplier)).equals(str)) {
str = newStr;
if (counter-- == 0) {
throw new IllegalStateException("Infinite recursion");
}
}
return newStr;
}
@Override
public int hashCode() {
// Auto generated code
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(tokens);
return result;
}
@Override
public boolean equals(Object obj) {
// Auto generated code
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TokenReplace other = (TokenReplace) obj;
return Arrays.equals(tokens, other.tokens);
}
@Override
public String toString() {
return "TokenReplace(" + String.join("|", tokens) + ")";
}
public static TokenReplace combine(TokenReplace x, TokenReplace y) {
return new TokenReplace(Stream.of(x.tokens, y.tokens).flatMap(Stream::of).toArray(String[]::new));
}
public static Function<String, Object> createCachingTokenValueSupplier(Map<String, Supplier<Object>> tokenValueSuppliers) {
Objects.requireNonNull(tokenValueSuppliers);
final Map<String, Object> cache = new HashMap<>();
return token -> {
final var value = cache.computeIfAbsent(token, k -> {
final var tokenValueSupplier = Objects.requireNonNull(tokenValueSuppliers.get(token), () -> {
return String.format("No token value supplier for token [%s]", token);
});
return Optional.ofNullable(tokenValueSupplier.get()).orElse(NULL_SUPPLIED);
});
if (value == NULL_SUPPLIED) {
throw new NullPointerException(String.format("Null value for token [%s]", token));
}
return value;
};
}
private final String[] tokens;
private final transient List<Pattern> regexps;
private final static Object NULL_SUPPLIED = new Object();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2025, 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
@ -25,15 +25,16 @@
package jdk.jpackage.main;
import jdk.internal.opt.CommandLine;
import jdk.jpackage.internal.Arguments;
import jdk.jpackage.internal.Log;
import jdk.jpackage.internal.CLIHelp;
import java.io.PrintWriter;
import java.util.ResourceBundle;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.NoSuchFileException;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import jdk.internal.opt.CommandLine;
import jdk.jpackage.internal.Arguments;
import jdk.jpackage.internal.CLIHelp;
import jdk.jpackage.internal.Log;
public class Main {
@ -69,7 +70,7 @@ public class Main {
String[] newArgs;
try {
newArgs = CommandLine.parse(args);
} catch (FileNotFoundException fnfe) {
} catch (FileNotFoundException|NoSuchFileException fnfe) {
Log.fatalError(MessageFormat.format(I18N.getString(
"ERR_CannotParseOptions"), fnfe.getMessage()));
return 1;

View File

@ -97,12 +97,12 @@ public class PackageTestTest extends JUnitAdapter {
private final int tickCount;
}
private final static int ERROR_EXIT_CODE_JPACKAGE = 35;
private final static int ERROR_EXIT_CODE_INSTALL = 27;
private static final int ERROR_EXIT_CODE_JPACKAGE = 35;
private static final int ERROR_EXIT_CODE_INSTALL = 27;
private final static CallbackFactory NEVER = new CallbackFactory(0);
private final static CallbackFactory ONCE = new CallbackFactory(1);
private final static CallbackFactory TWICE = new CallbackFactory(2);
private static final CallbackFactory NEVER = new CallbackFactory(0);
private static final CallbackFactory ONCE = new CallbackFactory(1);
private static final CallbackFactory TWICE = new CallbackFactory(2);
enum BundleVerifier {
ONCE_SUCCESS(ONCE),
@ -238,7 +238,7 @@ public class PackageTestTest extends JUnitAdapter {
private final int expectedJPackageExitCode;
}
private final static class CountingInstaller extends TickCounter implements Function<JPackageCommand, Integer> {
private static final class CountingInstaller extends TickCounter implements Function<JPackageCommand, Integer> {
@Override
public Integer apply(JPackageCommand cmd) {
@ -376,7 +376,7 @@ public class PackageTestTest extends JUnitAdapter {
};
}).setExpectedExitCode(expectedJPackageExitCode)
.setExpectedInstallExitCode(handlersSpec.installExitCode)
.isPackageTypeSupported(type -> true)
.isPackageTypeEnabled(type -> true)
.forTypes().packageHandlers(handlers);
}
@ -439,7 +439,7 @@ public class PackageTestTest extends JUnitAdapter {
}
}
private final static class TestSpecBuilder {
private static final class TestSpecBuilder {
TestSpecBuilder type(PackageType v) {
type = Objects.requireNonNull(v);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, 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
@ -22,20 +22,60 @@
*/
package jdk.jpackage.test;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Stream;
public final class CannedFormattedString {
public record CannedFormattedString(BiFunction<String, Object[], String> formatter, String key, Object[] args) {
CannedFormattedString(BiFunction<String, Object[], String> formatter,
String key, Object[] args) {
this.formatter = formatter;
this.key = key;
this.args = args;
@FunctionalInterface
public interface CannedArgument {
public String value();
}
public static Object cannedArgument(Supplier<Object> supplier, String label) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(label);
return new CannedArgument() {
@Override
public String value() {
return supplier.get().toString();
}
@Override
public String toString( ) {
return label;
}
};
}
public static Object cannedAbsolutePath(Path v) {
return cannedArgument(() -> v.toAbsolutePath(), String.format("AbsolutePath(%s)", v));
}
public static Object cannedAbsolutePath(String v) {
return cannedAbsolutePath(Path.of(v));
}
public CannedFormattedString {
Objects.requireNonNull(formatter);
Objects.requireNonNull(key);
Objects.requireNonNull(args);
List.of(args).forEach(Objects::requireNonNull);
}
public String getValue() {
return formatter.apply(key, args);
return formatter.apply(key, Stream.of(args).map(arg -> {
if (arg instanceof CannedArgument cannedArg) {
return cannedArg.value();
} else {
return arg;
}
}).toArray());
}
@Override
@ -46,8 +86,4 @@ public final class CannedFormattedString {
return String.format("%s+%s", key, List.of(args));
}
}
private final BiFunction<String, Object[], String> formatter;
private final String key;
private final Object[] args;
}

View File

@ -33,8 +33,10 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@ -92,6 +94,13 @@ public final class Executor extends CommandArguments<Executor> {
public Executor removeEnvVar(String envVarName) {
removeEnvVars.add(Objects.requireNonNull(envVarName));
setEnvVars.remove(envVarName);
return this;
}
public Executor setEnvVar(String envVarName, String envVarValue) {
setEnvVars.put(Objects.requireNonNull(envVarName), Objects.requireNonNull(envVarValue));
removeEnvVars.remove(envVarName);
return this;
}
@ -370,11 +379,27 @@ public final class Executor extends CommandArguments<Executor> {
builder.directory(directory.toFile());
sb.append(String.format("; in directory [%s]", directory));
}
if (!removeEnvVars.isEmpty()) {
final var envComm = Comm.compare(builder.environment().keySet(), removeEnvVars);
builder.environment().keySet().removeAll(envComm.common());
if (!setEnvVars.isEmpty()) {
final var defaultEnv = builder.environment();
final var envComm = Comm.compare(defaultEnv.keySet(), setEnvVars.keySet());
envComm.unique2().forEach(envVar -> {
trace(String.format("Adding %s=[%s] to environment", envVar, setEnvVars.get(envVar)));
});
envComm.common().forEach(envVar -> {
TKit.trace(String.format("Clearing %s in environment", envVar));
final var curValue = defaultEnv.get(envVar);
final var newValue = setEnvVars.get(envVar);
if (!curValue.equals(newValue)) {
trace(String.format("Setting %s=[%s] in environment", envVar, setEnvVars.get(envVar)));
}
});
defaultEnv.putAll(setEnvVars);
}
if (!removeEnvVars.isEmpty()) {
final var defaultEnv = builder.environment().keySet();
final var envComm = Comm.compare(defaultEnv, removeEnvVars);
defaultEnv.removeAll(envComm.common());
envComm.common().forEach(envVar -> {
trace(String.format("Clearing %s in environment", envVar));
});
}
@ -515,6 +540,7 @@ public final class Executor extends CommandArguments<Executor> {
private Set<SaveOutputType> saveOutputType;
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

@ -31,6 +31,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
@ -350,6 +351,7 @@ public final class HelloApp {
this.launcherPath = helloAppLauncher;
this.outputFilePath = TKit.workDir().resolve(OUTPUT_FILENAME);
this.params = new HashMap<>();
this.env = new HashMap<>();
this.defaultLauncherArgs = new ArrayList<>();
}
@ -363,6 +365,16 @@ public final class HelloApp {
return this;
}
public AppOutputVerifier addEnvironment(Map<String, String> v) {
env.putAll(v);
return this;
}
public AppOutputVerifier addEnvironmentVar(String name, String value) {
env.put(Objects.requireNonNull(name), Objects.requireNonNull(name));
return this;
}
public AppOutputVerifier addDefaultArguments(String... v) {
return addDefaultArguments(List.of(v));
}
@ -466,6 +478,10 @@ public final class HelloApp {
.setExecutable(executablePath)
.addArguments(List.of(args));
env.forEach((envVarName, envVarValue) -> {
executor.setEnvVar(envVarName, envVarValue);
});
return configureEnvironment(executor);
}
@ -476,6 +492,7 @@ public final class HelloApp {
private int expectedExitCode;
private final List<String> defaultLauncherArgs;
private final Map<String, String> params;
private final Map<String, String> env;
}
public static AppOutputVerifier assertApp(Path helloAppLauncher) {

View File

@ -76,7 +76,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
prerequisiteActions = new Actions(cmd.prerequisiteActions);
verifyActions = new Actions(cmd.verifyActions);
appLayoutAsserts = cmd.appLayoutAsserts;
outputValidator = cmd.outputValidator;
outputValidators = cmd.outputValidators;
executeInDirectory = cmd.executeInDirectory;
winMsiLogFile = cmd.winMsiLogFile;
}
@ -218,26 +218,27 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
}
public String name() {
String appImage = getArgumentValue("--app-image");
if (appImage != null) {
String name = AppImageFile.load(Path.of(appImage)).mainLauncherName();
// can be null if using foreign app-image
return ((name != null) ? name : getArgumentValue("--name"));
}
return getArgumentValue("--name", () -> getArgumentValue("--main-class"));
return nameFromAppImage().or(this::nameFromBasicArgs).or(this::nameFromRuntimeImage).orElseThrow();
}
public String installerName() {
verifyIsOfType(PackageType.NATIVE);
String installerName = getArgumentValue("--name",
() -> getArgumentValue("--main-class", () -> null));
if (installerName == null) {
String appImage = getArgumentValue("--app-image");
if (appImage != null) {
installerName = AppImageFile.load(Path.of(appImage)).mainLauncherName();
}
}
return installerName;
return nameFromBasicArgs().or(this::nameFromAppImage).or(this::nameFromRuntimeImage).orElseThrow();
}
private Optional<String> nameFromAppImage() {
return Optional.ofNullable(getArgumentValue("--app-image"))
.map(Path::of).map(AppImageFile::load).map(AppImageFile::mainLauncherName);
}
private Optional<String> nameFromRuntimeImage() {
return Optional.ofNullable(getArgumentValue("--runtime-image"))
.map(Path::of).map(Path::getFileName).map(Path::toString);
}
private Optional<String> nameFromBasicArgs() {
return Optional.ofNullable(getArgumentValue("--name")).or(
() -> Optional.ofNullable(getArgumentValue("--main-class")));
}
public boolean isRuntime() {
@ -273,7 +274,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
};
addPrerequisiteAction(cmd -> {
Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
Path fakeRuntimeDir = TKit.createTempDirectory("fake_runtime");
TKit.trace(String.format("Init fake runtime in [%s] directory",
fakeRuntimeDir));
@ -703,21 +704,53 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
}
public JPackageCommand validateOutput(TKit.TextStreamVerifier validator) {
return JPackageCommand.this.validateOutput(validator::apply);
return validateOutput(validator::apply);
}
public JPackageCommand validateOutput(Consumer<Stream<String>> validator) {
if (validator != null) {
saveConsoleOutput(true);
outputValidator = validator;
} else {
outputValidator = null;
}
Objects.requireNonNull(validator);
saveConsoleOutput(true);
outputValidators.add(validator);
return this;
}
public JPackageCommand validateOutput(CannedFormattedString str) {
return JPackageCommand.this.validateOutput(TKit.assertTextStream(str.getValue()));
@FunctionalInterface
public interface CannedArgument {
public String value(JPackageCommand cmd);
}
public static Object cannedArgument(Function<JPackageCommand, Object> supplier, String label) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(label);
return new CannedArgument() {
@Override
public String value(JPackageCommand cmd) {
return supplier.apply(cmd).toString();
}
@Override
public String toString( ) {
return label;
}
};
}
public String getValue(CannedFormattedString str) {
return new CannedFormattedString(str.formatter(), str.key(), Stream.of(str.args()).map(arg -> {
if (arg instanceof CannedArgument cannedArg) {
return cannedArg.value(this);
} else {
return arg;
}
}).toArray()).getValue();
}
public JPackageCommand validateOutput(CannedFormattedString... str) {
// Will look up the given errors in the order they are specified.
return validateOutput(Stream.of(str)
.map(this::getValue)
.map(TKit::assertTextStream)
.reduce(TKit.TextStreamVerifier::andThen).get());
}
public boolean isWithToolProvider() {
@ -798,7 +831,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
.createExecutor()
.execute(expectedExitCode);
if (outputValidator != null) {
for (final var outputValidator: outputValidators) {
outputValidator.accept(result.getOutput().stream());
}
@ -876,6 +909,9 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
copy.immutable = false;
copy.removeArgumentWithValue("--runtime-image");
copy.dmgInstallDir = cmd.appInstallationDirectory();
if (!copy.hasArgument("--name")) {
copy.addArguments("--name", cmd.nameFromRuntimeImage().orElseThrow());
}
return copy;
}
@ -1022,7 +1058,7 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
// to allow the jlink process to print exception stacktraces on any failure
addArgument("-J-Djlink.debug=true");
}
if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) {
if (!hasArgument("--runtime-image") && !hasArgument("--jlink-options") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) {
addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
}
@ -1195,14 +1231,14 @@ public class JPackageCommand extends CommandArguments<JPackageCommand> {
private Path executeInDirectory;
private Path winMsiLogFile;
private Set<AppLayoutAssert> appLayoutAsserts = Set.of(AppLayoutAssert.values());
private Consumer<Stream<String>> outputValidator;
private List<Consumer<Stream<String>>> outputValidators = new ArrayList<>();
private static boolean defaultWithToolProvider;
private static final Map<String, PackageType> PACKAGE_TYPES = Functional.identity(
() -> {
Map<String, PackageType> reply = new HashMap<>();
for (PackageType type : PackageType.values()) {
reply.put(type.getName(), type);
reply.put(type.getType(), type);
}
return reply;
}).get();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, 2025, 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
@ -64,7 +64,7 @@ public enum JPackageStringBundle {
}
}
public CannedFormattedString cannedFormattedString(String key, String ... args) {
public CannedFormattedString cannedFormattedString(String key, Object ... args) {
return new CannedFormattedString(this::getFormattedString, key, args);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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,6 +23,10 @@
package jdk.jpackage.test;
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.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
@ -32,9 +36,7 @@ import java.util.Deque;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import static java.util.stream.Collectors.toCollection;
import java.util.stream.Stream;
import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
public final class Main {
@ -88,9 +90,7 @@ public final class Main {
TKit.unbox(throwable);
} finally {
if (!success) {
TKit.log(
String.format("Error processing parameter=[%s]",
arg));
TKit.log(String.format("Error processing parameter=[%s]", arg));
}
}
}
@ -104,6 +104,13 @@ public final class Main {
// Just list the tests
orderedTests.forEach(test -> System.out.println(String.format(
"%s; workDir=[%s]", test.fullName(), test.workDir())));
}
orderedTests.stream().collect(toMap(TestInstance::fullName, x -> x, (x, y) -> {
throw new IllegalArgumentException(String.format("Multiple tests with the same description: [%s]", x.fullName()));
}));
if (listTests) {
return;
}

View File

@ -71,7 +71,7 @@ import jdk.jpackage.internal.util.function.ThrowingRunnable;
public final class PackageTest extends RunnablePackageTest {
public PackageTest() {
isPackageTypeSupported = PackageType::isSupported;
isPackageTypeEnabled = PackageType::isEnabled;
jpackageFactory = JPackageCommand::new;
packageHandlers = new HashMap<>();
disabledInstallers = new HashSet<>();
@ -102,7 +102,7 @@ public final class PackageTest extends RunnablePackageTest {
newTypes = Stream.of(types).collect(Collectors.toSet());
}
currentTypes = newTypes.stream()
.filter(isPackageTypeSupported)
.filter(isPackageTypeEnabled)
.filter(Predicate.not(excludeTypes::contains))
.collect(Collectors.toUnmodifiableSet());
return this;
@ -394,9 +394,9 @@ public final class PackageTest extends RunnablePackageTest {
return this;
}
PackageTest isPackageTypeSupported(Predicate<PackageType> v) {
PackageTest isPackageTypeEnabled(Predicate<PackageType> v) {
Objects.requireNonNull(v);
isPackageTypeSupported = v;
isPackageTypeEnabled = v;
return this;
}
@ -505,7 +505,7 @@ public final class PackageTest extends RunnablePackageTest {
case UNPACK -> {
cmd.setUnpackedPackageLocation(null);
final var unpackRootDir = TKit.createTempDirectory(
String.format("unpacked-%s", type.getName()));
String.format("unpacked-%s", type.getType()));
final Path unpackDir = packageHandlers.unpack(cmd, unpackRootDir);
if (!unpackDir.startsWith(TKit.workDir())) {
state.deleteUnpackDirs.add(unpackDir);
@ -618,7 +618,7 @@ public final class PackageTest extends RunnablePackageTest {
return processed(Action.UNPACK) && packageHandlers.unpackHandler().isEmpty();
}
private final static class State {
private static final class State {
private final Set<Action> packageActions = new HashSet<>();
private final List<Path> deleteUnpackDirs = new ArrayList<>();
}
@ -918,7 +918,7 @@ public final class PackageTest extends RunnablePackageTest {
private final Map<PackageType, PackageHandlers> packageHandlers;
private final Set<PackageType> disabledInstallers;
private final Set<PackageType> disabledUninstallers;
private Predicate<PackageType> isPackageTypeSupported;
private Predicate<PackageType> isPackageTypeEnabled;
private Supplier<JPackageCommand> jpackageFactory;
private boolean ignoreBundleOutputDir;
private boolean createMsiLog;

View File

@ -26,6 +26,7 @@ import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@ -48,19 +49,23 @@ public enum PackageType {
TKit.isLinux() ? "jdk.jpackage.internal.LinuxRpmBundler" : null),
MAC_DMG(".dmg", TKit.isOSX() ? "jdk.jpackage.internal.MacDmgBundler" : null),
MAC_PKG(".pkg", TKit.isOSX() ? "jdk.jpackage.internal.MacPkgBundler" : null),
IMAGE("app-image", null, null);
IMAGE;
PackageType() {
type = "app-image";
suffix = null;
supported = true;
enabled = true;
}
PackageType(String packageName, String bundleSuffix, String bundlerClass) {
name = packageName;
suffix = bundleSuffix;
if (bundlerClass != null && !Inner.DISABLED_PACKAGERS.contains(getName())) {
supported = isBundlerSupported(bundlerClass);
} else {
supported = false;
}
type = Objects.requireNonNull(packageName);
suffix = Objects.requireNonNull(bundleSuffix);
supported = Optional.ofNullable(bundlerClass).map(PackageType::isBundlerSupported).orElse(false);
enabled = supported && !Inner.DISABLED_PACKAGERS.contains(getType());
if (suffix != null && supported) {
TKit.trace(String.format("Bundler %s supported", getName()));
if (suffix != null && enabled) {
TKit.trace(String.format("Bundler %s enabled", getType()));
}
}
@ -69,30 +74,23 @@ public enum PackageType {
}
void applyTo(JPackageCommand cmd) {
cmd.setArgumentValue("--type", getName());
cmd.setArgumentValue("--type", getType());
}
String getSuffix() {
return suffix;
return Optional.ofNullable(suffix).orElseThrow(UnsupportedOperationException::new);
}
boolean isSupported() {
public boolean isSupported() {
return supported;
}
String getName() {
return name;
public boolean isEnabled() {
return supported;
}
static PackageType fromSuffix(String packageFilename) {
if (packageFilename != null) {
for (PackageType v : values()) {
if (packageFilename.endsWith(v.getSuffix())) {
return v;
}
}
}
return null;
public String getType() {
return type;
}
private static boolean isBundlerSupportedImpl(String bundlerClass) {
@ -133,8 +131,9 @@ public enum PackageType {
return reply.get();
}
private final String name;
private final String type;
private final String suffix;
private final boolean enabled;
private final boolean supported;
public static final Set<PackageType> LINUX = Set.of(LINUX_DEB, LINUX_RPM);

View File

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -119,12 +120,24 @@ final class TestInstance implements ThrowingRunnable {
return String.format("%s(length=%d)", asString, Array.getLength(v));
}
return String.format("%s", v);
}).collect(Collectors.joining(", "));
}).collect(Collectors.joining(", ")).transform(str -> {
final var sb = new StringBuilder();
for (var chr : str.toCharArray()) {
if (chr != ' ' && (Character.isWhitespace(chr) || Character.isISOControl(chr))) {
sb.append("\\u").append(ARGS_CHAR_FORMATTER.toHexDigits(chr));
} else {
sb.append(chr);
}
}
return sb.toString();
});
}
private List<Object> ctorArgs;
private List<Object> methodArgs;
private Method method;
private static final HexFormat ARGS_CHAR_FORMATTER = HexFormat.of().withUpperCase();
}
static TestDesc create(Method m, Object... args) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -107,7 +107,7 @@ public class WindowsHelper {
final Optional<Path> msiLogFile;
if (createMsiLog) {
msiLogFile = Optional.of(TKit.createTempFile(String.format("logs\\%s-msi.log",
cmd.packageType().getName())));
cmd.packageType().getType())));
} else {
msiLogFile = Optional.empty();
}
@ -175,13 +175,19 @@ public class WindowsHelper {
Path installationSubDirectory = getInstallationSubDirectory(cmd);
Path from = Path.of(extraPathComponent).resolve(installationSubDirectory);
Path to = installationSubDirectory;
TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to,
unpackDir));
ThrowingRunnable.toRunnable(() -> {
Files.createDirectories(unpackDir.resolve(to).getParent());
Files.move(unpackDir.resolve(from), unpackDir.resolve(to));
TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent));
}).run();
// Files.move() occasionally results into java.nio.file.AccessDeniedException
Executor.tryRunMultipleTimes(ThrowingRunnable.toRunnable(() -> {
TKit.trace(String.format("Convert [%s] into [%s] in [%s] directory", from, to, unpackDir));
final var dstDir = unpackDir.resolve(to);
TKit.deleteDirectoryRecursive(dstDir);
Files.move(unpackDir.resolve(from), dstDir);
TKit.deleteDirectoryRecursive(unpackDir.resolve(extraPathComponent));
}), 3, 5);
}
}
return destinationDir;
@ -656,7 +662,7 @@ public class WindowsHelper {
private final RegValuePath reg;
private final Optional<SpecialFolderDotNet> alt;
private final static Map<SpecialFolder, Path> CACHE = new ConcurrentHashMap<>();
private static final Map<SpecialFolder, Path> CACHE = new ConcurrentHashMap<>();
}
private static final class ShortPathUtils {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -22,8 +22,10 @@
*/
package jdk.jpackage.internal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -112,28 +114,57 @@ public class DottedVersionTest {
return data;
}
@ParameterizedTest
@MethodSource
public void testInvalid(String str) {
assertThrowsExactly(IllegalArgumentException.class, () -> new DottedVersion(str));
record InvalidVersionTestSpec(String version, String invalidComponent) {
public InvalidVersionTestSpec {
Objects.requireNonNull(version);
Objects.requireNonNull(invalidComponent);
}
InvalidVersionTestSpec(String version) {
this(version, "");
}
void run() {
final String expectedErrorMsg;
if (invalidComponent.isEmpty()) {
expectedErrorMsg = MessageFormat.format(I18N.getString("error.version-string-zero-length-component"), version);
} else {
expectedErrorMsg = MessageFormat.format(I18N.getString("error.version-string-invalid-component"), version, invalidComponent);
}
final var ex = assertThrowsExactly(IllegalArgumentException.class, () -> new DottedVersion(version));
assertEquals(expectedErrorMsg, ex.getMessage());
}
}
private static Stream<String> testInvalid() {
@ParameterizedTest
@MethodSource
public void testInvalid(InvalidVersionTestSpec testSpec) {
testSpec.run();
}
private static Stream<InvalidVersionTestSpec> testInvalid() {
return Stream.of(
"1.-1",
"5.",
"4.2.",
"3..2",
"2.a",
"0a",
".",
" ",
" 1",
"1. 2",
"+1",
"-1",
"-0",
"+0"
new InvalidVersionTestSpec("1.-1", "-1"),
new InvalidVersionTestSpec("5."),
new InvalidVersionTestSpec("4.2."),
new InvalidVersionTestSpec("3..2", ".2"),
new InvalidVersionTestSpec("3...2", "..2"),
new InvalidVersionTestSpec("2.a", "a"),
new InvalidVersionTestSpec("0a", "a"),
new InvalidVersionTestSpec("1.0a", "0a"),
new InvalidVersionTestSpec(".", "."),
new InvalidVersionTestSpec("..", ".."),
new InvalidVersionTestSpec(".a.b", ".a.b"),
new InvalidVersionTestSpec(".1.2", ".1.2"),
new InvalidVersionTestSpec(" ", " "),
new InvalidVersionTestSpec(" 1", " 1"),
new InvalidVersionTestSpec("1. 2", " 2"),
new InvalidVersionTestSpec("+1", "+1"),
new InvalidVersionTestSpec("-1", "-1"),
new InvalidVersionTestSpec("-0", "-0"),
new InvalidVersionTestSpec("+0", "+0")
);
}
@ -145,7 +176,8 @@ public class DottedVersionTest {
@Test
public void testEmptyGreedy() {
assertThrowsExactly(IllegalArgumentException.class, () -> DottedVersion.greedy(""), "Version may not be empty string");
final var ex = assertThrowsExactly(IllegalArgumentException.class, () -> DottedVersion.greedy(""));
assertEquals(I18N.getString("error.version-string-empty"), ex.getMessage());
}
@Test

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2025, 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.internal.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
public class TokenReplaceTest {
public record TestSpec(String str, Optional<String> expectedStr, Optional<Exception> expectedCtorException,
Optional<Exception> expectedApplyToException, Map<String, String> tokenWithValues, boolean recursive) {
public TestSpec {
Objects.requireNonNull(expectedStr);
Objects.requireNonNull(expectedCtorException);
Objects.requireNonNull(expectedApplyToException);
Objects.requireNonNull(tokenWithValues);
tokenWithValues.values().forEach(Objects::requireNonNull);
if (expectedStr.isPresent()) {
if (!(expectedCtorException.isEmpty() && expectedApplyToException.isEmpty())) {
throw new IllegalArgumentException();
}
} else if (expectedCtorException.isEmpty() == expectedApplyToException.isEmpty()) {
throw new IllegalArgumentException();
}
}
static final class Builder {
Builder str(String v) {
str = v;
return this;
}
Builder recursive(boolean v) {
recursive = v;
return this;
}
Builder recursive() {
return recursive(true);
}
Builder expect(String v) {
expectedStr = v;
return this;
}
Builder expectCtorThrow(String v) {
expectedCtorException = new IllegalArgumentException(v);
return this;
}
Builder expectApplyToNPE() {
expectedApplyToException = new NullPointerException();
return this;
}
Builder expectInfiniteRecursion() {
expectedApplyToException = new IllegalStateException("Infinite recursion");
return this;
}
Builder token(String token, String value) {
tokenWithValues.put(token, value);
return this;
}
TestSpec create() {
return new TestSpec(str, expectedStr(), Optional.ofNullable(expectedCtorException),
Optional.ofNullable(expectedApplyToException), tokenWithValues, recursive);
}
private Optional<String> expectedStr() {
if (expectedCtorException == null && expectedApplyToException == null) {
return Optional.ofNullable(expectedStr).or(() -> Optional.of(str));
} else {
return Optional.empty();
}
}
private boolean recursive;
private String str;
private String expectedStr;
private Exception expectedCtorException;
private Exception expectedApplyToException;
private final Map<String, String> tokenWithValues = new HashMap<>();
}
void test() {
final var tokens = tokenWithValues.keySet().toArray(String[]::new);
expectedStr.ifPresent(expected -> {
final var tokenReplace = new TokenReplace(tokens);
final String actual;
if (recursive) {
actual = tokenReplace.recursiveApplyTo(str, tokenWithValues::get);
} else {
actual = tokenReplace.applyTo(str, tokenWithValues::get);
}
assertEquals(expected, actual);
});
expectedCtorException.ifPresent(expected -> {
final var ex = assertThrows(expected.getClass(), () -> {
new TokenReplace(tokens);
});
assertEquals(expected.getMessage(), ex.getMessage());
});
expectedApplyToException.ifPresent(expected -> {
final var tokenReplace = new TokenReplace(tokens);
final var ex = assertThrows(expected.getClass(), () -> {
if (recursive) {
tokenReplace.recursiveApplyTo(str, tokenWithValues::get);
} else {
tokenReplace.applyTo(str, tokenWithValues::get);
}
});
assertEquals(expected.getMessage(), ex.getMessage());
});
}
}
@ParameterizedTest
@MethodSource
public void test(TestSpec spec) {
spec.test();
}
public static Stream<TestSpec> test() {
return Stream.of(
testSpec("foo").token("", "B").expectCtorThrow("Empty token in the list of tokens"),
testSpec("foo").expectCtorThrow("Empty token list"),
testSpec("a").expect("a").token("b", "B"),
testSpec("a").expect("A").token("a", "A"),
testSpec("aaa").expect("AAA").token("a", "A"),
testSpec("aaa").recursive().expect("{B}{B}{B}").token("a", "b").token("b", "{B}"),
testSpec("aaa").token("a", "aa").token("aa", "C").expect("Caa"),
testSpec("aaa").token("a", "aa").token("aa", "C").expect("CC").recursive(),
testSpec("aaa").expect("A2A").token("a", "A").token("aa", "A2"),
testSpec("aaa").token("a", "b").token("b", "c").token("c", "a").expect("bbb"),
testSpec("aaa").token("a", "b").token("b", "").recursive().expect(""),
testSpec("aaa").token("a", "").recursive().expect(""),
testSpec("aaa").token("a", "b").token("b", "c").token("c", "a").expectInfiniteRecursion().recursive(),
testSpec(null).token("a", "b").expectApplyToNPE(),
testSpec("abc").expect("abc").token(".", "A"),
testSpec("abc.").expect("abcD").token(".", "D")
).map(TestSpec.Builder::create);
}
private static final class CountingSupplier implements Supplier<Object> {
CountingSupplier(Object value, int expectedCount) {
this.value = value;
this.expectedCount = expectedCount;
}
@Override
public Object get() {
counter++;
return value;
}
public Object value() {
return value;
}
void verifyCount() {
assertEquals(expectedCount, counter);
}
private final Object value;
private int counter;
private final int expectedCount;
}
@Test
public void testCombine() {
final var x = new TokenReplace("a");
final var y = new TokenReplace("aa");
final var xy = TokenReplace.combine(x, y);
assertEquals(xy, new TokenReplace("aa", "a"));
assertEquals(xy, new TokenReplace("a", "aa"));
}
@Test
public void testCombine2() {
final var x = new TokenReplace("a");
final var y = new TokenReplace("a");
final var xy = TokenReplace.combine(x, y);
assertEquals(xy, new TokenReplace("a", "a"));
assertEquals(xy, new TokenReplace("a"));
assertEquals(xy, x);
assertEquals(xy, y);
}
@Test
public void testCombine3() {
final var x = new TokenReplace("a");
final var y = new TokenReplace("b");
final var xy = TokenReplace.combine(x, y);
assertEquals(xy, new TokenReplace("a", "b"));
assertEquals(xy, new TokenReplace("b", "a"));
}
@Test
public void testEquals() {
final var x = new TokenReplace("x");
final var y = new TokenReplace("y");
final var y2 = new TokenReplace("y");
assertNotEquals(x, y);
assertNotEquals(x, null);
assertNotEquals(null, x);
assertNotEquals(x, "x");
assertEquals(y, y2);
assertEquals(y, y);
}
@Test
public void testCreateCachingTokenValueSupplier() {
final var neverCalledSupplier = new CountingSupplier("", 0);
final var calledOnceSupplier = new CountingSupplier("foo", 1);
final var calledOnceNullSupplier = new CountingSupplier(null, 1);
final var supplier = TokenReplace.createCachingTokenValueSupplier(Map.of(
"never", neverCalledSupplier,
"once", calledOnceSupplier,
"onceNull", calledOnceNullSupplier
));
for (int i = 0; i != 2; i++) {
assertEquals(calledOnceSupplier.value(), supplier.apply("once"));
final var ex = assertThrows(NullPointerException.class, () -> supplier.apply("onceNull"));
assertEquals("Null value for token [onceNull]", ex.getMessage());
}
final var ex = assertThrows(NullPointerException.class, () -> supplier.apply("foo"));
assertEquals("No token value supplier for token [foo]", ex.getMessage());
neverCalledSupplier.verifyCount();
calledOnceSupplier.verifyCount();
calledOnceNullSupplier.verifyCount();
}
private static TestSpec.Builder testSpec(String str) {
return new TestSpec.Builder().str(str);
}
}

View File

@ -21,14 +21,19 @@
* questions.
*/
import static jdk.jpackage.test.JPackageStringBundle.MAIN;
import java.io.IOException;
import java.nio.file.Path;
import jdk.jpackage.test.TKit;
import java.util.List;
import java.util.Objects;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.LinuxHelper;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.LinuxHelper;
import jdk.jpackage.test.Annotations.Test;
import java.util.List;
import jdk.jpackage.test.RunnablePackageTest.Action;
import jdk.jpackage.test.TKit;
/*
* @test
@ -55,48 +60,47 @@ public class LinuxResourceTest {
})
.forTypes(PackageType.LINUX_DEB)
.addInitializer(cmd -> {
Path controlFile = Path.of(cmd.getArgumentValue("--resource-dir"),
"control");
Path controlFile = Path.of(cmd.getArgumentValue("--resource-dir"), "control");
final var packageProp = property("Package", "dont-install-me");
final var verProp = property("Version", "1.2.3-R2");
final var arhProp = property("Architecture", "bar");
TKit.createTextFile(controlFile, List.of(
"Package: dont-install-me",
"Version: 1.2.3-R2",
packageProp.format(),
verProp.format(),
"Section: APPLICATION_SECTION",
"Maintainer: APPLICATION_MAINTAINER",
"Priority: optional",
"Architecture: bar",
arhProp.format(),
"Provides: dont-install-me",
"Description: APPLICATION_DESCRIPTION",
"Installed-Size: APPLICATION_INSTALLED_SIZE",
"Depends: PACKAGE_DEFAULT_DEPENDENCIES"
));
})
.addBundleVerifier((cmd, result) -> {
TKit.assertTextStream("Using custom package resource [DEB control file]")
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(String.format(
"Expected value of \"Package\" property is [%s]. Actual value in output package is [dont-install-me]",
LinuxHelper.getPackageName(cmd)))
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(
"Expected value of \"Version\" property is [1.0]. Actual value in output package is [1.2.3-R2]")
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(String.format(
"Expected value of \"Architecture\" property is [%s]. Actual value in output package is [bar]",
LinuxHelper.getDefaultPackageArch(cmd.packageType())))
.predicate(String::contains)
.apply(result.getOutput().stream());
cmd.validateOutput(MAIN.cannedFormattedString(
"message.using-custom-resource",
String.format("[%s]", MAIN.cannedFormattedString("resource.deb-control-file").getValue()),
controlFile.getFileName()));
packageProp.expectedValue(LinuxHelper.getPackageName(cmd)).token("APPLICATION_PACKAGE").resourceDirFile(controlFile).validateOutput(cmd);
verProp.expectedValue(cmd.version()).token("APPLICATION_VERSION_WITH_RELEASE").resourceDirFile(controlFile).validateOutput(cmd);
arhProp.expectedValue(LinuxHelper.getDefaultPackageArch(cmd.packageType())).token("APPLICATION_ARCH").resourceDirFile(controlFile).validateOutput(cmd);
})
.forTypes(PackageType.LINUX_RPM)
.addInitializer(cmd -> {
Path specFile = Path.of(cmd.getArgumentValue("--resource-dir"),
LinuxHelper.getPackageName(cmd) + ".spec");
final var packageProp = property("Name", "dont-install-me");
final var verProp = property("Version", "1.2.3");
final var releaseProp = property("Release", "R2");
TKit.createTextFile(specFile, List.of(
"Name: dont-install-me",
"Version: 1.2.3",
"Release: R2",
packageProp.format(),
verProp.format(),
releaseProp.format(),
"Summary: APPLICATION_SUMMARY",
"License: APPLICATION_LICENSE_TYPE",
"Prefix: %{dirname:APPLICATION_DIRECTORY}",
@ -113,25 +117,77 @@ public class LinuxResourceTest {
"%files",
"APPLICATION_DIRECTORY"
));
cmd.validateOutput(MAIN.cannedFormattedString(
"message.using-custom-resource",
String.format("[%s]", MAIN.cannedFormattedString("resource.rpm-spec-file").getValue()),
specFile.getFileName()));
packageProp.expectedValue(LinuxHelper.getPackageName(cmd)).token("APPLICATION_PACKAGE").resourceDirFile(specFile).validateOutput(cmd);
verProp.expectedValue(cmd.version()).token("APPLICATION_VERSION").resourceDirFile(specFile).validateOutput(cmd);
releaseProp.expectedValue("1").token("APPLICATION_RELEASE").resourceDirFile(specFile).validateOutput(cmd);
})
.addBundleVerifier((cmd, result) -> {
TKit.assertTextStream("Using custom package resource [RPM spec file]")
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(String.format(
"Expected value of \"Name\" property is [%s]. Actual value in output package is [dont-install-me]",
LinuxHelper.getPackageName(cmd)))
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(
"Expected value of \"Version\" property is [1.0]. Actual value in output package is [1.2.3]")
.predicate(String::contains)
.apply(result.getOutput().stream());
TKit.assertTextStream(
"Expected value of \"Release\" property is [1]. Actual value in output package is [R2]")
.predicate(String::contains)
.apply(result.getOutput().stream());
})
.run();
.run(Action.CREATE);
}
private static final class PropertyValidator {
PropertyValidator name(String v) {
name = v;
return this;
}
PropertyValidator customValue(String v) {
customValue = v;
return this;
}
PropertyValidator expectedValue(String v) {
expectedValue = v;
return this;
}
PropertyValidator token(String v) {
token = v;
return this;
}
PropertyValidator resourceDirFile(Path v) {
resourceDirFile = v;
return this;
}
String format() {
Objects.requireNonNull(name);
Objects.requireNonNull(customValue);
return String.format("%s: %s", name, customValue);
}
void validateOutput(JPackageCommand cmd) {
Objects.requireNonNull(name);
Objects.requireNonNull(customValue);
Objects.requireNonNull(expectedValue);
Objects.requireNonNull(token);
Objects.requireNonNull(resourceDirFile);
final var customResourcePath = customResourcePath();
cmd.validateOutput(
MAIN.cannedFormattedString("error.unexpected-package-property", name, expectedValue, customValue, customResourcePath),
MAIN.cannedFormattedString("error.unexpected-package-property.advice", token, customValue, name, customResourcePath));
}
private Path customResourcePath() {
return resourceDirFile.getFileName();
}
private String name;
private String customValue;
private String expectedValue;
private String token;
private Path resourceDirFile;
}
private static PropertyValidator property(String name, String customValue) {
return new PropertyValidator().name(name).customValue(customValue);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, 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
@ -25,9 +25,7 @@ import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.Annotations.Test;
/**
* Tests generation of app image with --mac-app-store and --jlink-options. jpackage should able
* to generate app image if "--strip-native-commands" is specified for --jlink-options and should
* fail if it is not specified.
* Tests generation of app image with --mac-app-store and --jlink-options.
*/
/*
@ -50,13 +48,4 @@ public class MacAppStoreJLinkOptionsTest {
cmd.executeAndAssertHelloAppImageCreated();
}
@Test
public static void testWithoutStripNativeCommands() throws Exception {
JPackageCommand cmd = JPackageCommand.helloAppImage();
cmd.addArguments("--mac-app-store", "--jlink-options",
"--strip-debug --no-man-pages --no-header-files");
cmd.execute(1);
}
}

View File

@ -1,100 +0,0 @@
/*
* Copyright (c) 2022, 2024, 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.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JavaTool;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
/**
* Tests generation of app image with --mac-app-store and --runtime-image. jpackage should able
* to generate app image if runtime image does not have "bin" folder and fail otherwise.
*/
/*
* @test
* @summary jpackage with --mac-app-store and --runtime-image
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @build MacAppStoreRuntimeTest
* @requires (os.family == "mac")
* @run main/othervm -Xmx512m jdk.jpackage.test.Main
* --jpt-run=MacAppStoreRuntimeTest
*/
public class MacAppStoreRuntimeTest {
private static String getRuntimeImage(boolean stripNativeCommands) throws IOException {
final Path workDir = TKit.createTempDirectory("runtime").resolve("data");
final Path jlinkOutputDir = workDir.resolve("temp.runtime");
Files.createDirectories(jlinkOutputDir.getParent());
// List of modules required for test app.
final var modules = new String[] {
"java.base",
"java.desktop"
};
List<String> jlinkArgs = new ArrayList<>();
jlinkArgs.add("--output");
jlinkArgs.add(jlinkOutputDir.toString());
jlinkArgs.add("--add-modules");
jlinkArgs.add(String.join(",", modules));
jlinkArgs.add("--strip-debug");
jlinkArgs.add("--no-header-files");
jlinkArgs.add("--no-man-pages");
if (stripNativeCommands) {
jlinkArgs.add("--strip-native-commands");
}
new Executor()
.setToolProvider(JavaTool.JLINK)
.dumpOutput()
.addArguments(jlinkArgs)
.execute();
return jlinkOutputDir.toString();
}
@Test
@Parameter("true")
@Parameter("false")
public static void test(boolean stripNativeCommands) throws Exception {
JPackageCommand cmd = JPackageCommand.helloAppImage();
cmd.addArguments("--mac-app-store", "--runtime-image", getRuntimeImage(stripNativeCommands));
if (stripNativeCommands) {
cmd.executeAndAssertHelloAppImageCreated();
} else {
cmd.execute(1);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -62,17 +62,15 @@ public class SigningAppImageTest {
@Test
// ({"sign or not", "signing-key or sign-identity", "certificate index"})
// Sign, signing-key and ASCII certificate
@Parameter({"true", "true", SigningBase.ASCII_INDEX})
@Parameter({"true", "true", "ASCII_INDEX"})
// Sign, signing-key and UNICODE certificate
@Parameter({"true", "true", SigningBase.UNICODE_INDEX})
@Parameter({"true", "true", "UNICODE_INDEX"})
// Sign, signing-indentity and UNICODE certificate
@Parameter({"true", "false", SigningBase.UNICODE_INDEX})
@Parameter({"true", "false", "UNICODE_INDEX"})
// Unsigned
@Parameter({"false", "true", "-1"})
public void test(String... testArgs) throws Exception {
boolean doSign = Boolean.parseBoolean(testArgs[0]);
boolean signingKey = Boolean.parseBoolean(testArgs[1]);
int certIndex = Integer.parseInt(testArgs[2]);
@Parameter({"false", "true", "INVALID_INDEX"})
public void test(boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception {
final var certIndex = certEnum.value();
SigningCheck.checkCertificates(certIndex);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -110,27 +110,24 @@ public class SigningPackageTest {
return SigningBase.getDevNameIndex(devName);
} else {
// Signing-indentity
return Integer.valueOf(SigningBase.UNICODE_INDEX);
return SigningBase.CertIndex.UNICODE_INDEX.value();
}
}
@Test
// ("signing-key or sign-identity", "sign app-image", "sign pkg", "certificate index"})
// Signing-key and ASCII certificate
@Parameter({"true", "true", "true", SigningBase.ASCII_INDEX})
@Parameter({"true", "true", "true", "ASCII_INDEX"})
// Signing-key and UNICODE certificate
@Parameter({"true", "true", "true", SigningBase.UNICODE_INDEX})
@Parameter({"true", "true", "true", "UNICODE_INDEX"})
// Signing-indentity and UNICODE certificate
@Parameter({"false", "true", "true", SigningBase.UNICODE_INDEX})
@Parameter({"false", "true", "true", "UNICODE_INDEX"})
// Signing-indentity, but sign app-image only and UNICODE certificate
@Parameter({"false", "true", "false", SigningBase.UNICODE_INDEX})
@Parameter({"false", "true", "false", "UNICODE_INDEX"})
// Signing-indentity, but sign pkg only and UNICODE certificate
@Parameter({"false", "false", "true", SigningBase.UNICODE_INDEX})
public static void test(String... testArgs) throws Exception {
boolean signingKey = Boolean.parseBoolean(testArgs[0]);
boolean signAppImage = Boolean.parseBoolean(testArgs[1]);
boolean signPKG = Boolean.parseBoolean(testArgs[2]);
int certIndex = Integer.parseInt(testArgs[3]);
@Parameter({"false", "false", "true", "UNICODE_INDEX"})
public static void test(boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception {
final var certIndex = certEnum.value();
SigningCheck.checkCertificates(certIndex);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2025, 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
@ -32,9 +32,23 @@ import jdk.jpackage.test.Executor.Result;
public class SigningBase {
enum CertIndex {
ASCII_INDEX(0),
UNICODE_INDEX(1),
INVALID_INDEX(-1);
CertIndex(int value) {
this.value = value;
}
int value() {
return value;
}
private final int value;
}
public static int DEFAULT_INDEX = 0;
public static final String ASCII_INDEX = "0";
public static final String UNICODE_INDEX = "0";
private static String [] DEV_NAMES = {
"jpackage.openjdk.java.net",
"jpackage.openjdk.java.net (ö)",

View File

@ -25,10 +25,13 @@ import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;
import java.util.List;
import jdk.jpackage.internal.util.XmlUtils;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.RunnablePackageTest.Action;
import jdk.jpackage.test.Annotations.Test;
@ -106,17 +109,16 @@ public class AppImagePackageTest {
public static void testBadAppImage() throws IOException {
Path appImageDir = TKit.createTempDirectory("appimage");
Files.createFile(appImageDir.resolve("foo"));
configureAppImageWithoutJPackageXMLFile(appImageDir).addInitializer(
cmd -> {
cmd.removeArgumentWithValue("--name");
}).run(Action.CREATE);
configureBadAppImage(appImageDir).addInitializer(cmd -> {
cmd.removeArgumentWithValue("--name");
}).run(Action.CREATE);
}
@Test
public static void testBadAppImage2() throws IOException {
Path appImageDir = TKit.createTempDirectory("appimage");
Files.createFile(appImageDir.resolve("foo"));
configureAppImageWithoutJPackageXMLFile(appImageDir).run(Action.CREATE);
configureBadAppImage(appImageDir).run(Action.CREATE);
}
@Test
@ -126,29 +128,45 @@ public class AppImagePackageTest {
JPackageCommand appImageCmd = JPackageCommand.helloAppImage().
setFakeRuntime().setArgumentValue("--dest", appImageDir);
configureAppImageWithoutJPackageXMLFile(appImageCmd.outputBundle()).
addRunOnceInitializer(() -> {
appImageCmd.execute();
Files.delete(AppImageFile.getPathInAppImage(appImageCmd.
outputBundle()));
}).run(Action.CREATE);
configureBadAppImage(appImageCmd.outputBundle()).addRunOnceInitializer(() -> {
appImageCmd.execute();
Files.delete(AppImageFile.getPathInAppImage(appImageCmd.outputBundle()));
}).run(Action.CREATE);
}
private static PackageTest configureAppImageWithoutJPackageXMLFile(
Path appImageDir) {
return new PackageTest()
.addInitializer(cmd -> {
cmd.saveConsoleOutput(true);
cmd.addArguments("--app-image", appImageDir);
cmd.removeArgumentWithValue("--input");
cmd.ignoreDefaultVerbose(true); // no "--verbose" option
})
.addBundleVerifier((cmd, result) -> {
TKit.assertTextStream(
"Error: Missing .jpackage.xml file in app-image dir").apply(
result.getOutput().stream());
})
.setExpectedExitCode(1);
@Test
public static void testBadAppImageFile() throws IOException {
final var appImageRoot = TKit.createTempDirectory("appimage");
final var appImageCmd = JPackageCommand.helloAppImage().
setFakeRuntime().setArgumentValue("--dest", appImageRoot);
final var appImageDir = appImageCmd.outputBundle();
final var expectedError = JPackageStringBundle.MAIN.cannedFormattedString(
"error.invalid-app-image", appImageDir, AppImageFile.getPathInAppImage(appImageDir));
configureBadAppImage(appImageDir, expectedError).addRunOnceInitializer(() -> {
appImageCmd.execute();
XmlUtils.createXml(AppImageFile.getPathInAppImage(appImageDir), xml -> {
xml.writeStartElement("jpackage-state");
xml.writeEndElement();
});
}).run(Action.CREATE);
}
private static PackageTest configureBadAppImage(Path appImageDir) {
return configureBadAppImage(appImageDir,
JPackageStringBundle.MAIN.cannedFormattedString("error.foreign-app-image", appImageDir));
}
private static PackageTest configureBadAppImage(Path appImageDir, CannedFormattedString expectedError) {
return new PackageTest().addInitializer(cmd -> {
cmd.addArguments("--app-image", appImageDir);
cmd.removeArgumentWithValue("--input");
cmd.ignoreDefaultVerbose(true); // no "--verbose" option
cmd.validateOutput(expectedError);
}).setExpectedExitCode(1);
}
private static Path iconPath(String name) {

View File

@ -31,7 +31,6 @@ import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.TKit;
/*
@ -70,19 +69,6 @@ public final class AppVersionTest {
"--app-version", "7.5.81"}}
}));
// These are invalid version strings.
// Don't need to test all invalid input as this is handled in
// PlatformVersionTest unit test
if (TKit.isWindows()) {
data.addAll(List.of(new Object[][]{
{null, "Hello", new String[]{"--app-version", "256"}}
}));
} else if (TKit.isOSX()) {
data.addAll(List.of(new Object[][]{
{null, "Hello", new String[]{"--app-version", "0.2"}}
}));
}
return data;
}
@ -95,17 +81,6 @@ public final class AppVersionTest {
@Test
public void test() throws XPathExpressionException, IOException {
if (expectedVersion == null) {
new PackageTest()
.setExpectedExitCode(1)
.configureHelloApp(javaAppDesc)
.addInitializer(cmd -> {
cmd.addArguments(jpackageArgs);
})
.run();
return;
}
JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);

View File

@ -36,6 +36,7 @@ import java.util.regex.Pattern;
import java.util.stream.Stream;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.HelloApp;
@ -381,7 +382,10 @@ public final class BasicTest {
);
if (TestTempType.TEMPDIR_NOT_EMPTY.equals(type)) {
pkgTest.setExpectedExitCode(1).addBundleVerifier(cmd -> {
pkgTest.setExpectedExitCode(1).addInitializer(cmd -> {
cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString(
"ERR_BuildRootInvalid", cmd.getArgumentValue("--temp")));
}).addBundleVerifier(cmd -> {
// Check jpackage didn't use the supplied directory.
Path tempDir = Path.of(cmd.getArgumentValue("--temp"));
TKit.assertDirectoryContent(tempDir).match(Path.of("foo.txt"));

View File

@ -22,19 +22,32 @@
*/
import static java.util.stream.Collectors.toMap;
import static jdk.internal.util.OperatingSystem.LINUX;
import static jdk.internal.util.OperatingSystem.MACOS;
import static jdk.internal.util.OperatingSystem.WINDOWS;
import static jdk.jpackage.test.CannedFormattedString.cannedAbsolutePath;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import jdk.jpackage.internal.util.TokenReplace;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.ParameterSupplier;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.RunnablePackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
import static jdk.internal.util.OperatingSystem.WINDOWS;
/*
* @test
@ -42,7 +55,7 @@ import static jdk.internal.util.OperatingSystem.WINDOWS;
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile -Xlint:all -Werror ErrorTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=ErrorTest
* --jpt-before-run=jdk.jpackage.test.JPackageCommand.useExecutableByDefault
*/
@ -53,101 +66,566 @@ import static jdk.internal.util.OperatingSystem.WINDOWS;
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile -Xlint:all -Werror ErrorTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=ErrorTest
* --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
*/
public final class ErrorTest {
public static Collection<?> input() {
return List.of(new Object[][]{
enum Token {
JAVA_HOME(cmd -> {
return System.getProperty("java.home");
}),
APP_IMAGE(cmd -> {
final var appImageRoot = TKit.createTempDirectory("appimage");
final var appImageCmd = JPackageCommand.helloAppImage()
.setFakeRuntime().setArgumentValue("--dest", appImageRoot);
appImageCmd.execute();
return appImageCmd.outputBundle().toString();
}),
ADD_LAUNCHER_PROPERTY_FILE;
private Token() {
this.valueSupplier = Optional.empty();
}
private Token(Function<JPackageCommand, Object> valueSupplier) {
this.valueSupplier = Optional.of(valueSupplier);
}
String token() {
return makeToken(name());
}
TokenReplace asTokenReplace() {
return tokenReplace;
}
Optional<Object> expand(JPackageCommand cmd) {
return valueSupplier.map(func -> func.apply(cmd));
}
private static String makeToken(String v) {
Objects.requireNonNull(v);
return String.format("@@%s@@", v);
}
private final Optional<Function<JPackageCommand, Object>> valueSupplier;
private final TokenReplace tokenReplace = new TokenReplace(token());
}
public record TestSpec(Optional<PackageType> type, Optional<String> appDesc, List<String> addArgs,
List<String> removeArgs, List<CannedFormattedString> expectedErrors) {
static final class Builder {
Builder type(PackageType v) {
type = v;
return this;
}
Builder notype() {
return type(null);
}
Builder nativeType() {
return type(NATIVE_TYPE);
}
Builder appDesc(String v) {
appDesc = v;
return this;
}
Builder noAppDesc() {
return appDesc(null);
}
Builder setAddArgs(List<String> v) {
addArgs.clear();
addArgs.addAll(v);
return this;
}
Builder setAddArgs(String... v) {
return setAddArgs(List.of(v));
}
Builder addArgs(List<String> v) {
addArgs.addAll(v);
return this;
}
Builder addArgs(String... v) {
return addArgs(List.of(v));
}
Builder setRemoveArgs(List<String> v) {
removeArgs.clear();
removeArgs.addAll(v);
return this;
}
Builder setRemoveArgs(String... v) {
return setRemoveArgs(List.of(v));
}
Builder removeArgs(List<String> v) {
removeArgs.addAll(v);
return this;
}
Builder removeArgs(String... v) {
return removeArgs(List.of(v));
}
Builder setErrors(List<CannedFormattedString> v) {
expectedErrors = v;
return this;
}
Builder setErrors(CannedFormattedString... v) {
return setErrors(List.of(v));
}
Builder errors(List<CannedFormattedString> v) {
expectedErrors.addAll(v);
return this;
}
Builder errors(CannedFormattedString... v) {
return errors(List.of(v));
}
Builder error(String key, Object ... args) {
return errors(JPackageStringBundle.MAIN.cannedFormattedString(key, args));
}
Builder invalidTypeArg(String arg, String... otherArgs) {
return addArgs(arg).addArgs(otherArgs).error("ERR_InvalidTypeOption", arg, type.getType());
}
Builder unsupportedPlatformOption(String arg, String ... otherArgs) {
return addArgs(arg).addArgs(otherArgs).error("ERR_UnsupportedOption", arg);
}
TestSpec create() {
return new TestSpec(Optional.ofNullable(type), Optional.ofNullable(appDesc),
List.copyOf(addArgs), List.copyOf(removeArgs), List.copyOf(expectedErrors));
}
private PackageType type = PackageType.IMAGE;
private String appDesc = DEFAULT_APP_DESC;
private List<String> addArgs = new ArrayList<>();
private List<String> removeArgs = new ArrayList<>();
private List<CannedFormattedString> expectedErrors = new ArrayList<>();
}
public TestSpec {
Objects.requireNonNull(type);
Objects.requireNonNull(appDesc);
Objects.requireNonNull(addArgs);
addArgs.forEach(Objects::requireNonNull);
Objects.requireNonNull(removeArgs);
removeArgs.forEach(Objects::requireNonNull);
if (expectedErrors.isEmpty()) {
throw new IllegalArgumentException("The list of expected errors must be non-empty");
}
}
void test() {
test(Map.of());
}
void test(Map<Token, Function<JPackageCommand, Object>> tokenValueSuppliers) {
final var cmd = appDesc.map(JPackageCommand::helloAppImage).orElseGet(JPackageCommand::new);
type.ifPresent(cmd::setPackageType);
removeArgs.forEach(cmd::removeArgumentWithValue);
cmd.addArguments(addArgs);
final var tokenValueSupplier = TokenReplace.createCachingTokenValueSupplier(Stream.of(Token.values()).collect(toMap(Token::token, token -> {
return () -> {
return token.expand(cmd).orElseGet(() -> {
final var tvs = Objects.requireNonNull(tokenValueSuppliers.get(token), () -> {
return String.format("No token value supplier for token [%s]", token);
});
return tvs.apply(cmd);
});
};
})));
for (final var token : Token.values()) {
final var newArgs = cmd.getAllArguments().stream().map(arg -> {
return token.asTokenReplace().applyTo(arg, tokenValueSupplier);
}).toList();
cmd.clearArguments().addArguments(newArgs);
}
defaultInit(cmd, expectedErrors);
cmd.execute(1);
}
@Override
public final String toString() {
final var sb = new StringBuilder();
type.ifPresent(v -> {
sb.append(v).append("; ");
});
appDesc.ifPresent(v -> {
sb.append("app-desc=").append(v).append("; ");
});
if (!addArgs.isEmpty()) {
sb.append("args-add=").append(addArgs).append("; ");
}
if (!removeArgs.isEmpty()) {
sb.append("args-del=").append(removeArgs).append("; ");
}
sb.append("errors=").append(expectedErrors);
return sb.toString();
}
private static final String DEFAULT_APP_DESC = "Hello";
}
private static TestSpec.Builder testSpec() {
return new TestSpec.Builder();
}
public static Collection<Object[]> basic() {
final List<TestSpec> testCases = new ArrayList<>();
testCases.addAll(Stream.of(
// non-existent arg
{"Hello",
new String[]{"--no-such-argument"},
null,
JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidOption", "--no-such-argument")},
testSpec().addArgs("--no-such-argument")
.error("ERR_InvalidOption", "--no-such-argument"),
// no main jar
{"Hello",
null,
new String[]{"--main-jar"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_NoEntryPoint")},
testSpec().removeArgs("--main-jar").error("ERR_NoEntryPoint"),
// no main-class
{"Hello",
null,
new String[]{"--main-class"},
JPackageStringBundle.MAIN.cannedFormattedString("error.no-main-class-with-main-jar", "hello.jar"),
JPackageStringBundle.MAIN.cannedFormattedString("error.no-main-class-with-main-jar.advice", "hello.jar")},
testSpec().removeArgs("--main-class")
.error("error.no-main-class-with-main-jar", "hello.jar")
.error("error.no-main-class-with-main-jar.advice", "hello.jar"),
// non-existent main jar
{"Hello",
new String[]{"--main-jar", "non-existent.jar"},
null,
JPackageStringBundle.MAIN.cannedFormattedString("error.main-jar-does-not-exist", "non-existent.jar")},
testSpec().addArgs("--main-jar", "non-existent.jar")
.error("error.main-jar-does-not-exist", "non-existent.jar"),
// non-existent runtime
{"Hello",
new String[]{"--runtime-image", "non-existent.runtime"},
null,
JPackageStringBundle.MAIN.cannedFormattedString("message.runtime-image-dir-does-not-exist", "runtime-image", "non-existent.runtime")},
testSpec().addArgs("--runtime-image", "non-existent.runtime")
.error("message.runtime-image-dir-does-not-exist", "runtime-image", "non-existent.runtime"),
// non-existent app image
testSpec().noAppDesc().nativeType().addArgs("--name", "foo", "--app-image", "non-existent.appimage")
.error("ERR_AppImageNotExist", "non-existent.appimage"),
// non-existent resource-dir
{"Hello",
new String[]{"--resource-dir", "non-existent.dir"},
null,
JPackageStringBundle.MAIN.cannedFormattedString("message.resource-dir-does-not-exist", "resource-dir", "non-existent.dir")},
testSpec().addArgs("--resource-dir", "non-existent.dir")
.error("message.resource-dir-does-not-exist", "resource-dir", "non-existent.dir"),
// non-existent icon
testSpec().addArgs("--icon", "non-existent.icon")
.error("ERR_IconFileNotExit", cannedAbsolutePath("non-existent.icon")),
// non-existent license file
testSpec().nativeType().addArgs("--license-file", "non-existent.license")
.error("ERR_LicenseFileNotExit"),
// invalid type
{"Hello",
new String[]{"--type", "invalid-type"},
null,
JPackageStringBundle.MAIN.cannedFormattedString("ERR_InvalidInstallerType", "invalid-type")},
// no --input
{"Hello",
null,
new String[]{"--input"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_MissingArgument", "--input")},
testSpec().addArgs("--type", "invalid-type")
.error("ERR_InvalidInstallerType", "invalid-type"),
// no --input for non-mudular app
testSpec().removeArgs("--input").error("error.no-input-parameter"),
// no --module-path
{"com.other/com.other.Hello",
null,
new String[]{"--module-path"},
JPackageStringBundle.MAIN.cannedFormattedString("ERR_MissingArgument", "--runtime-image or --module-path")},
});
testSpec().appDesc("com.other/com.other.Hello").removeArgs("--module-path")
.error("ERR_MissingArgument", "--runtime-image or --module-path"),
// no main class in module path
testSpec().noAppDesc().addArgs("--module", "java.base", "--runtime-image", Token.JAVA_HOME.token())
.error("ERR_NoMainClass"),
// no module in module path
testSpec().noAppDesc().addArgs("--module", "com.foo.bar", "--runtime-image", Token.JAVA_HOME.token())
.error("error.no-module-in-path", "com.foo.bar"),
// --main-jar and --module-name
testSpec().noAppDesc().addArgs("--main-jar", "foo.jar", "--module", "foo.bar")
.error("ERR_BothMainJarAndModule"),
// non-existing argument file
testSpec().noAppDesc().notype().addArgs("@foo")
.error("ERR_CannotParseOptions", "foo"),
// invalid jlink option
testSpec().addArgs("--jlink-options", "--foo")
.error("error.jlink.failed", "Error: unknown option: --foo")
).map(TestSpec.Builder::create).toList());
// forbidden jlink options
testCases.addAll(Stream.of("--output", "--add-modules", "--module-path").map(opt -> {
return testSpec().addArgs("--jlink-options", opt).error("error.blocked.option", opt);
}).map(TestSpec.Builder::create).toList());
// --runtime-image and --app-image are mutually-exclusive
testCases.addAll(createRuntimeMutuallyExclusive("--app-image", "app-image"));
// --runtime-image and --app-modules are mutually-exclusive
testCases.addAll(createRuntimeMutuallyExclusive("--add-modules", "foo.bar", "--module", "foo.bar"));
// --runtime-image and --jlink-options are mutually-exclusive
testCases.addAll(createRuntimeMutuallyExclusive("--jlink-options", "--bind-services", "--module", "foo.bar"));
return toTestArgs(testCases.stream());
}
record ArgumentGroup(String arg, String... otherArgs) {
ArgumentGroup {
Objects.requireNonNull(arg);
List.of(otherArgs).forEach(Objects::requireNonNull);
}
String[] asArray() {
return Stream.concat(Stream.of(arg), Stream.of(otherArgs)).toArray(String[]::new);
}
}
private static List<TestSpec> createRuntimeMutuallyExclusive(String arg, String... otherArgs) {
return createMutuallyExclusive(
new ArgumentGroup("--runtime-image", Token.JAVA_HOME.token()),
new ArgumentGroup(arg, otherArgs)
).map(TestSpec.Builder::noAppDesc).map(TestSpec.Builder::nativeType).map(TestSpec.Builder::create).toList();
}
private static Stream<TestSpec.Builder> createMutuallyExclusive(ArgumentGroup firstGroup, ArgumentGroup secondGroup) {
final Supplier<TestSpec.Builder> createBuilder = () -> {
return testSpec().error("ERR_MutuallyExclusiveOptions", firstGroup.arg(), secondGroup.arg());
};
return Stream.of(
createBuilder.get().addArgs(firstGroup.asArray()).addArgs(secondGroup.asArray()),
createBuilder.get().addArgs(secondGroup.asArray()).addArgs(firstGroup.asArray()));
}
public static Collection<Object[]> invalidAppVersion() {
return fromTestSpecBuilders(Stream.of(
// Invalid app version. Just cover all different error messages.
// Extensive testing of invalid version strings is done in DottedVersionTest unit test.
testSpec().addArgs("--app-version", "").error("error.version-string-empty"),
testSpec().addArgs("--app-version", "1.").error("error.version-string-zero-length-component", "1."),
testSpec().addArgs("--app-version", "1.b.3").error("error.version-string-invalid-component", "1.b.3", "b.3")
));
}
@Test
@ParameterSupplier("input")
public static void test(String javaAppDesc, String[] jpackageArgs,
String[] removeArgs, CannedFormattedString... expectedErrors) {
// Init default jpackage test command line.
var cmd = JPackageCommand.helloAppImage(javaAppDesc);
defaultInit(cmd, expectedErrors);
// Add arguments if requested.
Optional.ofNullable(jpackageArgs).ifPresent(cmd::addArguments);
// Remove arguments if requested.
Optional.ofNullable(removeArgs).map(List::of).ifPresent(
args -> args.forEach(cmd::removeArgumentWithValue));
cmd.execute(1);
@ParameterSupplier("basic")
@ParameterSupplier(value="testWindows", ifOS = WINDOWS)
@ParameterSupplier(value="testMac", ifOS = MACOS)
@ParameterSupplier(value="winOption", ifNotOS = WINDOWS)
@ParameterSupplier(value="linuxOption", ifNotOS = LINUX)
@ParameterSupplier(value="macOption", ifNotOS = MACOS)
@ParameterSupplier(value="invalidAppVersion", ifOS = {WINDOWS,MACOS})
public static void test(TestSpec spec) {
spec.test();
}
@Test(ifOS = WINDOWS)
public static void testWinService() {
CannedFormattedString[] expectedErrors = new CannedFormattedString[] {
JPackageStringBundle.MAIN.cannedFormattedString("error.missing-service-installer"),
JPackageStringBundle.MAIN.cannedFormattedString("error.missing-service-installer.advice")
};
new PackageTest().configureHelloApp()
.addInitializer(cmd -> {
defaultInit(cmd, expectedErrors);
cmd.addArgument("--launcher-as-service");
})
.setExpectedExitCode(1)
.run(RunnablePackageTest.Action.CREATE);
@Test
@Parameter({"--input", "foo"})
@Parameter({"--module-path", "dir"})
@Parameter({"--add-modules", "java.base"})
@Parameter({"--main-class", "Hello"})
@Parameter({"--arguments", "foo"})
@Parameter({"--java-options", "-Dfoo.bar=10"})
@Parameter({"--add-launcher", "foo=foo.properties"})
@Parameter({"--app-content", "dir"})
@Parameter(value="--win-console", ifOS = WINDOWS)
public static void testRuntimeInstallerInvalidOptions(String... args) {
testSpec().noAppDesc().nativeType().addArgs("--runtime-image", Token.JAVA_HOME.token()).addArgs(args)
.error("ERR_NoInstallerEntryPoint", args[0]).create().test();
}
private static void defaultInit(JPackageCommand cmd, CannedFormattedString... expectedErrors) {
@Test
@ParameterSupplier
public static void testAdditionLaunchers(TestSpec spec) {
final Path propsFile = TKit.createTempFile("add-launcher.properties");
TKit.createPropertiesFile(propsFile, Map.of());
spec.test(Map.of(Token.ADD_LAUNCHER_PROPERTY_FILE, cmd -> propsFile));
}
public static Collection<Object[]> testAdditionLaunchers() {
return fromTestSpecBuilders(Stream.of(
testSpec().addArgs("--add-launcher", Token.ADD_LAUNCHER_PROPERTY_FILE.token())
.error("ERR_NoAddLauncherName"),
testSpec().removeArgs("--name").addArgs("--name", "foo", "--add-launcher", "foo=" + Token.ADD_LAUNCHER_PROPERTY_FILE.token())
.error("ERR_NoUniqueName")
));
}
@Test
@ParameterSupplier("invalidNames")
public static void testInvalidAppName(String name) {
testSpec().removeArgs("--name").addArgs("--name", name)
.error("ERR_InvalidAppName", adjustTextStreamVerifierArg(name)).create().test();
}
@Test
@ParameterSupplier("invalidNames")
public static void testInvalidAddLauncherName(String name) {
testAdditionLaunchers(testSpec()
.addArgs("--add-launcher", name + "=" + Token.ADD_LAUNCHER_PROPERTY_FILE.token())
.error("ERR_InvalidSLName", adjustTextStreamVerifierArg(name))
.create());
}
public static Collection<Object[]> invalidNames() {
final List<String> data = new ArrayList<>();
data.addAll(List.of("", "foo/bar", "foo\tbar", "foo\rbar", "foo\nbar"));
if (TKit.isWindows()) {
data.add("foo\\bar");
}
return toTestArgs(data.stream());
}
public static Collection<Object[]> testWindows() {
final List<TestSpec> testCases = new ArrayList<>();
testCases.addAll(PackageType.WINDOWS.stream().map(type -> {
return Stream.of(
testSpec().type(type).addArgs("--launcher-as-service")
.error("error.missing-service-installer")
.error("error.missing-service-installer.advice"),
// The below version strings are invalid for msi and exe packaging.
// They are valid for app image packaging.
testSpec().type(type).addArgs("--app-version", "1234")
.error("error.msi-product-version-components", "1234")
.error("error.version-string-wrong-format.advice"),
testSpec().type(type).addArgs("--app-version", "1.2.3.4.5")
.error("error.msi-product-version-components", "1.2.3.4.5")
.error("error.version-string-wrong-format.advice"),
testSpec().type(type).addArgs("--app-version", "256.1")
.error("error.msi-product-version-major-out-of-range", "256.1")
.error("error.version-string-wrong-format.advice"),
testSpec().type(type).addArgs("--app-version", "1.256")
.error("error.msi-product-version-minor-out-of-range", "1.256")
.error("error.version-string-wrong-format.advice"),
testSpec().type(type).addArgs("--app-version", "1.2.65536")
.error("error.msi-product-version-build-out-of-range", "1.2.65536")
.error("error.version-string-wrong-format.advice")
);
}).flatMap(x -> x).map(TestSpec.Builder::create).toList());
return toTestArgs(testCases.stream());
}
public static Collection<Object[]> testMac() {
final List<TestSpec> testCases = new ArrayList<>();
testCases.addAll(Stream.of(
testSpec().addArgs("--app-version", "0.2")
.error("message.version-string-first-number-not-zero")
.error("error.invalid-cfbundle-version.advice"),
testSpec().addArgs("--app-version", "1.2.3.4")
.error("message.version-string-too-many-components")
.error("error.invalid-cfbundle-version.advice"),
testSpec().invalidTypeArg("--mac-installer-sign-identity", "foo"),
testSpec().type(PackageType.MAC_DMG).invalidTypeArg("--mac-installer-sign-identity", "foo"),
testSpec().invalidTypeArg("--mac-dmg-content", "foo"),
testSpec().type(PackageType.MAC_PKG).invalidTypeArg("--mac-dmg-content", "foo"),
testSpec().noAppDesc().addArgs("--app-image", Token.APP_IMAGE.token())
.error("error.app-image.mac-sign.required"),
testSpec().type(PackageType.MAC_PKG).addArgs("--mac-package-identifier", "#1")
.error("message.invalid-identifier", "#1"),
// Bundle for mac app store should not have runtime commands
testSpec().nativeType().addArgs("--mac-app-store", "--jlink-options", "--bind-services")
.error("ERR_MissingJLinkOptMacAppStore", "--strip-native-commands"),
testSpec().nativeType().addArgs("--mac-app-store", "--runtime-image", Token.JAVA_HOME.token())
.error("ERR_MacAppStoreRuntimeBinExists", JPackageCommand.cannedArgument(cmd -> {
return Path.of(cmd.getArgumentValue("--runtime-image")).toAbsolutePath();
}, Token.JAVA_HOME.token()))
).map(TestSpec.Builder::create).toList());
// Test a few app-image options that should not be used when signing external app image
testCases.addAll(Stream.of(
new ArgumentGroup("--app-version", "2.0"),
new ArgumentGroup("--name", "foo"),
new ArgumentGroup("--mac-app-store")
).map(argGroup -> {
return testSpec().noAppDesc().addArgs(argGroup.asArray()).addArgs("--app-image", Token.APP_IMAGE.token())
.error("ERR_InvalidOptionWithAppImageSigning", argGroup.arg());
}).<TestSpec>mapMulti((builder, acc) -> {
// It should bail out with the same error message regardless of `--mac-sign` option.
acc.accept(builder.create());
acc.accept(builder.addArgs("--mac-sign").create());
}).toList());
testCases.addAll(createMutuallyExclusive(
new ArgumentGroup("--mac-signing-key-user-name", "foo"),
new ArgumentGroup("--mac-app-image-sign-identity", "bar")
).map(TestSpec.Builder::create).toList());
testCases.addAll(createMutuallyExclusive(
new ArgumentGroup("--mac-signing-key-user-name", "foo"),
new ArgumentGroup("--mac-installer-sign-identity", "bar")
).map(TestSpec.Builder::nativeType).map(TestSpec.Builder::create).toList());
return toTestArgs(testCases.stream());
}
private record UnsupportedPlatformOption(String name, Optional<String> value) {
UnsupportedPlatformOption {
Objects.requireNonNull(name);
Objects.requireNonNull(value);
}
UnsupportedPlatformOption(String name) {
this(name, Optional.empty());
}
UnsupportedPlatformOption(String name, String value) {
this(name, Optional.of(value));
}
TestSpec toTestSpec() {
return value.map(v -> testSpec().unsupportedPlatformOption(name, v)).orElseGet(
() -> testSpec().unsupportedPlatformOption(name)).create();
}
static Collection<Object[]> createTestArgs(UnsupportedPlatformOption... options) {
return toTestArgs(Stream.of(options).map(UnsupportedPlatformOption::toTestSpec));
}
}
public static Collection<Object[]> winOption() {
return UnsupportedPlatformOption.createTestArgs(
new UnsupportedPlatformOption("--win-console"),
new UnsupportedPlatformOption("--win-dir-chooser"),
new UnsupportedPlatformOption("--win-help-url", "url"),
new UnsupportedPlatformOption("--win-menu"),
new UnsupportedPlatformOption("--win-menu-group", "name"),
new UnsupportedPlatformOption("--win-per-user-install"),
new UnsupportedPlatformOption("--win-shortcut"),
new UnsupportedPlatformOption("--win-shortcut-prompt"),
new UnsupportedPlatformOption("--win-update-url", "url"),
new UnsupportedPlatformOption("--win-upgrade-uuid", "uuid")
);
}
public static Collection<Object[]> linuxOption() {
return UnsupportedPlatformOption.createTestArgs(
new UnsupportedPlatformOption("--linux-package-name", "name"),
new UnsupportedPlatformOption("--linux-deb-maintainer", "email-address"),
new UnsupportedPlatformOption("--linux-menu-group", "menu-group-name"),
new UnsupportedPlatformOption("--linux-package-deps", "deps"),
new UnsupportedPlatformOption("--linux-rpm-license-type", "type"),
new UnsupportedPlatformOption("--linux-app-release", "release"),
new UnsupportedPlatformOption("--linux-app-category", "category-value"),
new UnsupportedPlatformOption("--linux-shortcut")
);
}
public static Collection<Object[]> macOption() {
return UnsupportedPlatformOption.createTestArgs(
new UnsupportedPlatformOption("--mac-package-identifier", "identifier"),
new UnsupportedPlatformOption("--mac-package-name", "name"),
new UnsupportedPlatformOption("--mac-package-signing-prefix", "prefix"),
new UnsupportedPlatformOption("--mac-sign"),
new UnsupportedPlatformOption("--mac-signing-keychain", "keychain-name"),
new UnsupportedPlatformOption("--mac-signing-key-user-name", "name"),
new UnsupportedPlatformOption("--mac-app-store"),
new UnsupportedPlatformOption("--mac-entitlements", "path"),
new UnsupportedPlatformOption("--mac-app-category", "category"),
new UnsupportedPlatformOption("--mac-dmg-content", "additional-content")
);
}
private static void defaultInit(JPackageCommand cmd, List<CannedFormattedString> expectedErrors) {
// Disable default logic adding `--verbose` option
// to jpackage command line.
@ -158,11 +636,36 @@ public final class ErrorTest {
// with jpackage arguments in this test.
cmd.ignoreDefaultRuntime(true);
// Configure jpackage output verifier to look up the list of provided
// errors in the order they are specified.
cmd.validateOutput(Stream.of(expectedErrors)
.map(CannedFormattedString::getValue)
.map(TKit::assertTextStream)
.reduce(TKit.TextStreamVerifier::andThen).get());
cmd.validateOutput(expectedErrors.toArray(CannedFormattedString[]::new));
}
private static PackageType defaultNativeType() {
if (TKit.isLinux()) {
return PackageType.LINUX.stream().filter(PackageType::isSupported).findFirst().orElseThrow();
} else if (TKit.isOSX()) {
return PackageType.MAC_DMG;
} else if (TKit.isWindows()) {
return PackageType.WIN_MSI;
} else {
throw new UnsupportedOperationException();
}
}
private static <T> Collection<Object[]> toTestArgs(Stream<T> stream) {
return stream.map(v -> {
return new Object[] {v};
}).toList();
}
private static Collection<Object[]> fromTestSpecBuilders(Stream<TestSpec.Builder> stream) {
return toTestArgs(stream.map(TestSpec.Builder::create));
}
private static String adjustTextStreamVerifierArg(String str) {
return LINE_SEP_REGEXP.split(str)[0];
}
private static final Pattern LINE_SEP_REGEXP = Pattern.compile("\\R");
private static final PackageType NATIVE_TYPE = defaultNativeType();
}

View File

@ -21,12 +21,14 @@
* questions.
*/
import static jdk.jpackage.test.JPackageStringBundle.MAIN;
import java.nio.file.Path;
import java.util.Map;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.FileAssociations;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
@ -111,22 +113,16 @@ public class FileAssociationsTest {
public static void testNoMime() {
final Path propFile = TKit.workDir().resolve("fa.properties");
PackageTest packageTest = new PackageTest().excludeTypes(PackageType.MAC);
packageTest.configureHelloApp().addRunOnceInitializer(() -> {
initPackageTest().addRunOnceInitializer(() -> {
TKit.createPropertiesFile(propFile, Map.of(
"extension", "foo",
"description", "bar"
));
}).addInitializer(cmd -> {
cmd.addArguments("--file-associations", propFile).saveConsoleOutput(true);
}).setExpectedExitCode(1).addBundleVerifier((cmd, result) -> {
TKit.assertTextStream(
"No MIME types were specified for File Association number 1")
.apply(result.getOutput().stream());
TKit.assertTextStream(
"Advice to fix: Specify MIME type for File Association number 1")
.apply(result.getOutput().stream());
cmd.addArguments("--file-associations", propFile);
cmd.validateOutput(
MAIN.cannedFormattedString("error.no-content-types-for-file-association", 1),
MAIN.cannedFormattedString("error.no-content-types-for-file-association.advice", 1));
}).run();
}
@ -134,23 +130,25 @@ public class FileAssociationsTest {
public static void testTooManyMimes() {
final Path propFile = TKit.workDir().resolve("fa.properties");
PackageTest packageTest = new PackageTest().excludeTypes(PackageType.MAC);
packageTest.configureHelloApp().addRunOnceInitializer(() -> {
initPackageTest().addRunOnceInitializer(() -> {
TKit.createPropertiesFile(propFile, Map.of(
"mime-type", "application/x-jpackage-foo, application/x-jpackage-bar",
"extension", "foo",
"description", "bar"
));
}).addInitializer(cmd -> {
cmd.addArguments("--file-associations", propFile).saveConsoleOutput(true);
}).setExpectedExitCode(1).addBundleVerifier((cmd, result) -> {
TKit.assertTextStream(
"More than one MIME types was specified for File Association number 1")
.apply(result.getOutput().stream());
TKit.assertTextStream(
"Advice to fix: Specify only one MIME type for File Association number 1")
.apply(result.getOutput().stream());
cmd.addArguments("--file-associations", propFile);
cmd.validateOutput(
MAIN.cannedFormattedString("error.too-many-content-types-for-file-association", 1),
MAIN.cannedFormattedString("error.too-many-content-types-for-file-association.advice", 1));
}).run();
}
private static PackageTest initPackageTest() {
return new PackageTest()
.excludeTypes(PackageType.MAC)
.configureHelloApp()
.addInitializer(JPackageCommand::setFakeRuntime)
.setExpectedExitCode(1);
}
}

View File

@ -154,7 +154,7 @@ public final class JLinkOptionsTest {
}
private final JPackageCommand createJPackageCommand(String javaAppDesc) {
return JPackageCommand.helloAppImage(javaAppDesc).ignoreDefaultRuntime(true);
return JPackageCommand.helloAppImage(javaAppDesc);
}
private final Set<String> getModulesInRuntime(String ... jlinkOptions) {

View File

@ -54,13 +54,13 @@ import jdk.jpackage.test.TKit;
public class JavaOptionsEqualsTest {
private final static String OPTION1 =
private static final String OPTION1 =
"--add-exports=java.base/sun.util=me.mymodule.foo,ALL-UNNAMED";
private final static String OPTION2 =
private static final String OPTION2 =
"--add-exports=java.base/sun.security.util=other.mod.bar,ALL-UNNAMED";
private final static String WARNING1 =
private static final String WARNING1 =
"WARNING: Unknown module: me.mymodule.foo";
private final static String WARNING2 =
private static final String WARNING2 =
"WARNING: Unknown module: other.mod.bar";
private final JPackageCommand cmd;
@ -75,17 +75,13 @@ public class JavaOptionsEqualsTest {
}
public JavaOptionsEqualsTest(String javaAppDesc, String[] jpackageArgs) {
cmd = JPackageCommand.helloAppImage(javaAppDesc);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
}
cmd = JPackageCommand.helloAppImage(javaAppDesc).addArguments(jpackageArgs).ignoreFakeRuntime();
}
@Test
public void test() {
cmd.executeAndAssertHelloAppImageCreated();
List<String> output = HelloApp.executeLauncher(cmd).getOutput();
TKit.assertNotNull(output, "output is null");
TKit.assertTextStream(WARNING1).apply(output.stream());
TKit.assertTextStream(WARNING2).apply(output.stream());
}

View File

@ -76,7 +76,7 @@ public class JavaOptionsTest {
public JavaOptionsTest(String javaAppDesc, String[] jpackageArgs,
String[] expectedParams) {
cmd = JPackageCommand.helloAppImage(javaAppDesc);
cmd = JPackageCommand.helloAppImage(javaAppDesc).ignoreFakeRuntime();
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
}
@ -90,7 +90,6 @@ public class JavaOptionsTest {
// 2.) run the launcher it generated
List<String> output = HelloApp.executeLauncher(cmd).getOutput();
TKit.assertNotNull(output, "output is null");
for (String expect : expected) {
TKit.assertTextStream(expect).apply(output.stream());
}

View File

@ -39,10 +39,13 @@ import java.util.stream.Stream;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.CfgFile;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import static jdk.jpackage.test.JPackageCommand.cannedArgument;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.JavaTool;
import jdk.jpackage.test.TKit;
@ -87,8 +90,8 @@ public final class MainClassTest {
return this;
}
Script expectedErrorMessage(String v) {
expectedErrorMessage = v;
Script expectedErrorMessage(String key, Object... args) {
expectedErrorMessage = JPackageStringBundle.MAIN.cannedFormattedString(key, args);
return this;
}
@ -131,7 +134,7 @@ public final class MainClassTest {
private boolean withJLink;
private MainClassType mainClass;
private MainClassType jarMainClass;
private String expectedErrorMessage;
private CannedFormattedString expectedErrorMessage;
}
public MainClassTest(Script script) {
@ -194,11 +197,12 @@ public final class MainClassTest {
if (withMainClass.contains(jarMainClass)
|| withMainClass.contains(mainClass)) {
} else if (modular) {
script.expectedErrorMessage(
"Error: Main application class is missing");
script.expectedErrorMessage("ERR_NoMainClass");
} else {
script.expectedErrorMessage(
"A main class was not specified nor was one found in the jar");
"error.no-main-class-with-main-jar", cannedArgument(cmd -> {
return cmd.getArgumentValue("--main-jar");
}, "MAIN-JAR"));
}
scripts.add(new Script[]{script});
@ -218,11 +222,7 @@ public final class MainClassTest {
if (script.expectedErrorMessage != null) {
// This is the case when main class is not found nor in jar
// file nor on command line.
List<String> output = cmd
.saveConsoleOutput(true)
.execute(1)
.getOutput();
TKit.assertTextStream(script.expectedErrorMessage).apply(output.stream());
cmd.validateOutput(script.expectedErrorMessage).execute(1);
return;
}

View File

@ -30,10 +30,12 @@ import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.test.CannedFormattedString;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JPackageStringBundle;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Parameters;
@ -121,25 +123,22 @@ public final class ModulePathTest {
if (withGoodPath) {
cmd.executeAndAssertHelloAppImageCreated();
} else {
final String expectedErrorMessage;
final CannedFormattedString expectedErrorMessage;
if (modulePathArgs.isEmpty()) {
expectedErrorMessage = "Error: Missing argument: --runtime-image or --module-path";
expectedErrorMessage = JPackageStringBundle.MAIN.cannedFormattedString(
"ERR_MissingArgument", "--runtime-image or --module-path");
} else {
expectedErrorMessage = String.format(
"Failed to find %s module in module path", appDesc.moduleName());
expectedErrorMessage = JPackageStringBundle.MAIN.cannedFormattedString(
"error.no-module-in-path", appDesc.moduleName());
}
List<String> output = cmd
.saveConsoleOutput(true)
.execute(1)
.getOutput();
TKit.assertTextStream(expectedErrorMessage).apply(output.stream());
cmd.validateOutput(expectedErrorMessage).execute(1);
}
}
private final List<String> modulePathArgs;
private final static String GOOD_PATH = "@GoodPath@";
private final static String EMPTY_DIR = "@EmptyDir@";
private final static String NON_EXISTING_DIR = "@NonExistingDir@";
private static final String GOOD_PATH = "@GoodPath@";
private static final String EMPTY_DIR = "@EmptyDir@";
private static final String NON_EXISTING_DIR = "@NonExistingDir@";
}

View File

@ -1,91 +0,0 @@
/*
* Copyright (c) 2020, 2025, 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.
*/
import java.util.Collection;
import java.util.List;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
/*
* @test
* @summary jpackage application version testing
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile -Xlint:all -Werror NonExistentTest.java
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=NonExistentTest
*/
public final class NonExistentTest {
private final String expectedError;
private final JPackageCommand cmd;
@Parameters
public static Collection<?> input() {
return List.of(new Object[][]{
// non-existent icon
{"Hello",
new String[]{"--icon", "non-existent"},
"Error:"},
{"com.other/com.other.Hello",
new String[]{"--icon", "non-existent"},
"Error:"},
// non-existent input
{"Hello",
new String[]{"--input", "non-existent"},
"Exception:"},
{"com.other/com.other.Hello",
new String[]{"--input", "non-existent"},
"Exception:"},
// non-existent resource-dir
{"Hello",
new String[]{"--resource-dir", "non-existent"},
"Specified resource directory"},
{"com.other/com.other.Hello",
new String[]{"--resource-dir", "non-existent"},
"Specified resource directory"},
});
}
public NonExistentTest(String javaAppDesc, String[] jpackageArgs,
String expectedError) {
this.expectedError = expectedError;
cmd = JPackageCommand.helloAppImage(javaAppDesc)
.saveConsoleOutput(true).dumpOutput(true);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
}
}
@Test
public void test() {
List<String> output = cmd.execute(1).getOutput();
TKit.assertNotNull(output, "output is null");
TKit.assertTextStream(expectedError).apply(output.stream());
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright (c) 2022, 2025, 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.
*/
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Files;
import java.util.Collection;
import java.util.List;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
/*
* @test
* @summary Test jpackage output for erroneous input with --type "app-image" and --app-image
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @compile -Xlint:all -Werror PredefinedAppImageErrorTest.java
*
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=PredefinedAppImageErrorTest
* --jpt-before-run=jdk.jpackage.test.JPackageCommand.useExecutableByDefault
*
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
* --jpt-run=PredefinedAppImageErrorTest
* --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
*/
public final class PredefinedAppImageErrorTest {
private final String expectedError;
private final JPackageCommand cmd;
@Parameters
public static Collection<?> input() throws IOException {
return List.of(new Object[][]{
// --mac-sign is required
{"Hello",
null,
new String[]{"--input", "--dest", "--name", "--main-jar", "--main-class"},
TKit.isOSX() ?
"--mac-sign option is required" :
"Option [--app-image] is not valid with type [app-image]"
},
// --mac-app-store is required
{"Hello",
new String[]{"--mac-sign", "--mac-app-store", "--mac-app-image-sign-identity", "test"},
new String[]{"--input", "--dest", "--name", "--main-jar", "--main-class"},
TKit.isOSX() ?
"Option [--mac-app-store] is not valid" :
"Option [--mac-sign] is not valid on this platform"
},
});
}
public PredefinedAppImageErrorTest(String javaAppDesc, String[] jpackageArgs,
String[] removeArgs,
String expectedError) {
this.expectedError = expectedError;
cmd = JPackageCommand.helloAppImage(javaAppDesc)
.saveConsoleOutput(true).dumpOutput(true);
if (jpackageArgs != null) {
cmd.addArguments(jpackageArgs);
}
if (removeArgs != null) {
for (String arg : removeArgs) {
cmd.removeArgumentWithValue(arg);
}
}
}
@Test
public void test() throws IOException {
getDummyAppImage(cmd);
List<String> output = cmd.execute(1).getOutput();
TKit.assertNotNull(output, "output is null");
TKit.assertTextStream(expectedError).apply(output.stream());
}
private void getDummyAppImage(JPackageCommand cmd) throws IOException {
Path dummyAppFolder
= TKit.createTempDirectory("DummyAppImage").toAbsolutePath();
Path dummyAppFile
= dummyAppFolder.resolve("DummyAppFile").toAbsolutePath();
Files.createFile(dummyAppFile);
cmd.addArguments("--app-image", dummyAppFolder.toString());
new AppImageFile("PredefinedAppImageErrorTest", "Hello").save(dummyAppFolder);
}
}

View File

@ -29,10 +29,12 @@ import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.RunnablePackageTest.Action;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.JavaTool;
import jdk.jpackage.test.LinuxHelper;
@ -83,17 +85,24 @@ public class RuntimePackageTest {
}
@Test
public static void testUsrInstallDir() {
@Parameter("/usr")
@Parameter("/usr/lib/Java")
public static void testUsrInstallDir(String installDir) {
init(PackageType.LINUX)
.addInitializer(cmd -> cmd.addArguments("--install-dir", "/usr"))
.run();
}
@Test
public static void testUsrInstallDir2() {
init(PackageType.LINUX)
.addInitializer(cmd -> cmd.addArguments("--install-dir", "/usr/lib/Java"))
.run();
public static void testName() {
// Test that jpackage can derive package name from the path to runtime image.
init(PackageType.NATIVE)
.addInitializer(cmd -> cmd.removeArgumentWithValue("--name"))
// Don't attempt to install this package as it may have an odd name derived from
// the runtime image path. Say, on Linux for `--runtime-image foo/bar/sed`
// command line jpackage will create a package named 'sed' that will conflict
// with the default 'sed' package.
.run(Action.CREATE_AND_UNPACK);
}
private static PackageTest init(Set<PackageType> types) {
@ -157,7 +166,8 @@ public class RuntimePackageTest {
"Check the package doesn't deliver [%s] copyright file",
copyright));
}
});
})
.forTypes(types);
}
private static Set<Path> listFiles(Path root) throws IOException {