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 
  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.regex.Pattern;
  34 import java.util.stream.Collectors;
  35 import java.util.stream.Stream;
  36 import jdk.jpackage.test.*;
  37 import jdk.jpackage.test.Functional.ThrowingConsumer;
  38 import jdk.jpackage.test.Annotations.*;
  39 
  40 /*
  41  * @test
  42  * @summary jpackage basic testing
  43  * @library ../../../../helpers
  44  * @build jdk.jpackage.test.*
  45  * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
  46  * @compile BasicTest.java
  47  * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
  48  *  --jpt-run=jdk.jpackage.tests.BasicTest
  49  */
  50 
  51 public final class BasicTest {
  52     @Test
  53     public void testNoArgs() {
  54         List<String> output =
  55                 getJPackageToolProvider().executeAndGetOutput();
  56         TKit.assertStringListEquals(List.of("Usage: jpackage <options>",
  57                 "Use jpackage --help (or -h) for a list of possible options"),
  58                 output, "Check jpackage output");
  59     }
  60 
  61     @Test
  62     public void testVersion() {
  63         List<String> output =
  64                 getJPackageToolProvider()
  65                         .addArgument("--version")
  66                         .executeAndGetOutput();
  67         TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
  68                 output, "Check jpackage output");
  69     }
  70 
  71     @Test
  72     public void testHelp() {
  73         List<String> hOutput = getJPackageToolProvider()
  74                 .addArgument("-h").executeAndGetOutput();
  75         List<String> helpOutput = getJPackageToolProvider()
  76                 .addArgument("--help").executeAndGetOutput();
  77 
  78         TKit.assertStringListEquals(hOutput, helpOutput,
  79                 "Check -h and --help parameters produce the same output");
  80 
  81         final String windowsPrefix = "--win-";
  82         final String linuxPrefix = "--linux-";
  83         final String osxPrefix = "--mac-";
  84 
  85         final String expectedPrefix;
  86         final List<String> unexpectedPrefixes;
  87 
  88         if (TKit.isWindows()) {
  89             expectedPrefix = windowsPrefix;
  90             unexpectedPrefixes = List.of(osxPrefix, linuxPrefix);
  91         } else if (TKit.isLinux()) {
  92             expectedPrefix = linuxPrefix;
  93             unexpectedPrefixes = List.of(windowsPrefix, osxPrefix);
  94         } else if (TKit.isOSX()) {
  95             expectedPrefix = osxPrefix;
  96             unexpectedPrefixes = List.of(linuxPrefix,  windowsPrefix);
  97         } else {
  98             throw TKit.throwUnknownPlatformError();
  99         }
 100 
 101         Function<String, Predicate<String>> createPattern = (prefix) -> {
 102             return Pattern.compile("^  " + prefix).asPredicate();
 103         };
 104 
 105         Function<List<String>, Long> countStrings = (prefixes) -> {
 106             return hOutput.stream().filter(
 107                     prefixes.stream().map(createPattern).reduce(x -> false,
 108                             Predicate::or)).peek(TKit::trace).count();
 109         };
 110 
 111         TKit.trace("Check parameters in help text");
 112         TKit.assertNotEquals(0, countStrings.apply(List.of(expectedPrefix)),
 113                 "Check help text contains plaform specific parameters");
 114         TKit.assertEquals(0, countStrings.apply(unexpectedPrefixes),
 115                 "Check help text doesn't contain unexpected parameters");
 116     }
 117 
 118     @Test
 119     @SuppressWarnings("unchecked")
 120     public void testVerbose() {
 121         JPackageCommand cmd = JPackageCommand.helloAppImage()
 122                 .setFakeRuntime().executePrerequisiteActions();
 123 
 124         List<String> expectedVerboseOutputStrings = new ArrayList<>();
 125         expectedVerboseOutputStrings.add("Creating app package:");
 126         if (TKit.isWindows()) {
 127             expectedVerboseOutputStrings.add("Result application bundle:");
 128             expectedVerboseOutputStrings.add(
 129                     "Succeeded in building Windows Application Image package");
 130         } else if (TKit.isLinux()) {
 131             expectedVerboseOutputStrings.add(
 132                     "Succeeded in building Linux Application Image package");
 133         } else if (TKit.isOSX()) {
 134             expectedVerboseOutputStrings.add("Preparing Info.plist:");
 135             expectedVerboseOutputStrings.add(
 136                     "Succeeded in building Mac Application Image package");
 137         } else {
 138             TKit.throwUnknownPlatformError();
 139         }
 140 
 141         TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
 142         List<String> nonVerboseOutput = cmd.createExecutor().executeAndGetOutput();
 143         List<String>[] verboseOutput = (List<String>[])new List<?>[1];
 144 
 145         // Directory clean up is not 100% reliable on Windows because of
 146         // antivirus software that can lock .exe files. Setup
 147         // diffreent output directory instead of cleaning the default one for
 148         // verbose jpackage run.
 149         TKit.withTempDirectory("verbose-output", tempDir -> {
 150             cmd.setArgumentValue("--dest", tempDir);
 151             verboseOutput[0] = cmd.createExecutor().addArgument(
 152                     "--verbose").executeAndGetOutput();
 153         });
 154 
 155         TKit.assertTrue(nonVerboseOutput.size() < verboseOutput[0].size(),
 156                 "Check verbose output is longer than regular");
 157 
 158         expectedVerboseOutputStrings.forEach(str -> {
 159             TKit.assertTextStream(str).label("regular output")
 160                     .predicate(String::contains).negate()
 161                     .apply(nonVerboseOutput.stream());
 162         });
 163 
 164         expectedVerboseOutputStrings.forEach(str -> {
 165             TKit.assertTextStream(str).label("verbose output")
 166                     .apply(verboseOutput[0].stream());
 167         });
 168     }
 169 
 170     @Test
 171     public void testNoName() {
 172         final String mainClassName = "Greetings";
 173 
 174         JPackageCommand cmd = JPackageCommand.helloAppImage(mainClassName)
 175                 .removeArgumentWithValue("--name");
 176 
 177         Path expectedImageDir = cmd.outputDir().resolve(mainClassName);
 178         if (TKit.isOSX()) {
 179             expectedImageDir = expectedImageDir.getParent().resolve(
 180                     expectedImageDir.getFileName().toString() + ".app");
 181         }
 182 
 183         cmd.executeAndAssertHelloAppImageCreated();
 184         TKit.assertEquals(expectedImageDir.toAbsolutePath().normalize().toString(),
 185                 cmd.outputBundle().toAbsolutePath().normalize().toString(),
 186                 String.format(
 187                         "Check [%s] directory is filled with application image data",
 188                         expectedImageDir));
 189     }
 190 
 191     @Test
 192      // Regular app
 193     @Parameter("Hello")
 194     // Modular app
 195     @Parameter("com.other/com.other.Hello")
 196     public void testApp(String javaAppDesc) {
 197         JPackageCommand.helloAppImage(javaAppDesc)
 198         .executeAndAssertHelloAppImageCreated();
 199     }
 200 
 201     @Test
 202     public void testWhitespaceInPaths() {
 203         JPackageCommand.helloAppImage("a/b c.jar:Hello")
 204         .setArgumentValue("--input", TKit.workDir().resolve("The quick brown fox"))
 205         .setArgumentValue("--dest", TKit.workDir().resolve("jumps over the lazy dog"))
 206         .executeAndAssertHelloAppImageCreated();
 207     }
 208 
 209     @Test
 210     @Parameter("ALL-MODULE-PATH")
 211     @Parameter("ALL-DEFAULT")
 212     @Parameter("java.desktop")
 213     @Parameter("java.desktop,jdk.jartool")
 214     @Parameter({ "java.desktop", "jdk.jartool" })
 215     public void testAddModules(String... addModulesArg) {
 216         JPackageCommand cmd = JPackageCommand
 217                 .helloAppImage("goodbye.jar:com.other/com.other.Hello");
 218         Stream.of(addModulesArg).map(v -> Stream.of("--add-modules", v)).flatMap(
 219                 s -> s).forEachOrdered(cmd::addArgument);
 220         cmd.executeAndAssertHelloAppImageCreated();
 221     }
 222 
 223     /**
 224      * Test --temp option. Doesn't make much sense for app image as temporary
 225      * directory is used only on Windows. Test it in packaging mode.
 226      * @throws IOException
 227      */
 228     @Test
 229     public void testTemp() throws IOException {
 230         TKit.withTempDirectory("temp-root", tempRoot -> {
 231             Function<JPackageCommand, Path> getTempDir = cmd -> {
 232                 return tempRoot.resolve(cmd.outputBundle().getFileName());
 233             };
 234 
 235             ThrowingConsumer<JPackageCommand> addTempDir = cmd -> {
 236                 Path tempDir = getTempDir.apply(cmd);
 237                 Files.createDirectories(tempDir);
 238                 cmd.addArguments("--temp", tempDir);
 239             };
 240 
 241             new PackageTest().configureHelloApp().addInitializer(addTempDir)
 242             .addBundleVerifier(cmd -> {
 243                 // Check jpackage actually used the supplied directory.
 244                 Path tempDir = getTempDir.apply(cmd);
 245                 TKit.assertNotEquals(0, tempDir.toFile().list().length,
 246                         String.format(
 247                                 "Check jpackage wrote some data in the supplied temporary directory [%s]",
 248                                 tempDir));
 249             })
 250             .run();
 251 
 252             new PackageTest().configureHelloApp().addInitializer(addTempDir)
 253             .addInitializer(cmd -> {
 254                 // Clean output from the previus jpackage run.
 255                 Files.delete(cmd.outputBundle());
 256             })
 257             // Temporary directory should not be empty,
 258             // jpackage should exit with error.
 259             .setExpectedExitCode(1)
 260             .run();
 261         });
 262     }
 263 
 264     @Test
 265     public void testAtFile() throws IOException {
 266         JPackageCommand cmd = JPackageCommand.helloAppImage();
 267 
 268         // Init options file with the list of options configured
 269         // for JPackageCommand instance.
 270         final Path optionsFile = TKit.workDir().resolve("options");
 271         Files.write(optionsFile,
 272                 List.of(String.join(" ", cmd.getAllArguments())));
 273 
 274         // Build app jar file.
 275         cmd.executePrerequisiteActions();
 276 
 277         // Make sure output directory is empty. Normally JPackageCommand would
 278         // do this automatically.
 279         TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
 280 
 281         // Instead of running jpackage command through configured
 282         // JPackageCommand instance, run vanilla jpackage command with @ file.
 283         getJPackageToolProvider()
 284                 .addArgument(String.format("@%s", optionsFile))
 285                 .execute().assertExitCodeIsZero();
 286 
 287         // Verify output of jpackage command.
 288         cmd.assertImageCreated();
 289         HelloApp.executeLauncherAndVerifyOutput(cmd);
 290     }
 291 
 292     @Parameter("Hello")
 293     @Parameter("com.foo/com.foo.main.Aloha")
 294     @Test
 295     public void testJLinkRuntime(String javaAppDesc) {
 296         JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc);
 297 
 298         // If `--module` parameter was set on jpackage command line, get its
 299         // value and extract module name.
 300         // E.g.: foo.bar2/foo.bar.Buz -> foo.bar2
 301         // Note: HelloApp class manages `--module` parameter on jpackage command line
 302         final String moduleName = cmd.getArgumentValue("--module", () -> null,
 303                 (v) -> v.split("/", 2)[0]);
 304 
 305         if (moduleName != null) {
 306             // Build module jar.
 307             cmd.executePrerequisiteActions();
 308         }
 309 
 310         TKit.withTempDirectory("runtime", tempDir -> {
 311             final Path runtimeDir = tempDir.resolve("data");
 312 
 313             // List of modules required for test app.
 314             final var modules = new String[] {
 315                 "java.base",
 316                 "java.desktop"
 317             };
 318 
 319             Executor jlink = getToolProvider(JavaTool.JLINK)
 320             .saveOutput(false)
 321             .addArguments(
 322                     "--add-modules", String.join(",", modules),
 323                     "--output", runtimeDir.toString(),
 324                     "--strip-debug",
 325                     "--no-header-files",
 326                     "--no-man-pages");
 327 
 328             if (moduleName != null) {
 329                 jlink.addArguments("--add-modules", moduleName, "--module-path",
 330                         Path.of(cmd.getArgumentValue("--module-path")).resolve(
 331                                 "hello.jar").toString());
 332             }
 333 
 334             jlink.execute().assertExitCodeIsZero();
 335 
 336             cmd.addArguments("--runtime-image", runtimeDir);
 337             cmd.executeAndAssertHelloAppImageCreated();
 338         });
 339     }
 340 
 341     private static Executor getJPackageToolProvider() {
 342         return getToolProvider(JavaTool.JPACKAGE);
 343     }
 344 
 345     private static Executor getToolProvider(JavaTool tool) {
 346         return new Executor().dumpOutput().saveOutput().setToolProvider(tool);
 347     }
 348 }