8308349: missing working directory option for launcher when invoked from shortcuts

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2025-08-12 03:15:49 +00:00
parent 6927fc3904
commit 72d3a2a977
16 changed files with 352 additions and 108 deletions

View file

@ -46,6 +46,7 @@ import javax.imageio.ImageIO;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import jdk.jpackage.internal.model.FileAssociation;
import jdk.jpackage.internal.model.LauncherShortcut;
import jdk.jpackage.internal.model.LinuxLauncher;
import jdk.jpackage.internal.model.LinuxPackage;
import jdk.jpackage.internal.model.Package;
@ -237,6 +238,23 @@ final class DesktopIntegration extends ShellCustomAction {
data.put("DEPLOY_BUNDLE_CATEGORY", pkg.menuGroupName());
data.put("APPLICATION_LAUNCHER", Enquoter.forPropertyValues().applyTo(
installedLayout.launchersDirectory().resolve(launcher.executableNameWithSuffix()).toString()));
data.put("STARTUP_DIRECTORY", launcher.shortcut()
.flatMap(LauncherShortcut::startupDirectory)
.map(startupDirectory -> {
switch (startupDirectory) {
case DEFAULT -> {
return (Path)null;
}
case APP_DIR -> {
return installedLayout.appDirectory();
}
default -> {
throw new AssertionError();
}
}
}).map(str -> {
return "Path=" + str;
}).orElse(null));
return data;
}

View file

