1 /*
   2  * Copyright (c) 2016, 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 /*
  25  * @test
  26  * @bug 8170859
  27  * @summary Basic test for incubator modules in jmods and images
  28  * @library /lib/testlibrary
  29  * @modules jdk.compiler jdk.jartool jdk.jlink
  30  * @build CompilerUtils
  31  * @run testng/othervm ImageModules
  32  */
  33 
  34 import java.io.ByteArrayOutputStream;
  35 import java.io.File;
  36 import java.io.IOException;
  37 import java.io.PrintStream;
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 import java.util.ArrayList;
  42 import java.util.List;
  43 import java.util.function.Consumer;
  44 import java.util.spi.ToolProvider;
  45 import java.util.stream.Collectors;
  46 import java.util.stream.Stream;
  47 
  48 import jdk.testlibrary.FileUtils;
  49 import org.testng.annotations.BeforeTest;
  50 import org.testng.annotations.DataProvider;
  51 import org.testng.annotations.Test;
  52 
  53 import static java.nio.charset.StandardCharsets.UTF_8;
  54 import static jdk.testlibrary.ProcessTools.executeCommand;
  55 import static org.testng.Assert.*;
  56 
  57 public class ImageModules {
  58     private static final String JAVA_HOME = System.getProperty("java.home");
  59     private static final Path JDK_JMODS = Paths.get(JAVA_HOME, "jmods");
  60 
  61     private static final Path TEST_SRC = Paths.get(System.getProperty("test.src"));
  62     private static final Path MODS_DIR = Paths.get("mods");
  63     private static final Path CP_DIR = Paths.get("cp");
  64     private static final Path JARS_DIR = Paths.get("jars");
  65     private static final Path JMODS_DIR = Paths.get("jmods");
  66     private static final Path IMAGE = Paths.get("image");
  67 
  68     private static final String JAVA_BASE = "java.base";
  69     private final String[] modules = new String[] { "message.writer",
  70                                                     "message.converter" };
  71 
  72     @BeforeTest
  73     private void setup() throws Throwable {
  74         Path src = TEST_SRC.resolve("src");
  75         for (String name : modules) {
  76             assertTrue(CompilerUtils.compile(src.resolve(name),
  77                                              MODS_DIR,
  78                                              "--module-source-path", src.toString()));
  79         }
  80 
  81         assertTrue(CompilerUtils.compile(src.resolve("cp"),
  82                                          CP_DIR,
  83                                          "--module-path", MODS_DIR.toString(),
  84                                          "--add-modules", "message.writer"));
  85     }
  86 
  87     @DataProvider(name = "singleModule")
  88     public Object[][] singleModuleValues() throws IOException {
  89         Object[][] values = new Object[][]{
  90          // { Extra args to the build the message.converter jmod
  91          //   Tokens to pass to the run time --add-modules option
  92          //   SUCCESS or FAILURE expected
  93          //   Messages expected in the run time output
  94          //   Messages that must not appear in the run time output },
  95             { "",
  96               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
  97               ToolResult.ASSERT_SUCCESS,
  98               List.of("hello world", "message.converter", "java.base"),
  99               List.of("WARNING") },
 100             { "--do-not-resolve-by-default",
 101               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 102               ToolResult.ASSERT_FAILURE,
 103               List.of("java.base", "java.lang.ClassNotFoundException: converter.MessageConverter"),
 104               List.of("WARNING", "message.converter") },
 105             { "--warn-if-resolved=incubating",
 106               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 107               ToolResult.ASSERT_SUCCESS,
 108               List.of("hello world", "message.converter", "java.base",
 109                       "WARNING: using incubating module(s): message.converter"),
 110               List.of() },
 111             { "--do-not-resolve-by-default --warn-if-resolved=incubating",
 112               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 113               ToolResult.ASSERT_FAILURE,
 114               List.of("java.base", "java.lang.ClassNotFoundException: converter.MessageConverter"),
 115               List.of("WARNING", "message.converter") },
 116             { "--do-not-resolve-by-default --warn-if-resolved=incubating",
 117               List.of("message.converter"),
 118               ToolResult.ASSERT_SUCCESS,
 119               List.of("hello world", "message.converter", "java.base", "WARNING"),
 120               List.of() }
 121         };
 122         return values;
 123     }
 124 
 125     @Test(dataProvider = "singleModule")
 126     public void singleModule(String extraJmodArg,
 127                              List<String> addModsTokens,
 128                              Consumer<ToolResult> assertExitCode,
 129                              List<String> expectedOutput,
 130                              List<String> unexpectedOutput)
 131         throws Throwable
 132     {
 133         if (Files.notExists(JDK_JMODS)) {
 134             System.out.println("JDK jmods not found test cannot run.");
 135             return;
 136         }
 137 
 138         FileUtils.deleteFileTreeUnchecked(JMODS_DIR);
 139         FileUtils.deleteFileTreeUnchecked(IMAGE);
 140         Files.createDirectories(JMODS_DIR);
 141         Path converterJmod = JMODS_DIR.resolve("converter.jmod");
 142 
 143         jmod("create",
 144              "--class-path", MODS_DIR.resolve("message.converter").toString(),
 145              extraJmodArg,
 146              converterJmod.toString())
 147             .assertSuccess();
 148 
 149         String mpath = JDK_JMODS.toString() + File.pathSeparator + JMODS_DIR.toString();
 150         jlink("--module-path", mpath,
 151               "--add-modules", JAVA_BASE + ",message.converter",
 152               "--output", IMAGE.toString())
 153              .assertSuccess();
 154 
 155         for (String addModsToken : addModsTokens) {
 156             String[] props = new String[] {"", "-Djdk.system.module.finder.disabledFastPath"};
 157             for (String systemProp : props)
 158                 java(IMAGE,
 159                      systemProp,
 160                      "--add-modules", addModsToken,
 161                      "-cp", CP_DIR.toString(),
 162                      "test.ConvertToLowerCase", "HEllo WoRlD")
 163                     .resultChecker(assertExitCode)
 164                     .resultChecker(r -> {
 165                         expectedOutput.forEach(e -> r.assertContains(e));
 166                         unexpectedOutput.forEach(e -> r.assertDoesNotContains(e));
 167                     });
 168         }
 169     }
 170 
 171     @Test
 172     public void singleModularJar() throws Throwable {
 173         FileUtils.deleteFileTreeUnchecked(JARS_DIR);
 174         Files.createDirectories(JARS_DIR);
 175         Path converterJar = JARS_DIR.resolve("converter.jar");
 176 
 177         jar("--create",
 178             "--file", converterJar.toString(),
 179             "--warn-if-resolved=incubating",
 180             "-C", MODS_DIR.resolve("message.converter").toString() , ".")
 181             .assertSuccess();
 182 
 183 
 184         java(Paths.get(JAVA_HOME),
 185              "--module-path", JARS_DIR.toString(),
 186              "--add-modules", "message.converter",
 187              "-cp", CP_DIR.toString(),
 188              "test.ConvertToLowerCase", "HEllo WoRlD")
 189             .assertSuccess()
 190             .resultChecker(r -> {
 191                 r.assertContains("WARNING: using incubating module(s): message.converter");
 192             });
 193     }
 194 
 195     @DataProvider(name = "twoModules")
 196     public Object[][] twoModulesValues() throws IOException {
 197         Object[][] values = new Object[][]{
 198          // { Extra args to the build the message.writer jmod
 199          //   Extra args to the build the message.converter jmod
 200          //   Tokens to pass to the run time --add-modules option
 201          //   SUCCESS or FAILURE expected
 202          //   Messages expected in the run time output
 203          //   Messages that must not appear in the run time output },
 204             { "",
 205               "",
 206               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 207               ToolResult.ASSERT_SUCCESS,
 208               List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base"),
 209               List.of() },
 210             { "",
 211               "--do-not-resolve-by-default",
 212               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 213               ToolResult.ASSERT_SUCCESS,
 214               List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base"),
 215               List.of() },
 216             { "--do-not-resolve-by-default",
 217               "",
 218               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 219               ToolResult.ASSERT_FAILURE,
 220               List.of("java.lang.ClassNotFoundException: writer.MessageWriter", "java.base"),
 221               List.of("message.writer") },
 222             { "--do-not-resolve-by-default",
 223               "--do-not-resolve-by-default",
 224               List.of("ALL-DEFAULT", "ALL-SYSTEM"),
 225               ToolResult.ASSERT_FAILURE,
 226               List.of("java.lang.ClassNotFoundException: writer.MessageWriter", "java.base"),
 227               List.of("message.converter", "message.writer") },
 228         // now add in warnings
 229             { "--do-not-resolve-by-default --warn-if-resolved=incubating",
 230               "",
 231               List.of("message.writer"),
 232               ToolResult.ASSERT_SUCCESS,
 233               List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base",
 234                       "WARNING: using incubating module(s): message.writer"),
 235               List.of() },
 236             { "",
 237               "--do-not-resolve-by-default --warn-if-resolved=incubating",
 238               List.of("message.writer"),
 239               ToolResult.ASSERT_SUCCESS,
 240               List.of("HELLO CHEGAR !!!", "message.writer", "message.converter", "java.base",
 241                       "WARNING: using incubating module(s): message.converter"),
 242               List.of() } };
 243         return values;
 244     }
 245 
 246     @Test(dataProvider = "twoModules")
 247     public void doNotResolveByDefaultTwoModules(String extraFirstJmodArg,
 248                                                 String extraSecondJmodArg,
 249                                                 List<String> addModsTokens,
 250                                                 Consumer<ToolResult> assertExitCode,
 251                                                 List<String> expectedOutput,
 252                                                 List<String> unexpectedOutput)
 253         throws Throwable
 254     {
 255         if (Files.notExists(JDK_JMODS)) {
 256             System.out.println("JDK jmods not found test cannot run.");
 257             return;
 258         }
 259 
 260         FileUtils.deleteFileTreeUnchecked(JMODS_DIR);
 261         FileUtils.deleteFileTreeUnchecked(IMAGE);
 262         Files.createDirectories(JMODS_DIR);
 263         Path writerJmod = JMODS_DIR.resolve("writer.jmod");
 264         Path converterJmod = JMODS_DIR.resolve("converter.jmod");
 265 
 266         jmod("create",
 267              extraFirstJmodArg,
 268              "--class-path", MODS_DIR.resolve("message.writer").toString(),
 269              writerJmod.toString());
 270 
 271         jmod("create",
 272              "--class-path", MODS_DIR.resolve("message.converter").toString(),
 273              extraSecondJmodArg,
 274              converterJmod.toString())
 275             .assertSuccess();
 276 
 277         String mpath = JDK_JMODS.toString() + File.pathSeparator + JMODS_DIR.toString();
 278         jlink("--module-path", mpath,
 279               "--add-modules", JAVA_BASE + ",message.writer,message.converter",
 280               "--output", IMAGE.toString())
 281              .assertSuccess();
 282 
 283         for (String addModsToken : addModsTokens) {
 284             String[] props = new String[] {"", "-Djdk.system.module.finder.disabledFastPath"};
 285             for (String systemProp : props)
 286                 java(IMAGE,
 287                      systemProp,
 288                      "--add-modules", addModsToken,
 289                      "-cp", CP_DIR.toString(),
 290                      "test.WriteUpperCase", "hello chegar !!!")
 291                     .resultChecker(assertExitCode)
 292                     .resultChecker(r -> {
 293                         expectedOutput.forEach(e -> r.assertContains(e));
 294                         unexpectedOutput.forEach(e -> r.assertDoesNotContains(e));
 295                     });
 296         }
 297     }
 298 
 299     static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
 300             .orElseThrow(() -> new RuntimeException("jmod tool not found"));
 301     static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
 302             .orElseThrow(() -> new RuntimeException("jar tool not found"));
 303     static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
 304             .orElseThrow(() -> new RuntimeException("jlink tool not found"));
 305 
 306     static ToolResult jmod(String... args) { return execTool(JMOD_TOOL, args); }
 307 
 308     static ToolResult jar(String... args) { return execTool(JAR_TOOL, args); }
 309 
 310     static ToolResult jlink(String... args) { return execTool(JLINK_TOOL, args); }
 311 
 312     static ToolResult java(Path image, String... opts) throws Throwable {
 313         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 314         PrintStream ps = new PrintStream(baos);
 315         String[] options = Stream.concat(Stream.of(getJava(image)),
 316                                          Stream.of(opts).filter(s -> !s.equals("")))
 317                                  .toArray(String[]::new);
 318 
 319         ProcessBuilder pb = new ProcessBuilder(options);
 320         int exitValue = executeCommand(pb).outputTo(ps)
 321                                           .errorTo(ps)
 322                                           .getExitValue();
 323 
 324         return new ToolResult(exitValue, new String(baos.toByteArray(), UTF_8));
 325     }
 326 
 327     static ToolResult execTool(ToolProvider tool, String... args) {
 328         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 329         PrintStream ps = new PrintStream(baos);
 330         List<String> filteredArgs = Stream.of(args)
 331                                           .map(s -> s.split(" ")).flatMap(Stream::of)
 332                                           .filter(s -> !s.equals(""))
 333                                           .collect(Collectors.toList());
 334         System.out.println(tool + " " + filteredArgs);
 335         int ec = tool.run(ps, ps, filteredArgs.toArray(new String[] {}));
 336         return new ToolResult(ec, new String(baos.toByteArray(), UTF_8));
 337     }
 338 
 339     static class ToolResult {
 340         final int exitCode;
 341         final String output;
 342 
 343         ToolResult(int exitValue, String output) {
 344             this.exitCode = exitValue;
 345             this.output = output;
 346         }
 347 
 348         static Consumer<ToolResult> ASSERT_SUCCESS = r ->
 349             assertEquals(r.exitCode, 0,
 350                         "Expected exit code 0, got " + r.exitCode
 351                                 + ", with output[" + r.output + "]");
 352         static Consumer<ToolResult> ASSERT_FAILURE = r ->
 353             assertNotEquals(r.exitCode, 0,
 354                            "Expected exit code != 0, got " + r.exitCode
 355                                    + ", with output[" + r.output + "]");
 356 
 357         ToolResult assertSuccess() { ASSERT_SUCCESS.accept(this); return this; }
 358         ToolResult assertFailure() { ASSERT_FAILURE.accept(this); return this; }
 359         ToolResult resultChecker(Consumer<ToolResult> r) { r.accept(this); return this; }
 360 
 361         ToolResult assertContains(String subString) {
 362             assertTrue(output.contains(subString),
 363                        "Expected to find [" + subString + "], in output ["
 364                             + output + "]" + "\n");
 365             return this;
 366         }
 367         ToolResult assertDoesNotContains(String subString) {
 368             assertFalse(output.contains(subString),
 369                        "Expected to NOT find [" + subString + "], in output ["
 370                            + output + "]" + "\n");
 371             return this;
 372         }
 373     }
 374 
 375     static String getJava(Path image) {
 376         boolean isWindows = System.getProperty("os.name").startsWith("Windows");
 377         Path java = image.resolve("bin").resolve(isWindows ? "java.exe" : "java");
 378         if (Files.notExists(java))
 379             throw new RuntimeException(java + " not found");
 380         return java.toAbsolutePath().toString();
 381     }
 382 }