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