1 /**
   2  * Copyright (c) 2015, 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  * @modules jdk.jlink/jdk.tools.jmod
  27  * @library /jdk/jigsaw/lib /lib/testlibrary
  28  * @build jdk.testlibrary.FileUtils CompilerUtils
  29  * @run testng JmodTest
  30  * @summary Basic test for jmod
  31  */
  32 
  33 import java.io.*;
  34 import java.lang.module.ModuleDescriptor;
  35 import java.lang.reflect.Method;
  36 import java.nio.file.*;
  37 import java.util.*;
  38 import java.util.function.Consumer;
  39 import java.util.regex.Pattern;
  40 import java.util.stream.Stream;
  41 import jdk.testlibrary.FileUtils;
  42 import org.testng.annotations.BeforeTest;
  43 import org.testng.annotations.Test;
  44 import static java.lang.module.ModuleDescriptor.Version;
  45 import static java.nio.charset.StandardCharsets.UTF_8;
  46 import static java.util.stream.Collectors.toSet;
  47 import static org.testng.Assert.*;
  48 
  49 public class JmodTest {
  50 
  51     static final String TEST_SRC = System.getProperty("test.src", ".");
  52     static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
  53     static final Path EXPLODED_DIR = Paths.get("build");
  54     static final Path MODS_DIR = Paths.get("jmods");
  55 
  56     static final String CLASSES_PREFIX = "classes/";
  57     static final String CMDS_PREFIX = "bin/";
  58     static final String LIBS_PREFIX = "native/";
  59     static final String CONFIGS_PREFIX = "conf/";
  60 
  61     @BeforeTest
  62     public void buildExplodedModules() throws IOException {
  63         if (Files.exists(EXPLODED_DIR))
  64             FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR);
  65 
  66         for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) {
  67             Path dir = EXPLODED_DIR.resolve(name);
  68             assertTrue(compileModule(name, dir.resolve("classes")));
  69             createCmds(dir.resolve("bin"));
  70             createLibs(dir.resolve("lib"));
  71             createConfigs(dir.resolve("conf"));
  72         }
  73 
  74         if (Files.exists(MODS_DIR))
  75             FileUtils.deleteFileTreeWithRetry(MODS_DIR);
  76         Files.createDirectories(MODS_DIR);
  77     }
  78 
  79     @Test
  80     public void testList() throws IOException {
  81         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
  82         jmod("--create",
  83              "--class-path", cp,
  84              MODS_DIR.resolve("foo.jmod").toString())
  85             .assertSuccess();
  86 
  87         jmod("--list",
  88               MODS_DIR.resolve("foo.jmod").toString())
  89             .assertSuccess()
  90             .resultChecker(r -> {
  91                 // asserts dependent on the exact contents of foo
  92                 assertContains(r.output, CLASSES_PREFIX + "module-info.class");
  93                 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/Foo.class");
  94                 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/internal/Message.class");
  95             });
  96     }
  97 
  98     @Test
  99     public void testMainClass() throws IOException {
 100         Path jmod = MODS_DIR.resolve("fooMainClass.jmod");
 101         FileUtils.deleteFileIfExistsWithRetry(jmod);
 102         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 103 
 104         jmod("--create",
 105              "--class-path", cp,
 106              "--main-class", "jdk.test.foo.Foo",
 107              jmod.toString())
 108             .assertSuccess()
 109             .resultChecker(r -> {
 110                 Optional<String> omc = getModuleDescriptor(jmod).mainClass();
 111                 assertTrue(omc.isPresent());
 112                 assertEquals(omc.get(), "jdk.test.foo.Foo");
 113             });
 114     }
 115 
 116     @Test
 117     public void testModuleVersion() throws IOException {
 118         Path jmod = MODS_DIR.resolve("fooVersion.jmod");
 119         FileUtils.deleteFileIfExistsWithRetry(jmod);
 120         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 121 
 122         jmod("--create",
 123              "--class-path", cp,
 124              "--module-version", "5.4.3",
 125              jmod.toString())
 126             .assertSuccess()
 127             .resultChecker(r -> {
 128                 Optional<Version> ov = getModuleDescriptor(jmod).version();
 129                 assertTrue(ov.isPresent());
 130                 assertEquals(ov.get().toString(), "5.4.3");
 131             });
 132     }
 133 
 134     @Test
 135     public void testConfig() throws IOException {
 136         Path jmod = MODS_DIR.resolve("fooConfig.jmod");
 137         FileUtils.deleteFileIfExistsWithRetry(jmod);
 138         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 139         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 140 
 141         jmod("--create",
 142              "--class-path", cp.toString(),
 143              "--config", cf.toString(),
 144              jmod.toString())
 145             .assertSuccess()
 146             .resultChecker(r -> {
 147                 try (Stream<String> s1 = findFiles(cf).map(p -> CONFIGS_PREFIX + p);
 148                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 149                     Set<String> expectedFilenames = Stream.concat(s1, s2)
 150                                                           .collect(toSet());
 151                     assertJmodContent(jmod, expectedFilenames);
 152                 }
 153             });
 154     }
 155 
 156     @Test
 157     public void testCmds() throws IOException {
 158         Path jmod = MODS_DIR.resolve("fooCmds.jmod");
 159         FileUtils.deleteFileIfExistsWithRetry(jmod);
 160         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 161         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 162 
 163         jmod("--create",
 164              "--cmds", bp.toString(),
 165              "--class-path", cp.toString(),
 166              jmod.toString())
 167             .assertSuccess()
 168             .resultChecker(r -> {
 169                 try (Stream<String> s1 = findFiles(bp).map(p -> CMDS_PREFIX + p);
 170                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 171                     Set<String> expectedFilenames = Stream.concat(s1,s2)
 172                                                           .collect(toSet());
 173                     assertJmodContent(jmod, expectedFilenames);
 174                 }
 175             });
 176     }
 177 
 178     @Test
 179     public void testLibs() throws IOException {
 180         Path jmod = MODS_DIR.resolve("fooLibs.jmod");
 181         FileUtils.deleteFileIfExistsWithRetry(jmod);
 182         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 183         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 184 
 185         jmod("--create",
 186              "--libs=", lp.toString(),
 187              "--class-path", cp.toString(),
 188              jmod.toString())
 189             .assertSuccess()
 190             .resultChecker(r -> {
 191                 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
 192                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 193                     Set<String> expectedFilenames = Stream.concat(s1,s2)
 194                                                           .collect(toSet());
 195                     assertJmodContent(jmod, expectedFilenames);
 196                 }
 197             });
 198     }
 199 
 200     @Test
 201     public void testAll() throws IOException {
 202         Path jmod = MODS_DIR.resolve("fooAll.jmod");
 203         FileUtils.deleteFileIfExistsWithRetry(jmod);
 204         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 205         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 206         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 207         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 208 
 209         jmod("--create",
 210              "--conf", cf.toString(),
 211              "--cmds=", bp.toString(),
 212              "--libs=", lp.toString(),
 213              "--class-path", cp.toString(),
 214              jmod.toString())
 215             .assertSuccess()
 216             .resultChecker(r -> {
 217                 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
 218                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p);
 219                      Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p);
 220                      Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) {
 221                     Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2),
 222                                                                   Stream.concat(s3, s4))
 223                                                           .collect(toSet());
 224                     assertJmodContent(jmod, expectedFilenames);
 225                 }
 226             });
 227     }
 228 
 229     @Test
 230     public void testExcludes() throws IOException {
 231         Path jmod = MODS_DIR.resolve("fooLibs.jmod");
 232         FileUtils.deleteFileIfExistsWithRetry(jmod);
 233         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 234         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 235 
 236         jmod("--create",
 237              "--libs=", lp.toString(),
 238              "--class-path", cp.toString(),
 239              "--exclude", "**internal**",
 240              "--exclude", "first.so",
 241              jmod.toString())
 242              .assertSuccess()
 243              .resultChecker(r -> {
 244                  Set<String> expectedFilenames = new HashSet<>();
 245                  expectedFilenames.add(CLASSES_PREFIX + "module-info.class");
 246                  expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/Foo.class");
 247                  expectedFilenames.add(LIBS_PREFIX + "second.so");
 248                  expectedFilenames.add(LIBS_PREFIX + "third/third.so");
 249                  assertJmodContent(jmod, expectedFilenames);
 250 
 251                  Set<String> unexpectedFilenames = new HashSet<>();
 252                  unexpectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class");
 253                  unexpectedFilenames.add(LIBS_PREFIX + "first.so");
 254                  assertJmodDoesNotContain(jmod, unexpectedFilenames);
 255              });
 256     }
 257 
 258     @Test
 259     public void printModuleDescriptorFoo() throws IOException {
 260         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 261         jmod("--create",
 262              "--class-path", cp,
 263               MODS_DIR.resolve("printModuleDescriptorFoo.jmod").toString())
 264              .assertSuccess();
 265 
 266         for (String opt : new String[]  {"--print-module-descriptor", "-p" }) {
 267             jmod(opt,
 268                  MODS_DIR.resolve("printModuleDescriptorFoo.jmod").toString())
 269                  .assertSuccess()
 270                  .resultChecker(r -> {
 271                      // Expect similar output: "Name:foo,  Requires: java.base
 272                      // Exports: jdk.test.foo,  Conceals: jdk.test.foo.internal"
 273                      Pattern p = Pattern.compile("\\s+Name:\\s+foo\\s+Requires:\\s+java.base");
 274                      assertTrue(p.matcher(r.output).find(),
 275                                "Expecting to find \"Name: foo, Requires: java.base\"" +
 276                                     "in output, but did not: [" + r.output + "]");
 277                      p = Pattern.compile(
 278                             "Exports:\\s+jdk.test.foo\\s+Conceals:\\s+jdk.test.foo.internal");
 279                      assertTrue(p.matcher(r.output).find(),
 280                                "Expecting to find \"Exports: ..., Conceals: ...\"" +
 281                                     "in output, but did not: [" + r.output + "]");
 282                  });
 283         }
 284     }
 285 
 286     @Test
 287     public void testVersion() {
 288         jmod("--version")
 289             .assertSuccess()
 290             .resultChecker(r -> {
 291                 assertContains(r.output, System.getProperty("java.version"));
 292             });
 293     }
 294 
 295     @Test
 296     public void testHelp() {
 297         jmod("--help")
 298             .assertSuccess()
 299             .resultChecker(r ->
 300                 assertTrue(r.output.startsWith("Usage: jmod"), "Help not printed")
 301             );
 302     }
 303 
 304     @Test
 305     public void testTmpFileAlreadyExists() throws IOException {
 306         // Implementation detail: jmod tool creates <jmod-file>.tmp
 307         // Ensure that there are no problems if existing
 308 
 309         Path jmod = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod");
 310         Path tmp = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod.tmp");
 311         FileUtils.deleteFileIfExistsWithRetry(jmod);
 312         FileUtils.deleteFileIfExistsWithRetry(tmp);
 313         Files.createFile(tmp);
 314         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 315 
 316         jmod("--create",
 317              "--class-path", cp,
 318              jmod.toString())
 319             .assertSuccess()
 320             .resultChecker(r ->
 321                 assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp)
 322             );
 323     }
 324 
 325     // ---
 326 
 327     static boolean compileModule(String name, Path dest) throws IOException {
 328         return CompilerUtils.compile(SRC_DIR.resolve(name), dest);
 329     }
 330 
 331     static void assertContains(String output, String subString) {
 332         if (output.contains(subString))
 333             assertTrue(true);
 334         else
 335             assertTrue(false,"Expected to find [" + subString + "], in output ["
 336                            + output + "]" + "\n");
 337     }
 338 
 339     static ModuleDescriptor getModuleDescriptor(Path jmod) {
 340         ClassLoader cl = ClassLoader.getSystemClassLoader();
 341         try (FileSystem fs = FileSystems.newFileSystem(jmod, cl)) {
 342             String p = "/classes/module-info.class";
 343             try (InputStream is = Files.newInputStream(fs.getPath(p))) {
 344                 return ModuleDescriptor.read(is);
 345             }
 346         } catch (IOException ioe) {
 347             throw new UncheckedIOException(ioe);
 348         }
 349     }
 350 
 351     static Stream<String> findFiles(Path dir) {
 352         try {
 353             return Files.find(dir, Integer.MAX_VALUE, (p, a) -> a.isRegularFile())
 354                         .map(dir::relativize)
 355                         .map(Path::toString)
 356                         .map(p -> p.replace(File.separator, "/"));
 357         } catch (IOException x) {
 358             throw new UncheckedIOException(x);
 359         }
 360     }
 361 
 362     static Set<String> getJmodContent(Path jmod) {
 363         JmodResult r = jmod("--list", jmod.toString()).assertSuccess();
 364         return Stream.of(r.output.split("\r?\n")).collect(toSet());
 365     }
 366 
 367     static void assertJmodContent(Path jmod, Set<String> expected) {
 368         Set<String> actual = getJmodContent(jmod);
 369         if (!Objects.equals(actual, expected)) {
 370             Set<String> unexpected = new HashSet<>(actual);
 371             unexpected.removeAll(expected);
 372             Set<String> notFound = new HashSet<>(expected);
 373             notFound.removeAll(actual);
 374             StringBuilder sb = new StringBuilder();
 375             sb.append("Unexpected but found:\n");
 376             unexpected.forEach(s -> sb.append("\t" + s + "\n"));
 377             sb.append("Expected but not found:\n");
 378             notFound.forEach(s -> sb.append("\t" + s + "\n"));
 379             assertTrue(false, "Jmod content check failed.\n" + sb.toString());
 380         }
 381     }
 382 
 383     static void assertJmodDoesNotContain(Path jmod, Set<String> unexpectedNames) {
 384         Set<String> actual = getJmodContent(jmod);
 385         Set<String> unexpected = new HashSet<>();
 386         for (String name : unexpectedNames) {
 387             if (actual.contains(name))
 388                 unexpected.add(name);
 389         }
 390         if (!unexpected.isEmpty()) {
 391             StringBuilder sb = new StringBuilder();
 392             for (String s : unexpected)
 393                 sb.append("Unexpected but found: " + s + "\n");
 394             sb.append("In :");
 395             for (String s : actual)
 396                 sb.append("\t" + s + "\n");
 397             assertTrue(false, "Jmod content check failed.\n" + sb.toString());
 398         }
 399     }
 400 
 401     static JmodResult jmod(String... args) {
 402         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 403         PrintStream ps = new PrintStream(baos);
 404         System.out.println("jmod " + Arrays.asList(args));
 405         int ec = jdk.tools.jmod.Main.run(args, ps);
 406         return new JmodResult(ec, new String(baos.toByteArray(), UTF_8));
 407     }
 408 
 409     static class JmodResult {
 410         final int exitCode;
 411         final String output;
 412 
 413         JmodResult(int exitValue, String output) {
 414             this.exitCode = exitValue;
 415             this.output = output;
 416         }
 417         JmodResult assertSuccess() { assertTrue(exitCode == 0, output); return this; }
 418         JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; }
 419     }
 420 
 421     static void createCmds(Path dir) throws IOException {
 422         List<String> files = Arrays.asList(
 423                 "first", "second", "third" + File.separator + "third");
 424         createFiles(dir, files);
 425     }
 426 
 427     static void createLibs(Path dir) throws IOException {
 428         List<String> files = Arrays.asList(
 429                 "first.so", "second.so", "third" + File.separator + "third.so");
 430         createFiles(dir, files);
 431     }
 432 
 433     static void createConfigs(Path dir) throws IOException {
 434         List<String> files = Arrays.asList(
 435                 "first.cfg", "second.cfg", "third" + File.separator + "third.cfg");
 436         createFiles(dir, files);
 437     }
 438 
 439     static void createFiles(Path dir, List<String> filenames) throws IOException {
 440         for (String name : filenames) {
 441             Path file = dir.resolve(name);
 442             Files.createDirectories(file.getParent());
 443             Files.createFile(file);
 444             try (OutputStream os  = Files.newOutputStream(file)) {
 445                 os.write("blahblahblah".getBytes(UTF_8));
 446             }
 447         }
 448     }
 449 
 450     // Standalone entry point.
 451     public static void main(String[] args) throws Throwable {
 452         JmodTest test = new JmodTest();
 453         test.buildExplodedModules();
 454         for (Method m : JmodTest.class.getDeclaredMethods()) {
 455             if (m.getAnnotation(Test.class) != null) {
 456                 System.out.println("Invoking " + m.getName());
 457                 m.invoke(test);
 458             }
 459         }
 460     }
 461 }