/* * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.jpackage.test; import java.awt.Desktop; import java.awt.GraphicsEnvironment; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Functional.ThrowingConsumer; import jdk.incubator.jpackage.internal.AppImageFile; import static jdk.jpackage.test.PackageType.*; /** * Instance of PackageTest is for configuring and running a single jpackage * command to produce platform specific package bundle. * * Provides methods to hook up custom configuration of jpackage command and * verification of the output bundle. */ public final class PackageTest { /** * Default test configuration for jpackage command. Default jpackage command * initialization includes: *
  • Set --input and --dest parameters. *
  • Set --name parameter. Value of the parameter is the name of the first * class with main function found in the callers stack. Defaults can be * overridden with custom initializers set with subsequent addInitializer() * function calls. */ public PackageTest() { action = DEFAULT_ACTION; excludeTypes = new HashSet<>(); forTypes(); setExpectedExitCode(0); handlers = new HashMap<>(); namedInitializers = new HashSet<>(); currentTypes.forEach(v -> handlers.put(v, new Handler(v))); } public PackageTest excludeTypes(PackageType... types) { excludeTypes.addAll(Stream.of(types).collect(Collectors.toSet())); return forTypes(currentTypes); } public PackageTest excludeTypes(Collection types) { return excludeTypes(types.toArray(PackageType[]::new)); } public PackageTest forTypes(PackageType... types) { Collection newTypes; if (types == null || types.length == 0) { newTypes = PackageType.NATIVE; } else { newTypes = Stream.of(types).collect(Collectors.toSet()); } currentTypes = newTypes.stream() .filter(PackageType::isSupported) .filter(Predicate.not(excludeTypes::contains)) .collect(Collectors.toUnmodifiableSet()); return this; } public PackageTest forTypes(Collection types) { return forTypes(types.toArray(PackageType[]::new)); } public PackageTest notForTypes(PackageType... types) { return notForTypes(List.of(types)); } public PackageTest notForTypes(Collection types) { Set workset = new HashSet<>(currentTypes); workset.removeAll(types); return forTypes(workset); } public PackageTest setExpectedExitCode(int v) { expectedJPackageExitCode = v; return this; } private PackageTest addInitializer(ThrowingConsumer v, String id) { if (id != null) { if (namedInitializers.contains(id)) { return this; } namedInitializers.add(id); } currentTypes.stream().forEach(type -> handlers.get(type).addInitializer( ThrowingConsumer.toConsumer(v))); return this; } public PackageTest addInitializer(ThrowingConsumer v) { return addInitializer(v, null); } public PackageTest addBundleVerifier( BiConsumer v) { currentTypes.stream().forEach( type -> handlers.get(type).addBundleVerifier(v)); return this; } public PackageTest addBundleVerifier(ThrowingConsumer v) { return addBundleVerifier( (cmd, unused) -> ThrowingConsumer.toConsumer(v).accept(cmd)); } public PackageTest addBundlePropertyVerifier(String propertyName, BiConsumer pred) { return addBundleVerifier(cmd -> { pred.accept(propertyName, LinuxHelper.getBundleProperty(cmd, propertyName)); }); } public PackageTest addBundlePropertyVerifier(String propertyName, String expectedPropertyValue) { return addBundlePropertyVerifier(propertyName, (unused, v) -> { TKit.assertEquals(expectedPropertyValue, v, String.format( "Check value of %s property is [%s]", propertyName, v)); }); } public PackageTest addBundleDesktopIntegrationVerifier(boolean integrated) { forTypes(LINUX, () -> { LinuxHelper.addBundleDesktopIntegrationVerifier(this, integrated); }); return this; } public PackageTest addInstallVerifier(ThrowingConsumer v) { currentTypes.stream().forEach( type -> handlers.get(type).addInstallVerifier( ThrowingConsumer.toConsumer(v))); return this; } public PackageTest addUninstallVerifier(ThrowingConsumer v) { currentTypes.stream().forEach( type -> handlers.get(type).addUninstallVerifier( ThrowingConsumer.toConsumer(v))); return this; } static void withTestFileAssociationsFile(FileAssociations fa, ThrowingConsumer consumer) { final String testFileDefaultName = String.join(".", "test", fa.getSuffix()); TKit.withTempFile(testFileDefaultName, fa.getSuffix(), testFile -> { if (TKit.isLinux()) { LinuxHelper.initFileAssociationsTestFile(testFile); } consumer.accept(testFile); }); } PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa, String... faLauncherDefaultArgs) { // Setup test app to have valid jpackage command line before // running check of type of environment. addInitializer(cmd -> new HelloApp(null).addTo(cmd), "HelloApp"); String noActionMsg = "Not running file associations test"; if (GraphicsEnvironment.isHeadless()) { TKit.trace(String.format( "%s because running in headless environment", noActionMsg)); return this; } addInstallVerifier(cmd -> { if (cmd.isFakeRuntime(noActionMsg)) { return; } withTestFileAssociationsFile(fa, testFile -> { testFile = testFile.toAbsolutePath().normalize(); final Path appOutput = testFile.getParent() .resolve(HelloApp.OUTPUT_FILENAME); Files.deleteIfExists(appOutput); TKit.trace(String.format("Use desktop to open [%s] file", testFile)); Desktop.getDesktop().open(testFile.toFile()); TKit.waitForFileCreated(appOutput, 7); List expectedArgs = new ArrayList<>(List.of( faLauncherDefaultArgs)); expectedArgs.add(testFile.toString()); // Wait a little bit after file has been created to // make sure there are no pending writes into it. Thread.sleep(3000); HelloApp.verifyOutputFile(appOutput, expectedArgs); }); }); forTypes(PackageType.LINUX, () -> { LinuxHelper.addFileAssociationsVerifier(this, fa); }); return this; } PackageTest forTypes(Collection types, Runnable action) { Set oldTypes = Set.of(currentTypes.toArray( PackageType[]::new)); try { forTypes(types); action.run(); } finally { forTypes(oldTypes); } return this; } PackageTest forTypes(PackageType type, Runnable action) { return forTypes(List.of(type), action); } PackageTest notForTypes(Collection types, Runnable action) { Set workset = new HashSet<>(currentTypes); workset.removeAll(types); return forTypes(workset, action); } PackageTest notForTypes(PackageType type, Runnable action) { return notForTypes(List.of(type), action); } public PackageTest configureHelloApp() { return configureHelloApp(null); } public PackageTest configureHelloApp(String encodedName) { addInitializer( cmd -> new HelloApp(JavaAppDesc.parse(encodedName)).addTo(cmd)); addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput); return this; } public void run() { List supportedHandlers = handlers.values().stream() .filter(entry -> !entry.isVoid()) .collect(Collectors.toList()); if (supportedHandlers.isEmpty()) { // No handlers with initializers found. Nothing to do. return; } Supplier initializer = new Supplier<>() { @Override public JPackageCommand get() { JPackageCommand cmd = new JPackageCommand().setDefaultInputOutput(); if (bundleOutputDir != null) { cmd.setArgumentValue("--dest", bundleOutputDir.toString()); } cmd.setDefaultAppName(); return cmd; } }; supportedHandlers.forEach(handler -> handler.accept(initializer.get())); } public PackageTest setAction(Action value) { action = value; return this; } public Action getAction() { return action; } private class Handler implements Consumer { Handler(PackageType type) { if (!PackageType.NATIVE.contains(type)) { throw new IllegalArgumentException( "Attempt to configure a test for image packaging"); } this.type = type; initializers = new ArrayList<>(); bundleVerifiers = new ArrayList<>(); installVerifiers = new ArrayList<>(); uninstallVerifiers = new ArrayList<>(); } boolean isVoid() { return initializers.isEmpty(); } void addInitializer(Consumer v) { initializers.add(v); } void addBundleVerifier(BiConsumer v) { bundleVerifiers.add(v); } void addInstallVerifier(Consumer v) { installVerifiers.add(v); } void addUninstallVerifier(Consumer v) { uninstallVerifiers.add(v); } @Override public void accept(JPackageCommand cmd) { type.applyTo(cmd); initializers.stream().forEach(v -> v.accept(cmd)); cmd.executePrerequisiteActions(); switch (action) { case CREATE: Executor.Result result = cmd.execute(); result.assertExitCodeIs(expectedJPackageExitCode); if (expectedJPackageExitCode == 0) { TKit.assertFileExists(cmd.outputBundle()); } else { TKit.assertPathExists(cmd.outputBundle(), false); } verifyPackageBundle(cmd.createImmutableCopy(), result); break; case VERIFY_INSTALL: if (expectedJPackageExitCode == 0) { verifyPackageInstalled(cmd.createImmutableCopy()); } break; case VERIFY_UNINSTALL: if (expectedJPackageExitCode == 0) { verifyPackageUninstalled(cmd.createImmutableCopy()); } break; } } private void verifyPackageBundle(JPackageCommand cmd, Executor.Result result) { if (expectedJPackageExitCode == 0) { if (PackageType.LINUX.contains(cmd.packageType())) { LinuxHelper.verifyPackageBundleEssential(cmd); } } bundleVerifiers.stream().forEach(v -> v.accept(cmd, result)); } private void verifyPackageInstalled(JPackageCommand cmd) { TKit.trace(String.format("Verify installed: %s", cmd.getPrintableCommandLine())); TKit.assertDirectoryExists(cmd.appRuntimeDirectory()); if (!cmd.isRuntime()) { TKit.assertExecutableFileExists(cmd.appLauncherPath()); if (PackageType.WINDOWS.contains(cmd.packageType())) { new WindowsHelper.AppVerifier(cmd); } } TKit.assertPathExists(AppImageFile.getPathInAppImage( cmd.appInstallationDirectory()), false); installVerifiers.stream().forEach(v -> v.accept(cmd)); } private void verifyPackageUninstalled(JPackageCommand cmd) { TKit.trace(String.format("Verify uninstalled: %s", cmd.getPrintableCommandLine())); if (!cmd.isRuntime()) { TKit.assertPathExists(cmd.appLauncherPath(), false); if (PackageType.WINDOWS.contains(cmd.packageType())) { new WindowsHelper.AppVerifier(cmd); } } TKit.assertPathExists(cmd.appInstallationDirectory(), false); uninstallVerifiers.stream().forEach(v -> v.accept(cmd)); } private final PackageType type; private final List> initializers; private final List> bundleVerifiers; private final List> installVerifiers; private final List> uninstallVerifiers; } private Collection currentTypes; private Set excludeTypes; private int expectedJPackageExitCode; private Map handlers; private Set namedInitializers; private Action action; /** * Test action. */ static public enum Action { /** * Create bundle. */ CREATE, /** * Verify bundle installed. */ VERIFY_INSTALL, /** * Verify bundle uninstalled. */ VERIFY_UNINSTALL; @Override public String toString() { return name().toLowerCase().replace('_', '-'); } }; private final static Action DEFAULT_ACTION; private final static File bundleOutputDir; static { final String propertyName = "output"; String val = TKit.getConfigProperty(propertyName); if (val == null) { bundleOutputDir = null; } else { bundleOutputDir = new File(val).getAbsoluteFile(); if (!bundleOutputDir.isDirectory()) { throw new IllegalArgumentException(String.format( "Invalid value of %s sytem property: [%s]. Should be existing directory", TKit.getConfigPropertyName(propertyName), bundleOutputDir)); } } } static { final String propertyName = "action"; String action = Optional.ofNullable(TKit.getConfigProperty(propertyName)).orElse( Action.CREATE.toString()).toLowerCase(); DEFAULT_ACTION = Stream.of(Action.values()).filter( a -> a.toString().equals(action)).findFirst().orElseThrow( () -> new IllegalArgumentException(String.format( "Unrecognized value of %s property: [%s]", TKit.getConfigPropertyName(propertyName), action))); } }