@ -109,12 +109,8 @@ final class LinuxFromParams {
static final BundlerParamInfo<LinuxPackage> DEB_PACKAGE = createPackageBundlerParam(
LinuxFromParams::createLinuxDebPackage);
private static final BundlerParamInfo<Boolean> LINUX_SHORTCUT_HINT = new BundlerParamInfo<>(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
Boolean.class,
params -> false,
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? false : Boolean.valueOf(s)
);
private static final BundlerParamInfo<String> LINUX_SHORTCUT_HINT = createStringBundlerParam(
Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId());
private static final BundlerParamInfo<String> LINUX_CATEGORY = createStringBundlerParam(
Arguments.CLIOptions.LINUX_CATEGORY.getId());

View file

@ -2,6 +2,7 @@
Name=APPLICATION_NAME
Comment=APPLICATION_DESCRIPTION
Exec=APPLICATION_LAUNCHER
STARTUP_DIRECTORY
Icon=APPLICATION_ICON
Terminal=false
Type=Application

View file

@ -37,6 +37,7 @@ import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.ResourceBundle;
@ -348,16 +349,13 @@ public class Arguments {
WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN),
WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> {
setOptionValue("win-menu", true);
}),
WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN,
createArgumentWithOptionalValueAction("win-menu")),
WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN),
WIN_SHORTCUT_HINT ("win-shortcut",
OptionCategories.PLATFORM_WIN, () -> {
setOptionValue("win-shortcut", true);
}),
WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN,
createArgumentWithOptionalValueAction("win-shortcut")),
WIN_SHORTCUT_PROMPT ("win-shortcut-prompt",
OptionCategories.PLATFORM_WIN, () -> {
@ -396,10 +394,8 @@ public class Arguments {
LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps",
OptionCategories.PLATFORM_LINUX),
LINUX_SHORTCUT_HINT ("linux-shortcut",
OptionCategories.PLATFORM_LINUX, () -> {
setOptionValue("linux-shortcut", true);
}),
LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX,
createArgumentWithOptionalValueAction("linux-shortcut")),
LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX);
@ -478,9 +474,32 @@ public class Arguments {
context().pos++;
}
private static void prevArg() {
Objects.checkIndex(context().pos, context().argList.size());
context().pos--;
}
private static boolean hasNextArg() {
return context().pos < context().argList.size();
}
private static Runnable createArgumentWithOptionalValueAction(String option) {
Objects.requireNonNull(option);
return () -> {
nextArg();
if (hasNextArg()) {
var value = getArg();
if (value.startsWith("-")) {
prevArg();
setOptionValue(option, true);
} else {
setOptionValue(option, value);
}
} else {
setOptionValue(option, true);
}
};
}
}
enum OptionCategories {

View file

@ -52,6 +52,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.VENDOR;
import static jdk.jpackage.internal.StandardBundlerParam.VERSION;
import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage;
import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller;
import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction;
import java.io.IOException;
import java.nio.file.Path;
@ -69,6 +70,7 @@ import jdk.jpackage.internal.model.Launcher;
import jdk.jpackage.internal.model.LauncherShortcut;
import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory;
import jdk.jpackage.internal.model.PackageType;
import jdk.jpackage.internal.model.ParseUtils;
import jdk.jpackage.internal.model.RuntimeLayout;
import jdk.jpackage.internal.util.function.ThrowingFunction;
@ -171,11 +173,11 @@ final class FromParams {
}
static Optional<LauncherShortcut> findLauncherShortcut(
BundlerParamInfo<Boolean> shortcutParam,
BundlerParamInfo<String> shortcutParam,
Map<String, ? super Object> mainParams,
Map<String, ? super Object> launcherParams) {
Optional<Boolean> launcherValue;
Optional<String> launcherValue;
if (launcherParams == mainParams) {
// The main launcher
launcherValue = Optional.empty();
@ -183,17 +185,19 @@ final class FromParams {
launcherValue = shortcutParam.findIn(launcherParams);
}
return launcherValue.map(withShortcut -> {
if (withShortcut) {
return Optional.of(LauncherShortcutStartupDirectory.DEFAULT);
} else {
return Optional.<LauncherShortcutStartupDirectory>empty();
}
}).or(() -> {
return shortcutParam.findIn(mainParams).map(_ -> {
return Optional.of(LauncherShortcutStartupDirectory.DEFAULT);
});
}).map(LauncherShortcut::new);
return launcherValue.map(ParseUtils::parseLauncherShortcutForAddLauncher).or(() -> {
return Optional.ofNullable(mainParams.get(shortcutParam.getID())).map(toFunction(value -> {
if (value instanceof Boolean) {
return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT);
} else {
try {
return ParseUtils.parseLauncherShortcutForMainLauncher((String)value);
} catch (IllegalArgumentException ex) {
throw I18N.buildConfigException("error.invalid-option-value", value, "--" + shortcutParam.getID()).create();
}
}
}));
});
}
private static ApplicationLaunchers createLaunchers(

View file

@ -41,7 +41,14 @@ public enum LauncherShortcutStartupDirectory {
* On Linux, it indicates that a shortcut doesn't have the startup directory
* configured explicitly.
*/
DEFAULT("true");
DEFAULT("true"),
/**
* The 'app' directory in the installed application app image. This is the
* directory that is referenced with {@link ApplicationLayout#appDirectory()}
* method.
*/
APP_DIR("app-dir");
LauncherShortcutStartupDirectory(String stringValue) {
this.stringValue = Objects.requireNonNull(stringValue);

View file

@ -0,0 +1,59 @@
/*
* 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.model;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Collection of functions to create instances of types defined in this package from strings.
*/
public final class ParseUtils {
private ParseUtils() {
}
public static LauncherShortcut parseLauncherShortcutForMainLauncher(String str) {
return parse(str, LauncherShortcutStartupDirectory.APP_DIR).map(LauncherShortcut::new).orElseThrow(IllegalArgumentException::new);
}
public static LauncherShortcut parseLauncherShortcutForAddLauncher(String str) {
return parse(str, LauncherShortcutStartupDirectory.values()).map(LauncherShortcut::new).orElseGet(() -> {
if (Boolean.valueOf(str)) {
return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT);
} else {
return new LauncherShortcut();
}
});
}
private static Optional<LauncherShortcutStartupDirectory> parse(String str, LauncherShortcutStartupDirectory... recognizedValues) {
Objects.requireNonNull(str);
return Stream.of(recognizedValues).filter(v -> {
return str.equals(v.asStringValue());
}).findFirst();
}
}

View file

@ -82,6 +82,8 @@ error.invalid-app-image=Error: app-image dir "{0}" generated by another jpackage
error.invalid-install-dir=Invalid installation directory "{0}"
error.invalid-option-value=Invalid value "{0}" of option {1}
MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
Advice to fix: {2}

View file

@ -105,10 +105,10 @@ final class WinFromParams {
static final BundlerParamInfo<WinMsiPackage> MSI_PACKAGE = createPackageBundlerParam(
WinFromParams::createWinMsiPackage);
private static final BundlerParamInfo<Boolean> WIN_MENU_HINT = createBooleanBundlerParam(
private static final BundlerParamInfo<String> WIN_MENU_HINT = createStringBundlerParam(
Arguments.CLIOptions.WIN_MENU_HINT.getId());
private static final BundlerParamInfo<Boolean> WIN_SHORTCUT_HINT = createBooleanBundlerParam(
private static final BundlerParamInfo<String> WIN_SHORTCUT_HINT = createStringBundlerParam(
Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId());
public static final BundlerParamInfo<Boolean> CONSOLE_HINT = createBooleanBundlerParam(

View file

@ -474,6 +474,9 @@ final class WixAppImageFragmentBuilder extends WixFragmentBuilder {
case DEFAULT -> {
return INSTALLDIR;
}
case APP_DIR -> {
return installedAppImage.appDirectory();
}
default -> {
throw new AssertionError();
}

View file

@ -54,6 +54,10 @@ public final class AdditionalLauncher {
setPersistenceHandler(null);
}
public String name() {
return name;
}
public AdditionalLauncher withVerifyActions(Action... actions) {
verifyActions.addAll(List.of(actions));
return this;

View file

@ -44,6 +44,7 @@ public enum LauncherShortcut {
public enum StartupDirectory {
DEFAULT("true"),
APP_DIR("app-dir"),
;
StartupDirectory(String stringValue) {
@ -79,7 +80,7 @@ public enum LauncherShortcut {
private final String stringValue;
private final static Map<String, StartupDirectory> VALUE_MAP =
private static final Map<String, StartupDirectory> VALUE_MAP =
Stream.of(values()).collect(toMap(StartupDirectory::asStringValue, x -> x));
}
@ -147,7 +148,14 @@ public enum LauncherShortcut {
private Optional<StartupDirectory> findMainLauncherShortcut(JPackageCommand cmd) {
if (cmd.hasArgument(optionName())) {
return Optional.of(StartupDirectory.DEFAULT);
var value = Optional.ofNullable(cmd.getArgumentValue(optionName())).filter(optionValue -> {
return !optionValue.startsWith("-");
});
if (value.isPresent()) {
return value.flatMap(StartupDirectory::parse);
} else {
return Optional.of(StartupDirectory.DEFAULT);
}
} else {
return Optional.empty();
}

View file

@ -514,6 +514,9 @@ public final class LinuxHelper {
case DEFAULT -> {
return (Path)null;
}
case APP_DIR -> {
return cmd.pathToPackageFile(appLayout.appDirectory());
}
default -> {
throw new AssertionError();
}

View file

@ -213,7 +213,17 @@ public final class WinShortcutVerifier {
final var installDir = Path.of(installRoot.getMsiPropertyName()).resolve(getInstallationSubDirectory(cmd));
final Function<StartupDirectory, Path> workDir = startupDirectory -> {
return installDir;
switch (startupDirectory) {
case DEFAULT -> {
return installDir;
}
case APP_DIR -> {
return ApplicationLayout.windowsAppImage().resolveAt(installDir).appDirectory();
}
default -> {
throw new IllegalArgumentException();
}
}
};
if (winMenu.isPresent()) {

View file

@ -28,16 +28,21 @@ import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import jdk.internal.util.OperatingSystem;
import jdk.jpackage.test.AdditionalLauncher;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.ParameterSupplier;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.FileAssociations;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.LauncherShortcut;
import jdk.jpackage.test.LauncherShortcut.InvokeShortcutSpec;
import jdk.jpackage.test.LauncherShortcut.StartupDirectory;
@ -63,6 +68,7 @@ import jdk.jpackage.test.WinShortcutVerifier;
* @key jpackagePlatformPackage
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @requires (os.family != "mac")
* @requires (jpackage.test.SQETest != null)
* @compile -Xlint:all -Werror AddLShortcutTest.java
* @run main/othervm/timeout=540 -Xmx512m
@ -76,6 +82,7 @@ import jdk.jpackage.test.WinShortcutVerifier;
* @key jpackagePlatformPackage
* @library /test/jdk/tools/jpackage/helpers
* @build jdk.jpackage.test.*
* @requires (os.family != "mac")
* @requires (jpackage.test.SQETest == null)
* @compile -Xlint:all -Werror AddLShortcutTest.java
* @run main/othervm/timeout=1080 -Xmx512m
@ -85,7 +92,7 @@ import jdk.jpackage.test.WinShortcutVerifier;
public class AddLShortcutTest {
@Test
@Test(ifNotOS = OperatingSystem.MACOS)
public void test() {
// Configure several additional launchers with each combination of
// possible shortcut hints in add-launcher property file.
@ -93,6 +100,8 @@ public class AddLShortcutTest {
// will have shortcuts while other launchers with some properties set
// to "false" will have none.
final var packageName = MethodHandles.lookup().lookupClass().getSimpleName();
PackageTest packageTest = new PackageTest().configureHelloApp();
packageTest.addInitializer(cmd -> {
cmd.addArguments("--arguments", "Duke", "--arguments", "is",
@ -102,11 +111,14 @@ public class AddLShortcutTest {
} else if (TKit.isLinux()) {
cmd.addArguments("--linux-shortcut");
}
cmd.setArgumentValue("--name", packageName);
var addLauncherApp = TKit.TEST_SRC_ROOT.resolve("apps/PrintEnv.java");
HelloApp.createBundle(JavaAppDesc.parse(addLauncherApp + "*another.jar:Welcome"), cmd.inputDir());
});
new FileAssociations(
MethodHandles.lookup().lookupClass().getSimpleName()).applyTo(
packageTest);
new FileAssociations(packageName).applyTo(packageTest);
new AdditionalLauncher("Foo")
.setDefaultArguments("yep!")
@ -131,11 +143,16 @@ public class AddLShortcutTest {
.setShortcuts(true, false)
.applyTo(packageTest);
new AdditionalLauncher("Launcher5")
.setDefaultArguments()
var launcher5 = new AdditionalLauncher("Launcher5")
.setDefaultArguments("--print-workdir")
.setIcon(GOLDEN_ICON)
.setShortcuts(false, true)
.applyTo(packageTest);
.setShortcut(LauncherShortcut.LINUX_SHORTCUT, StartupDirectory.APP_DIR)
.setShortcut(LauncherShortcut.WIN_DESKTOP_SHORTCUT, StartupDirectory.APP_DIR)
.setShortcut(LauncherShortcut.WIN_START_MENU_SHORTCUT, null)
.setProperty("main-jar", "another.jar")
.setProperty("main-class", "Welcome");
new ShortcutStartupDirectoryVerifier(packageName).add(launcher5).applyTo(packageTest);
packageTest.run();
}
@ -190,10 +207,50 @@ public class AddLShortcutTest {
predefinedAppImage[0] = cmd.outputBundle();
}).addInitializer(cmd -> {
cfgs[0].applyToMainLauncher(cmd);
cmd.removeArgumentWithValue("--input");
cmd.setArgumentValue("--name", "AddLShortcutDir2Test");
cmd.addArguments("--app-image", predefinedAppImage[0]);
cfgs[0].applyToMainLauncher(cmd);
}).run(RunnablePackageTest.Action.CREATE_AND_UNPACK);
}
@Test(ifNotOS = OperatingSystem.MACOS)
@Parameter(value = "DEFAULT")
@Parameter(value = "APP_DIR")
public void testLastArg(StartupDirectory startupDirectory) {
final List<String> shortcutArgs = new ArrayList<>();
if (TKit.isLinux()) {
shortcutArgs.add("--linux-shortcut");
} else if (TKit.isWindows()) {
shortcutArgs.add("--win-shortcut");
} else {
TKit.assertUnexpected("Unsupported platform");
}
if (startupDirectory == StartupDirectory.APP_DIR) {
shortcutArgs.add(startupDirectory.asStringValue());
}
Path[] predefinedAppImage = new Path[1];
new PackageTest().addRunOnceInitializer(() -> {
var cmd = JPackageCommand.helloAppImage()
.setArgumentValue("--name", "foo")
.setFakeRuntime();
cmd.execute();
predefinedAppImage[0] = cmd.outputBundle();
}).addInitializer(cmd -> {
cmd.removeArgumentWithValue("--input");
cmd.setArgumentValue("--name", "AddLShortcutDir3Test");
cmd.addArguments("--app-image", predefinedAppImage[0]);
cmd.ignoreDefaultVerbose(true);
}).addInitializer(cmd -> {
cmd.addArguments(shortcutArgs);
}).addBundleVerifier(cmd -> {
TKit.assertEquals(shortcutArgs.getLast(), cmd.getAllArguments().getLast(),
"Check the last argument of jpackage command line");
}).run(RunnablePackageTest.Action.CREATE_AND_UNPACK);
}
@ -207,6 +264,7 @@ public class AddLShortcutTest {
@Test(ifNotOS = OperatingSystem.MACOS)
@Parameter(value = "DEFAULT")
@Parameter(value = "APP_DIR")
public void testInvokeShortcuts(StartupDirectory startupDirectory) {
var testApp = TKit.TEST_SRC_ROOT.resolve("apps/PrintEnv.java");
@ -219,82 +277,118 @@ public class AddLShortcutTest {
cmd.addArguments("--arguments", "--print-workdir");
}).addInitializer(JPackageCommand::ignoreFakeRuntime).addHelloAppInitializer(testApp + "*Hello");
var shortcutStartupDirectoryVerifier = new ShortcutStartupDirectoryVerifier(name, "a");
shortcutStartupDirectoryVerifier.applyTo(test, startupDirectory);
test.addInstallVerifier(cmd -> {
if (!cmd.isPackageUnpacked("Not invoking launcher shortcuts")) {
Collection<? extends InvokeShortcutSpec> invokeShortcutSpecs;
if (TKit.isLinux()) {
invokeShortcutSpecs = LinuxHelper.getInvokeShortcutSpecs(cmd);
} else if (TKit.isWindows()) {
invokeShortcutSpecs = WinShortcutVerifier.getInvokeShortcutSpecs(cmd);
} else {
throw new UnsupportedOperationException();
}
shortcutStartupDirectoryVerifier.verify(invokeShortcutSpecs);
}
});
new ShortcutStartupDirectoryVerifier(name).add("a", startupDirectory).applyTo(test);
test.run();
}
private record ShortcutStartupDirectoryVerifier(String packageName, String launcherName) {
ShortcutStartupDirectoryVerifier {
Objects.requireNonNull(packageName);
Objects.requireNonNull(launcherName);
private static final class ShortcutStartupDirectoryVerifier {
ShortcutStartupDirectoryVerifier(String packageName) {
this.packageName = Objects.requireNonNull(packageName);
}
void applyTo(PackageTest test, StartupDirectory startupDirectory) {
var al = new AdditionalLauncher(launcherName);
al.setShortcut(shortcut(), Objects.requireNonNull(startupDirectory));
al.addJavaOptions(String.format("-Djpackage.test.appOutput=${%s}/%s",
outputDirVarName(), expectedOutputFilename()));
al.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test);
}
void applyTo(PackageTest test) {
verifiers.values().forEach(verifier -> {
verifier.applyTo(test);
});
test.addInstallVerifier(cmd -> {
if (!cmd.isPackageUnpacked("Not invoking launcher shortcuts")) {
Collection<? extends InvokeShortcutSpec> invokeShortcutSpecs;
if (TKit.isLinux()) {
invokeShortcutSpecs = LinuxHelper.getInvokeShortcutSpecs(cmd);
} else if (TKit.isWindows()) {
invokeShortcutSpecs = WinShortcutVerifier.getInvokeShortcutSpecs(cmd);
} else {
throw new UnsupportedOperationException();
}
void verify(Collection<? extends InvokeShortcutSpec> invokeShortcutSpecs) throws IOException {
var invokeShortcutSpecsMap = invokeShortcutSpecs.stream().collect(Collectors.groupingBy(InvokeShortcutSpec::launcherName));
TKit.trace(String.format("Verify shortcut [%s]", launcherName));
var expectedOutputFile = Path.of(System.getenv(outputDirVarName())).resolve(expectedOutputFilename());
TKit.deleteIfExists(expectedOutputFile);
var invokeShortcutSpec = invokeShortcutSpecs.stream().filter(v -> {
return launcherName.equals(v.launcherName());
}).findAny().orElseThrow();
invokeShortcutSpec.execute();
// On Linux, "gtk-launch" is used to launch a .desktop file. It is async and there is no
// way to make it wait for exit of a process it triggers.
TKit.waitForFileCreated(expectedOutputFile, Duration.ofSeconds(10), Duration.ofSeconds(3));
TKit.assertFileExists(expectedOutputFile);
var actualStr = Files.readAllLines(expectedOutputFile).getFirst();
var outputPrefix = "$CD=";
TKit.assertTrue(actualStr.startsWith(outputPrefix), "Check output starts with '" + outputPrefix+ "' string");
invokeShortcutSpec.expectedWorkDirectory().ifPresent(expectedWorkDirectory -> {
TKit.assertEquals(
expectedWorkDirectory,
Path.of(actualStr.substring(outputPrefix.length())),
String.format("Check work directory of %s of launcher [%s]",
invokeShortcutSpec.shortcut().propertyName(),
invokeShortcutSpec.launcherName()));
for (var e : verifiers.entrySet()) {
e.getValue().verify(invokeShortcutSpecsMap.get(e.getKey()));
}
}
});
}
private String expectedOutputFilename() {
return String.format("%s-%s.out", packageName, launcherName);
ShortcutStartupDirectoryVerifier add(String launcherName, StartupDirectory startupDirectory) {
return add(new AdditionalLauncher(launcherName)
.setShortcut(shortcut(), Objects.requireNonNull(Objects.requireNonNull(startupDirectory))));
}
private String outputDirVarName() {
ShortcutStartupDirectoryVerifier add(AdditionalLauncher addLauncher) {
var launcherVerifier = new LauncherVerifier(addLauncher);
verifiers.put(launcherVerifier.launcherName(), launcherVerifier);
return this;
}
private final class LauncherVerifier {
private LauncherVerifier(AdditionalLauncher addLauncher) {
this.addLauncher = Objects.requireNonNull(addLauncher);
}
private String launcherName() {
return addLauncher.name();
}
private void applyTo(PackageTest test) {
addLauncher.addJavaOptions(String.format("-Djpackage.test.appOutput=${%s}/%s",
outputDirVarName(), expectedOutputFilename()));
addLauncher.withoutVerifyActions(Action.EXECUTE_LAUNCHER).applyTo(test);
}
private void verify(Collection<? extends InvokeShortcutSpec> invokeShortcutSpecs) throws IOException {
Objects.requireNonNull(invokeShortcutSpecs);
if (invokeShortcutSpecs.isEmpty()) {
throw new IllegalArgumentException();
}
TKit.trace(String.format("Verify shortcut [%s]", launcherName()));
var expectedOutputFile = Path.of(System.getenv(outputDirVarName())).resolve(expectedOutputFilename());
TKit.deleteIfExists(expectedOutputFile);
var invokeShortcutSpec = invokeShortcutSpecs.stream().filter(v -> {
return launcherName().equals(v.launcherName());
}).findAny().orElseThrow();
invokeShortcutSpec.execute();
// On Linux, "gtk-launch" is used to launch a .desktop file. It is async and there is no
// way to make it wait for exit of a process it triggers.
TKit.waitForFileCreated(expectedOutputFile, Duration.ofSeconds(10), Duration.ofSeconds(3));
TKit.assertFileExists(expectedOutputFile);
var actualStr = Files.readAllLines(expectedOutputFile).getFirst();
var outputPrefix = "$CD=";
TKit.assertTrue(actualStr.startsWith(outputPrefix), "Check output starts with '" + outputPrefix+ "' string");
invokeShortcutSpec.expectedWorkDirectory().ifPresent(expectedWorkDirectory -> {
TKit.assertEquals(
expectedWorkDirectory,
Path.of(actualStr.substring(outputPrefix.length())),
String.format("Check work directory of %s of launcher [%s]",
invokeShortcutSpec.shortcut().propertyName(),
invokeShortcutSpec.launcherName()));
});
}
private String expectedOutputFilename() {
return String.format("%s-%s.out", packageName, launcherName());
}
private final AdditionalLauncher addLauncher;
}
private static String outputDirVarName() {
if (TKit.isLinux()) {
return "HOME";
} else if (TKit.isWindows()) {
@ -304,7 +398,7 @@ public class AddLShortcutTest {
}
}
private LauncherShortcut shortcut() {
private static LauncherShortcut shortcut() {
if (TKit.isLinux()) {
return LauncherShortcut.LINUX_SHORTCUT;
} else if (TKit.isWindows()) {
@ -313,6 +407,10 @@ public class AddLShortcutTest {
throw new UnsupportedOperationException();
}
}
private final String packageName;
// Keep the order
private final Map<String, LauncherVerifier> verifiers = new LinkedHashMap<>();
}

View file

@ -576,6 +576,9 @@ public final class ErrorTest {
);
}).flatMap(x -> x).map(TestSpec.Builder::create).toList());
invalidShortcut(testCases::add, "--win-menu");
invalidShortcut(testCases::add, "--win-shortcut");
return toTestArgs(testCases.stream());
}
@ -642,6 +645,8 @@ public final class ErrorTest {
.error("error.rpm-invalid-value-for-package-name.advice")
).map(TestSpec.Builder::create).toList());
invalidShortcut(testCases::add, "--linux-shortcut");
return toTestArgs(testCases.stream());
}
@ -697,6 +702,13 @@ public final class ErrorTest {
duplicateAddArgs(builder, accumulator, "--mac-sign");
}
private static void invalidShortcut(Consumer<TestSpec> accumulator, String shortcutOption) {
Objects.requireNonNull(shortcutOption);
Stream.of("true", "false", "").map(value -> {
return testSpec().nativeType().addArgs(shortcutOption, value).error("error.invalid-option-value", value, shortcutOption).create();
}).forEach(accumulator);
}
private record UnsupportedPlatformOption(String name, Optional<String> value) {
UnsupportedPlatformOption {
Objects.requireNonNull(name);