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