1 /*
   2  * Copyright (c) 2019, 2020, 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 
  24 package jdk.jpackage.tests;
  25 
  26 import java.io.IOException;
  27 import java.nio.file.Files;
  28 import java.nio.file.Path;
  29 import java.util.List;
  30 import java.util.ArrayList;
  31 import java.util.function.Function;
  32 import java.util.function.Predicate;
  33 import java.util.function.Supplier;
  34 import java.util.regex.Pattern;
  35 import java.util.stream.Stream;
  36 import jdk.jpackage.test.TKit;
  37 import jdk.jpackage.test.JPackageCommand;
  38 import jdk.jpackage.test.JavaAppDesc;
  39 import jdk.jpackage.test.PackageTest;
  40 import jdk.jpackage.test.HelloApp;
  41 import jdk.jpackage.test.Executor;
  42 import jdk.jpackage.test.JavaTool;
  43 import jdk.jpackage.test.Annotations.Test;
  44 import jdk.jpackage.test.Annotations.Parameter;
  45 
  46 /*
  47  * @test
  48  * @summary jpackage basic testing
  49  * @library ../../../../helpers
  50  * @build jdk.jpackage.test.*
  51  * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
  52  * @compile BasicTest.java
  53  * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
  54  *  --jpt-run=jdk.jpackage.tests.BasicTest
  55  */
  56 
  57 public final class BasicTest {
  58     @Test
  59     public void testNoArgs() {
  60         List<String> output =
  61                 getJPackageToolProvider().executeAndGetOutput();
  62         TKit.assertStringListEquals(List.of("Usage: jpackage <options>",
  63                 "Use jpackage --help (or -h) for a list of possible options"),
  64                 output, "Check jpackage output");
  65     }
  66 
  67     @Test
  68     public void testJpackageProps() {
  69         String appVersion = "3.0";
  70         JPackageCommand cmd = JPackageCommand.helloAppImage(
  71                 JavaAppDesc.parse("com.hello/com.hello.Hello"))
  72                 // Disable default logic adding `--verbose` option
  73                 // to jpackage command line.
  74                 .ignoreDefaultVerbose(true)
  75                 .saveConsoleOutput(true)
  76                 .addArguments("--app-version", appVersion, "--arguments",
  77                     "jpackage.app-version jpackage.app-path");
  78 
  79         cmd.executeAndAssertImageCreated();
  80 
  81         Path launcherPath = cmd.appLauncherPath();
  82         if (!cmd.isFakeRuntime(String.format(
  83                 "Not running [%s] launcher", launcherPath))) {
  84             List<String> output = HelloApp.executeLauncher(cmd).getOutput();
  85 
  86             TKit.assertTextStream("jpackage.app-version=" + appVersion)
  87                     .apply(output.stream());
  88             TKit.assertTextStream("jpackage.app-path=")
  89                     .apply(output.stream());
  90         }
  91     }
  92 
  93     @Test
  94     public void testVersion() {
  95         List<String> output =
  96                 getJPackageToolProvider()
  97                         .addArgument("--version")
  98                         .executeAndGetOutput();
  99         TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
 100                 output, "Check jpackage output");
 101     }
 102 
 103     @Test
 104     public void testHelp() {
 105         List<String> hOutput = getJPackageToolProvider()
 106                 .addArgument("-h").executeAndGetOutput();
 107         List<String> helpOutput = getJPackageToolProvider()
 108                 .addArgument("--help").executeAndGetOutput();
 109 
 110         TKit.assertStringListEquals(hOutput, helpOutput,
 111                 "Check -h and --help parameters produce the same output");
 112 
 113         final String windowsPrefix = "--win-";
 114         final String linuxPrefix = "--linux-";
 115         final String osxPrefix = "--mac-";
 116 
 117         final String expectedPrefix;
 118         final List<String> unexpectedPrefixes;
 119 
 120         if (TKit.isWindows()) {
 121             expectedPrefix = windowsPrefix;
 122             unexpectedPrefixes = List.of(osxPrefix, linuxPrefix);
 123         } else if (TKit.isLinux()) {
 124             expectedPrefix = linuxPrefix;
 125             unexpectedPrefixes = List.of(windowsPrefix, osxPrefix);
 126         } else if (TKit.isOSX()) {
 127             expectedPrefix = osxPrefix;
 128             unexpectedPrefixes = List.of(linuxPrefix,  windowsPrefix);
 129         } else {
 130             throw TKit.throwUnknownPlatformError();
 131         }
 132 
 133         Function<String, Predicate<String>> createPattern = (prefix) -> {
 134             return Pattern.compile("^  " + prefix).asPredicate();
 135         };
 136 
 137         Function<List<String>, Long> countStrings = (prefixes) -> {
 138             return hOutput.stream().filter(
 139                     prefixes.stream().map(createPattern).reduce(x -> false,
 140                             Predicate::or)).peek(TKit::trace).count();
 141         };
 142 
 143         TKit.trace("Check parameters in help text");
 144         TKit.assertNotEquals(0, countStrings.apply(List.of(expectedPrefix)),
 145                 "Check help text contains plaform specific parameters");
 146         TKit.assertEquals(0, countStrings.apply(unexpectedPrefixes),
 147                 "Check help text doesn't contain unexpected parameters");
 148     }
 149 
 150     @Test
 151     @SuppressWarnings("unchecked")
 152     public void testVerbose() {
 153         JPackageCommand cmd = JPackageCommand.helloAppImage()
 154                 // Disable default logic adding `--verbose` option
 155                 // to jpackage command line.
 156                 .ignoreDefaultVerbose(true)
 157                 .saveConsoleOutput(true)
 158                 .setFakeRuntime().executePrerequisiteActions();
 159 
 160         List<String> expectedVerboseOutputStrings = new ArrayList<>();
 161         expectedVerboseOutputStrings.add("Creating app package:");
 162         if (TKit.isWindows()) {
 163             expectedVerboseOutputStrings.add(
 164                     "Succeeded in building Windows Application Image package");
 165         } else if (TKit.isLinux()) {
 166             expectedVerboseOutputStrings.add(
 167                     "Succeeded in building Linux Application Image package");
 168         } else if (TKit.isOSX()) {
 169             expectedVerboseOutputStrings.add("Preparing Info.plist:");
 170             expectedVerboseOutputStrings.add(
 171                     "Succeeded in building Mac Application Image package");
 172         } else {
 173             TKit.throwUnknownPlatformError();
 174         }
 175 
 176         TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
 177         List<String> nonVerboseOutput = cmd.execute().getOutput();
 178         List<String>[] verboseOutput = (List<String>[])new List<?>[1];
 179 
 180         // Directory clean up is not 100% reliable on Windows because of
 181         // antivirus software that can lock .exe files. Setup
 182         // different output directory instead of cleaning the default one for
 183         // verbose jpackage run.
 184         TKit.withTempDirectory("verbose-output", tempDir -> {
 185             cmd.setArgumentValue("--dest", tempDir);
 186             cmd.addArgument("--verbose");
 187             verboseOutput[0] = cmd.execute().getOutput();
 188         });
 189 
 190         TKit.assertTrue(nonVerboseOutput.size() < verboseOutput[0].size(),
 191                 "Check verbose output is longer than regular");
 192 
 193         expectedVerboseOutputStrings.forEach(str -> {
 194             TKit.assertTextStream(str).label("regular output")
 195                     .predicate(String::contains).negate()
 196                     .apply(nonVerboseOutput.stream());
 197         });
 198 
 199         expectedVerboseOutputStrings.forEach(str -> {
 200             TKit.assertTextStream(str).label("verbose output")
 201                     .apply(verboseOutput[0].stream());
 202         });
 203     }
 204 
 205     @Test
 206     public void testNoName() {
 207         final String mainClassName = "Greetings";
 208 
 209         JPackageCommand cmd = JPackageCommand.helloAppImage(mainClassName)
 210                 .removeArgumentWithValue("--name");
 211 
 212         Path expectedImageDir = cmd.outputDir().resolve(mainClassName);
 213         if (TKit.isOSX()) {
 214             expectedImageDir = expectedImageDir.getParent().resolve(
 215                     expectedImageDir.getFileName().toString() + ".app");
 216         }
 217 
 218         cmd.executeAndAssertHelloAppImageCreated();
 219         TKit.assertEquals(expectedImageDir.toAbsolutePath().normalize().toString(),
 220                 cmd.outputBundle().toAbsolutePath().normalize().toString(),
 221                 String.format(
 222                         "Check [%s] directory is filled with application image data",
 223                         expectedImageDir));
 224     }
 225 
 226     @Test
 227     // Regular app
 228     @Parameter("Hello")
 229     // Modular app in .jar file
 230     @Parameter("com.other/com.other.Hello")
 231     // Modular app in .jmod file
 232     @Parameter("hello.jmod:com.other/com.other.Hello")
 233     public void testApp(String javaAppDesc) {
 234         JavaAppDesc appDesc = JavaAppDesc.parse(javaAppDesc);
 235         JPackageCommand cmd = JPackageCommand.helloAppImage(appDesc);
 236         if (appDesc.jmodFileName() != null) {
 237             // .jmod files are not supported at run-time. They should be
 238             // bundled in Java run-time with jlink command, so disable
 239             // use of external Java run-time if any configured.
 240             cmd.ignoreDefaultRuntime(true);
 241         }
 242         cmd.executeAndAssertHelloAppImageCreated();
 243     }
 244 
 245     @Test
 246     public void testWhitespaceInPaths() {
 247         JPackageCommand.helloAppImage("a/b c.jar:Hello")
 248         .setArgumentValue("--input", TKit.workDir().resolve("The quick brown fox"))
 249         .setArgumentValue("--dest", TKit.workDir().resolve("jumps over the lazy dog"))
 250         .executeAndAssertHelloAppImageCreated();
 251     }
 252 
 253     @Test
 254     @Parameter("ALL-MODULE-PATH")
 255     @Parameter("ALL-DEFAULT")
 256     @Parameter("java.desktop")
 257     @Parameter("java.desktop,jdk.jartool")
 258     @Parameter({ "java.desktop", "jdk.jartool" })
 259     public void testAddModules(String... addModulesArg) {
 260         JPackageCommand cmd = JPackageCommand
 261                 .helloAppImage("goodbye.jar:com.other/com.other.Hello")
 262                 .ignoreDefaultRuntime(true); // because of --add-modules
 263         Stream.of(addModulesArg).map(v -> Stream.of("--add-modules", v)).flatMap(
 264                 s -> s).forEachOrdered(cmd::addArgument);
 265         cmd.executeAndAssertHelloAppImageCreated();
 266     }
 267 
 268     /**
 269      * Test --temp option. Doesn't make much sense for app image as temporary
 270      * directory is used only on Windows. Test it in packaging mode.
 271      * @throws IOException
 272      */
 273     @Test
 274     @Parameter("true")
 275     @Parameter("false")
 276     public void testTemp(boolean withExistingTempDir) throws IOException {
 277         final Path tempRoot = TKit.createTempDirectory("tmp");
 278         // This Test has problems on windows where path in the temp dir are too long
 279         // for the wix tools.  We can't use a tempDir outside the TKit's WorkDir, so
 280         // we minimize both the tempRoot directory name (above) and the tempDir name
 281         // (below) to the extension part (which is necessary to differenciate between
 282         // the multiple PackageTypes that will be run for one JPackageCommand).
 283         // It might be beter if the whole work dir name was shortened from:
 284         // jtreg_open_test_jdk_tools_jpackage_share_jdk_jpackage_tests_BasicTest_java.
 285         Function<JPackageCommand, Path> getTempDir = cmd -> {
 286             String ext = cmd.outputBundle().getFileName().toString();
 287             int i = ext.lastIndexOf(".");
 288             if (i > 0 && i < (ext.length() - 1)) {
 289                 ext = ext.substring(i+1);
 290             }
 291             return tempRoot.resolve(ext);
 292         };
 293 
 294         Supplier<PackageTest> createTest = () -> {
 295             return new PackageTest()
 296             .configureHelloApp()
 297             // Force save of package bundle in test work directory.
 298             .addInitializer(JPackageCommand::setDefaultInputOutput)
 299             .addInitializer(cmd -> {
 300                 Path tempDir = getTempDir.apply(cmd);
 301                 if (withExistingTempDir) {
 302                     Files.createDirectories(tempDir);
 303                 } else {
 304                     Files.createDirectories(tempDir.getParent());
 305                 }
 306                 cmd.addArguments("--temp", tempDir);
 307             });
 308         };
 309 
 310         createTest.get()
 311         .addBundleVerifier(cmd -> {
 312             // Check jpackage actually used the supplied directory.
 313             Path tempDir = getTempDir.apply(cmd);
 314             TKit.assertNotEquals(0, tempDir.toFile().list().length,
 315                     String.format(
 316                             "Check jpackage wrote some data in the supplied temporary directory [%s]",
 317                             tempDir));
 318         })
 319         .run(PackageTest.Action.CREATE);
 320 
 321         createTest.get()
 322         // Temporary directory should not be empty,
 323         // jpackage should exit with error.
 324         .setExpectedExitCode(1)
 325         .run(PackageTest.Action.CREATE);
 326     }
 327 
 328     @Test
 329     public void testAtFile() throws IOException {
 330         JPackageCommand cmd = JPackageCommand
 331                 .helloAppImage()
 332                 .setArgumentValue("--dest", TKit.createTempDirectory("output"));
 333 
 334         // Init options file with the list of options configured
 335         // for JPackageCommand instance.
 336         final Path optionsFile = TKit.createTempFile(Path.of("options"));
 337         Files.write(optionsFile,
 338                 List.of(String.join(" ", cmd.getAllArguments())));
 339 
 340         // Build app jar file.
 341         cmd.executePrerequisiteActions();
 342 
 343         // Instead of running jpackage command through configured
 344         // JPackageCommand instance, run vanilla jpackage command with @ file.
 345         getJPackageToolProvider()
 346                 .addArgument(String.format("@%s", optionsFile))
 347                 .execute();
 348 
 349         // Verify output of jpackage command.
 350         cmd.assertImageCreated();
 351         HelloApp.executeLauncherAndVerifyOutput(cmd);
 352     }
 353 
 354     private static Executor getJPackageToolProvider() {
 355         return getToolProvider(JavaTool.JPACKAGE);
 356     }
 357 
 358     private static Executor getToolProvider(JavaTool tool) {
 359         return new Executor().dumpOutput().saveOutput().setToolProvider(tool);
 360     }
 361 }