1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.jpackage.test;
  24 
  25 import java.io.File;
  26 import java.nio.file.Path;
  27 import java.util.ArrayList;
  28 import java.util.Collection;
  29 import java.util.HashMap;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.Set;
  33 import java.util.function.BiConsumer;
  34 import java.util.function.Consumer;
  35 import java.util.function.Supplier;
  36 import java.util.stream.Collectors;
  37 import java.util.stream.Stream;
  38 
  39 /**
  40  * Instance of PackageTest is for configuring and running a single jpackage
  41  * command to produce platform specific package bundle.
  42  *
  43  * Provides methods hook up custom configuration of jpackage command and
  44  * verification of the output bundle.
  45  */
  46 public final class PackageTest {
  47 
  48     /**
  49      * Default test configuration for jpackage command. Default jpackage command
  50      * initialization includes:
  51      * <li>Set --input and --dest parameters.
  52      * <li>Set --name parameter. Value of the parameter is the name of the first
  53      * class with main function found in the callers stack. Defaults can be
  54      * overridden with custom initializers set with subsequent addInitializer()
  55      * function calls.
  56      */
  57     public PackageTest() {
  58         action = DEFAULT_ACTION;
  59         forTypes();
  60         setJPackageExitCode(0);
  61         handlers = new HashMap<>();
  62         currentTypes.forEach(v -> handlers.put(v, new Handler(v)));
  63     }
  64 
  65     public PackageTest forTypes(PackageType... types) {
  66         Collection<PackageType> newTypes;
  67         if (types == null || types.length == 0) {
  68             newTypes = PackageType.NATIVE;
  69         } else {
  70             newTypes = Set.of(types);
  71         }
  72         currentTypes = newTypes.stream().filter(type -> type.isSupported()).collect(
  73                 Collectors.toUnmodifiableSet());
  74         return this;
  75     }
  76 
  77     public PackageTest forTypes(Collection<PackageType> types) {
  78         return forTypes(types.toArray(PackageType[]::new));
  79     }
  80 
  81     public PackageTest setJPackageExitCode(int v) {
  82         expectedJPackageExitCode = 0;
  83         return this;
  84     }
  85 
  86     public PackageTest addInitializer(Consumer<JPackageCommand> v) {
  87         currentTypes.stream().forEach(type -> handlers.get(type).addInitializer(
  88                 v));
  89         return this;
  90     }
  91 
  92     public PackageTest addBundleVerifier(
  93             BiConsumer<JPackageCommand, Executor.Result> v) {
  94         currentTypes.stream().forEach(
  95                 type -> handlers.get(type).addBundleVerifier(v));
  96         return this;
  97     }
  98 
  99     public PackageTest addBundleVerifier(Consumer<JPackageCommand> v) {
 100         return addBundleVerifier((cmd, unused) -> v.accept(cmd));
 101     }
 102 
 103     public PackageTest addBundlePropertyVerifier(String propertyName,
 104             BiConsumer<String, String> pred) {
 105         return addBundleVerifier(cmd -> {
 106             String propertyValue = null;
 107             switch (cmd.packageType()) {
 108                 case LINUX_DEB:
 109                     propertyValue = LinuxHelper.getDebBundleProperty(
 110                             cmd.outputBundle(), propertyName);
 111                     break;
 112 
 113                 case LINUX_RPM:
 114                     propertyValue = LinuxHelper.geRpmBundleProperty(
 115                             cmd.outputBundle(), propertyName);
 116                     break;
 117 
 118                 default:
 119                     throw new UnsupportedOperationException();
 120             }
 121 
 122             pred.accept(propertyName, propertyValue);
 123         });
 124     }
 125 
 126     public PackageTest addBundlePropertyVerifier(String propertyName,
 127             String expectedPropertyValue) {
 128         return addBundlePropertyVerifier(propertyName, (unused, v) -> {
 129             Test.assertEquals(expectedPropertyValue, v, String.format(
 130                     "Check value of %s property is [%s]", propertyName, v));
 131         });
 132     }
 133 
 134     public PackageTest addInstallVerifier(Consumer<JPackageCommand> v) {
 135         currentTypes.stream().forEach(
 136                 type -> handlers.get(type).addInstallVerifier(v));
 137         return this;
 138     }
 139 
 140     public PackageTest addUninstallVerifier(Consumer<JPackageCommand> v) {
 141         currentTypes.stream().forEach(
 142                 type -> handlers.get(type).addUninstallVerifier(v));
 143         return this;
 144     }
 145 
 146     public PackageTest configureHelloApp() {
 147         addInitializer(cmd -> HelloApp.addTo(cmd));
 148         addInstallVerifier(cmd -> HelloApp.executeAndVerifyOutput(
 149                 cmd.launcherInstallationPath(), cmd.getAllArgumentValues(
 150                 "--arguments")));
 151         return this;
 152     }
 153 
 154     public void run() {
 155         List<Handler> supportedHandlers = handlers.values().stream()
 156                 .filter(entry -> !entry.isVoid())
 157                 .collect(Collectors.toList());
 158 
 159         if (supportedHandlers.isEmpty()) {
 160             // No handlers with initializers found. Nothing to do.
 161             return;
 162         }
 163 
 164         Supplier<JPackageCommand> initializer = new Supplier<>() {
 165             @Override
 166             public JPackageCommand get() {
 167                 JPackageCommand cmd = new JPackageCommand().setDefaultInputOutput();
 168                 if (bundleOutputDir != null) {
 169                     cmd.setArgumentValue("--dest", bundleOutputDir.toString());
 170                 }
 171                 cmd.setDefaultAppName();
 172                 return cmd;
 173             }
 174         };
 175 
 176         supportedHandlers.forEach(handler -> handler.accept(initializer.get()));
 177     }
 178 
 179     public PackageTest setAction(Action value) {
 180         action = value;
 181         return this;
 182     }
 183 
 184     public Action getAction() {
 185         return action;
 186     }
 187 
 188     private class Handler implements Consumer<JPackageCommand> {
 189 
 190         Handler(PackageType type) {
 191             if (!PackageType.NATIVE.contains(type)) {
 192                 throw new IllegalArgumentException(
 193                         "Attempt to configure a test for image packaging");
 194             }
 195             this.type = type;
 196             initializers = new ArrayList<>();
 197             bundleVerifiers = new ArrayList<>();
 198             installVerifiers = new ArrayList<>();
 199             uninstallVerifiers = new ArrayList<>();
 200         }
 201 
 202         boolean isVoid() {
 203             return initializers.isEmpty();
 204         }
 205 
 206         void addInitializer(Consumer<JPackageCommand> v) {
 207             initializers.add(v);
 208         }
 209 
 210         void addBundleVerifier(BiConsumer<JPackageCommand, Executor.Result> v) {
 211             bundleVerifiers.add(v);
 212         }
 213 
 214         void addInstallVerifier(Consumer<JPackageCommand> v) {
 215             installVerifiers.add(v);
 216         }
 217 
 218         void addUninstallVerifier(Consumer<JPackageCommand> v) {
 219             uninstallVerifiers.add(v);
 220         }
 221 
 222         @Override
 223         public void accept(JPackageCommand cmd) {
 224             type.applyTo(cmd);
 225 
 226             initializers.stream().forEach(v -> v.accept(cmd));
 227             switch (action) {
 228                 case CREATE:
 229                     Executor.Result result = cmd.execute();
 230                     result.assertExitCodeIs(expectedJPackageExitCode);
 231                     Test.assertFileExists(cmd.outputBundle(),
 232                             expectedJPackageExitCode == 0);
 233                     verifyPackageBundle(JPackageCommand.createImmutable(cmd),
 234                             result);
 235                     break;
 236 
 237                 case VERIFY_INSTALLED:
 238                     verifyPackageInstalled(JPackageCommand.createImmutable(cmd));
 239                     break;
 240 
 241                 case VERIFY_UNINSTALLED:
 242                     verifyPackageUninstalled(
 243                             JPackageCommand.createImmutable(cmd));
 244                     break;
 245             }
 246         }
 247 
 248         private void verifyPackageBundle(JPackageCommand cmd,
 249                 Executor.Result result) {
 250             bundleVerifiers.stream().forEach(v -> v.accept(cmd, result));
 251         }
 252 
 253         private void verifyPackageInstalled(JPackageCommand cmd) {
 254             Test.trace(String.format("Verify installed: %s",
 255                     cmd.getPrintableCommandLine()));
 256             if (cmd.isRuntime()) {
 257                 Test.assertDirectoryExists(
 258                         cmd.appInstallationDirectory().resolve("runtime"), false);
 259                 Test.assertDirectoryExists(
 260                         cmd.appInstallationDirectory().resolve("app"), false);
 261             }
 262 
 263             Test.assertExecutableFileExists(cmd.launcherInstallationPath(),
 264                     !cmd.isRuntime());
 265 
 266             if (PackageType.WINDOWS.contains(cmd.packageType())) {
 267                 new WindowsHelper.AppVerifier(cmd);
 268             }
 269 
 270             installVerifiers.stream().forEach(v -> v.accept(cmd));
 271         }
 272 
 273         private void verifyPackageUninstalled(JPackageCommand cmd) {
 274             Test.trace(String.format("Verify uninstalled: %s",
 275                     cmd.getPrintableCommandLine()));
 276             if (!cmd.isRuntime()) {
 277                 Test.assertFileExists(cmd.launcherInstallationPath(), false);
 278                 Test.assertDirectoryExists(cmd.appInstallationDirectory(), false);
 279             }
 280 
 281             if (PackageType.WINDOWS.contains(cmd.packageType())) {
 282                 new WindowsHelper.AppVerifier(cmd);
 283             }
 284 
 285             uninstallVerifiers.stream().forEach(v -> v.accept(cmd));
 286         }
 287 
 288         private final PackageType type;
 289         private final List<Consumer<JPackageCommand>> initializers;
 290         private final List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers;
 291         private final List<Consumer<JPackageCommand>> installVerifiers;
 292         private final List<Consumer<JPackageCommand>> uninstallVerifiers;
 293     }
 294 
 295     private Collection<PackageType> currentTypes;
 296     private int expectedJPackageExitCode;
 297     private Map<PackageType, Handler> handlers;
 298     private Action action;
 299 
 300     /**
 301      * Test action.
 302      */
 303     static public enum Action {
 304         /**
 305          * Create bundle.
 306          */
 307         CREATE,
 308         /**
 309          * Verify bundle installed.
 310          */
 311         VERIFY_INSTALLED,
 312         /**
 313          * Verify bundle uninstalled.
 314          */
 315         VERIFY_UNINSTALLED
 316     };
 317     private final static Action DEFAULT_ACTION;
 318     private final static File bundleOutputDir;
 319 
 320     static {
 321         final String JPACKAGE_TEST_OUTPUT = "jpackage.test.output";
 322 
 323         String val = System.getProperty(JPACKAGE_TEST_OUTPUT);
 324         if (val == null) {
 325             bundleOutputDir = null;
 326         } else {
 327             bundleOutputDir = new File(val).getAbsoluteFile();
 328 
 329             Test.assertTrue(bundleOutputDir.isDirectory(), String.format(
 330                     "Check value of %s property [%s] references a directory",
 331                     JPACKAGE_TEST_OUTPUT, bundleOutputDir));
 332             Test.assertTrue(bundleOutputDir.canWrite(), String.format(
 333                     "Check value of %s property [%s] references writable directory",
 334                     JPACKAGE_TEST_OUTPUT, bundleOutputDir));
 335         }
 336     }
 337 
 338     static {
 339         if (System.getProperty("jpackage.verify.install") != null) {
 340             DEFAULT_ACTION = Action.VERIFY_INSTALLED;
 341         } else if (System.getProperty("jpackage.verify.uninstall") != null) {
 342             DEFAULT_ACTION = Action.VERIFY_UNINSTALLED;
 343         } else {
 344             DEFAULT_ACTION = Action.CREATE;
 345         }
 346     }
 347 }