/* * Copyright (c) 2018, 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.io.IOException; import java.util.stream.Stream; import java.util.stream.Collectors; import java.util.function.Consumer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import jdk.jpackage.test.TKit; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.LauncherIconVerifier; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.Executor; import jdk.jpackage.test.LinuxHelper; import jdk.jpackage.test.AdditionalLauncher; import jdk.jpackage.internal.util.function.ThrowingConsumer; import jdk.jpackage.internal.util.function.ThrowingBiConsumer; import jdk.jpackage.test.Annotations.Parameters; import jdk.jpackage.test.Annotations.Test; /* * @test * @summary jpackage create image and package with custom icons for the main and additional launcher * @library /test/jdk/tools/jpackage/helpers * @build jdk.jpackage.test.* * @compile IconTest.java * @run main/othervm/timeout=540 -Xmx512m * jdk.jpackage.test.Main * --jpt-run=IconTest */ public class IconTest { enum IconType { /** * Icon not specified. */ DefaultIcon, /** * Explicit no icon. */ NoIcon, /** * Custom icon on command line. */ CustomIcon, /** * Custom icon in resource dir. */ ResourceDirIcon, /** * Custom icon on command line and in resource dir. */ CustomWithResourceDirIcon } enum BundleType { AppImage, Package } public IconTest(BundleType bundleType, IconType mainLauncherIconType, IconType additionalLauncherIconType, String[] extraJPackageArgs) { this.appImage = (bundleType == BundleType.AppImage); this.extraJPackageArgs = extraJPackageArgs; config = Map.of( Launcher.Main, mainLauncherIconType, Launcher.Additional, additionalLauncherIconType); } public IconTest(BundleType bundleType, IconType mainLauncherIconType, IconType additionalLauncherIconType) { this.appImage = (bundleType == BundleType.AppImage); this.extraJPackageArgs = new String[0]; config = Map.of( Launcher.Main, mainLauncherIconType, Launcher.Additional, additionalLauncherIconType); } public IconTest(BundleType bundleType, IconType mainLauncherIconType) { this.appImage = (bundleType == BundleType.AppImage); this.extraJPackageArgs = new String[0]; config = Map.of(Launcher.Main, mainLauncherIconType); } @Parameters public static Collection data() { List data = new ArrayList<>(); var withLinuxShortcut = Set.of(IconType.DefaultIcon, IconType.NoIcon); for (var bundleType : BundleType.values()) { if (TKit.isWindows() && bundleType == BundleType.Package) { // On Windows icons are embedded in launcher executables in // application image. Nothing is changed when app image is // packed in msi/exe package bundle, so skip testing of package // bundle. continue; } for (var mainLauncherIconType : IconType.values()) { if (mainLauncherIconType == IconType.NoIcon) { // `No icon` setting is not applicable for the main launcher. continue; } if (TKit.isOSX()) { // Custom icons not supported for additional launchers on Mac. data.add(new Object[]{bundleType, mainLauncherIconType}); continue; } for (var additionalLauncherIconType : IconType.values()) { data.add(new Object[]{bundleType, mainLauncherIconType, additionalLauncherIconType}); if (TKit.isLinux() && bundleType == BundleType.Package && withLinuxShortcut.contains(mainLauncherIconType) && withLinuxShortcut.contains( additionalLauncherIconType)) { data.add(new Object[]{bundleType, mainLauncherIconType, additionalLauncherIconType, new String[]{ "--linux-shortcut"}}); } } } } return data; } @Test public void test() throws IOException { if (appImage) { JPackageCommand cmd = initAppImageTest(); var result = cmd.executeAndAssertImageCreated(); ThrowingConsumer.toConsumer(createInstallVerifier()).accept(cmd); ThrowingBiConsumer.toBiConsumer(createBundleVerifier()).accept(cmd, result); } else { PackageTest test = initPackageTest(); test.addInstallVerifier(createInstallVerifier()); test.addBundleVerifier(createBundleVerifier()); test.addBundleDesktopIntegrationVerifier(config.values().stream() .anyMatch(this::isWithDesktopIntegration)); test.run(PackageTest.Action.CREATE_AND_UNPACK); } } boolean isWithDesktopIntegration(IconType iconType) { if (appImage) { return false; } boolean withDesktopFile = !Set.of( IconType.NoIcon, IconType.DefaultIcon).contains(iconType); withDesktopFile |= List.of(extraJPackageArgs).contains("--linux-shortcut"); return withDesktopFile; } private ThrowingBiConsumer createBundleVerifier() { return (cmd, result) -> { var verifier = createConsoleOutputVerifier(cmd.name(), config.get( Launcher.Main), null); if (verifier != null) { verifier.apply(result.getOutput().stream()); } if (config.containsKey(Launcher.Additional)) { verifier = createConsoleOutputVerifier( Launcher.Additional.launcherName, config.get( Launcher.Additional), config.get(Launcher.Main)); if (verifier != null) { verifier.apply(result.getOutput().stream()); } } }; } private TKit.TextStreamVerifier createConsoleOutputVerifier( String launcherName, IconType iconType, IconType mainIconType) { if (iconType == IconType.DefaultIcon && mainIconType != null) { iconType = mainIconType; } return createConsoleOutputVerifier(launcherName, iconType); } private static TKit.TextStreamVerifier createConsoleOutputVerifier( String launcherName, IconType iconType) { String lookupString = null; switch (iconType) { case DefaultIcon: lookupString = String.format( "Using default package resource %s [icon] (add %s%s to the resource-dir to customize)", "JavaApp" + TKit.ICON_SUFFIX, launcherName, TKit.ICON_SUFFIX); break; case ResourceDirIcon: lookupString = String.format( "Using custom package resource [icon] (loaded from %s%s)", launcherName, TKit.ICON_SUFFIX); break; case CustomIcon: case CustomWithResourceDirIcon: lookupString = "Using custom package resource [icon] (loaded from file"; break; default: return null; } return TKit.assertTextStream(lookupString); } private ThrowingConsumer createInstallVerifier() { LauncherIconVerifier verifier = new LauncherIconVerifier(); switch (config.get(Launcher.Main)) { case NoIcon: verifier.setExpectedIcon(null); break; case DefaultIcon: verifier.setExpectedDefaultIcon(); break; case CustomIcon: verifier.setExpectedIcon(Launcher.Main.cmdlineIcon); break; case ResourceDirIcon: verifier.setExpectedIcon(Launcher.Main.resourceDirIcon); break; case CustomWithResourceDirIcon: verifier.setExpectedIcon(Launcher.Main2.cmdlineIcon); break; } return cmd -> { verifier.applyTo(cmd); if (TKit.isLinux() && !cmd.isImagePackageType()) { Path desktopFile = LinuxHelper.getDesktopFile(cmd); if (isWithDesktopIntegration(config.get(Launcher.Main))) { TKit.assertFileExists(desktopFile); } else { TKit.assertPathExists(desktopFile, false); } } }; } private void initTest(JPackageCommand cmd, PackageTest test) { config.entrySet().forEach(ThrowingConsumer.toConsumer(entry -> { initTest(entry.getKey(), entry.getValue(), cmd, test); })); ThrowingConsumer initializer = testCmd -> { testCmd.saveConsoleOutput(true); testCmd.setFakeRuntime(); testCmd.addArguments(extraJPackageArgs); }; if (test != null) { test.addInitializer(initializer); } else { ThrowingConsumer.toConsumer(initializer).accept(cmd); } } private static void initTest(Launcher cfg, IconType iconType, JPackageCommand cmd, PackageTest test) throws IOException { Consumer addLauncher = v -> { if (test != null) { v.applyTo(test); } else { v.applyTo(cmd); } }; switch (iconType) { case DefaultIcon: if (cfg.launcherName != null) { addLauncher.accept(new AdditionalLauncher(cfg.launcherName)); } break; case NoIcon: if (cfg.launcherName != null) { addLauncher.accept( new AdditionalLauncher(cfg.launcherName).setNoIcon()); } break; case CustomIcon: if (test != null) { addCustomIcon(null, test, cfg.launcherName, cfg.cmdlineIcon); } else { addCustomIcon(cmd, null, cfg.launcherName, cfg.cmdlineIcon); } break; case ResourceDirIcon: if (Launcher.PRIMARY.contains(cfg) && cfg.launcherName != null) { addLauncher.accept(new AdditionalLauncher(cfg.launcherName)); } if (test != null) { test.addInitializer(testCmd -> { addResourceDirIcon(testCmd, cfg.launcherName, cfg.resourceDirIcon); }); } else { addResourceDirIcon(cmd, cfg.launcherName, cfg.resourceDirIcon); } break; case CustomWithResourceDirIcon: switch (cfg) { case Main: initTest(Launcher.Main2, IconType.CustomIcon, cmd, test); initTest(Launcher.Main2, IconType.ResourceDirIcon, cmd, test); break; case Additional: initTest(Launcher.Additional2, IconType.CustomIcon, cmd, test); initTest(Launcher.Additional2, IconType.ResourceDirIcon, cmd, test); break; default: throw new IllegalArgumentException(); } break; } } private JPackageCommand initAppImageTest() { JPackageCommand cmd = JPackageCommand.helloAppImage(); initTest(cmd, null); return cmd; } private PackageTest initPackageTest() { PackageTest test = new PackageTest().configureHelloApp(); initTest(null, test); return test; } private static void addResourceDirIcon(JPackageCommand cmd, String launcherName, Path iconPath) throws IOException { Path resourceDir = cmd.getArgumentValue("--resource-dir", () -> null, Path::of); if (resourceDir == null) { resourceDir = TKit.createTempDirectory("resources"); cmd.addArguments("--resource-dir", resourceDir); } String dstIconFileName = Optional.ofNullable(launcherName).orElseGet( () -> cmd.name()) + TKit.ICON_SUFFIX; TKit.trace(String.format("Resource file: [%s] <- [%s]", resourceDir.resolve(dstIconFileName), iconPath)); Files.copy(iconPath, resourceDir.resolve(dstIconFileName), StandardCopyOption.REPLACE_EXISTING); } private static void addCustomIcon(JPackageCommand cmd, PackageTest test, String launcherName, Path iconPath) throws IOException { if (launcherName != null) { AdditionalLauncher al = new AdditionalLauncher(launcherName).setIcon( iconPath); if (test != null) { al.applyTo(test); } else { al.applyTo(cmd); } } else if (test != null) { test.addInitializer(testCmd -> { testCmd.addArguments("--icon", iconPath); }); } else { cmd.addArguments("--icon", iconPath); } } private enum Launcher { Main(null, ICONS[0], ICONS[1]), Main2(null, ICONS[1], ICONS[0]), Additional("x", ICONS[2], ICONS[3]), Additional2("x", ICONS[3], ICONS[2]); Launcher(String name, Path cmdlineIcon, Path resourceDirIcon) { this.launcherName = name; this.cmdlineIcon = cmdlineIcon; this.resourceDirIcon = resourceDirIcon; } private final String launcherName; private final Path cmdlineIcon; private final Path resourceDirIcon; private static final Set PRIMARY = Set.of(Main, Additional); } private final boolean appImage; private final Map config; private final String[] extraJPackageArgs; private static Path iconPath(String name) { return TKit.TEST_SRC_ROOT.resolve(Path.of("resources", name + TKit.ICON_SUFFIX)); } private static final Path[] ICONS = Stream.of("icon", "icon2", "icon3", "icon4") .map(IconTest::iconPath) .collect(Collectors.toList()).toArray(Path[]::new); }