1 /**
   2  * Copyright (c) 2015, 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 8142968 8166568 8166286 8170618 8168149
  27  * @summary Basic test for jmod
  28  * @library /lib/testlibrary
  29  * @modules jdk.compiler
  30  *          jdk.jlink
  31  * @build jdk.testlibrary.FileUtils CompilerUtils
  32  * @run testng/othervm -Djava.io.tmpdir=. JmodTest
  33  */
  34 
  35 import java.io.*;
  36 import java.lang.module.ModuleDescriptor;
  37 import java.lang.reflect.Method;
  38 import java.nio.file.*;
  39 import java.util.*;
  40 import java.util.function.Consumer;
  41 import java.util.regex.Pattern;
  42 import java.util.spi.ToolProvider;
  43 import java.util.stream.Collectors;
  44 import java.util.stream.Stream;
  45 import jdk.testlibrary.FileUtils;
  46 import org.testng.annotations.BeforeTest;
  47 import org.testng.annotations.Test;
  48 
  49 import static java.io.File.pathSeparator;
  50 import static java.lang.module.ModuleDescriptor.Version;
  51 import static java.nio.charset.StandardCharsets.UTF_8;
  52 import static java.util.stream.Collectors.toSet;
  53 import static org.testng.Assert.*;
  54 
  55 public class JmodTest {
  56 
  57     static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
  58         .orElseThrow(() ->
  59             new RuntimeException("jmod tool not found")
  60         );
  61 
  62     static final String TEST_SRC = System.getProperty("test.src", ".");
  63     static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
  64     static final Path EXPLODED_DIR = Paths.get("build");
  65     static final Path MODS_DIR = Paths.get("jmods");
  66 
  67     static final String CLASSES_PREFIX = "classes/";
  68     static final String CMDS_PREFIX = "bin/";
  69     static final String LIBS_PREFIX = "lib/";
  70     static final String CONFIGS_PREFIX = "conf/";
  71 
  72     @BeforeTest
  73     public void buildExplodedModules() throws IOException {
  74         if (Files.exists(EXPLODED_DIR))
  75             FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR);
  76 
  77         for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) {
  78             Path dir = EXPLODED_DIR.resolve(name);
  79             assertTrue(compileModule(name, dir.resolve("classes")));
  80             copyResource(SRC_DIR.resolve("foo"),
  81                          dir.resolve("classes"),
  82                          "jdk/test/foo/resources/foo.properties");
  83             createCmds(dir.resolve("bin"));
  84             createLibs(dir.resolve("lib"));
  85             createConfigs(dir.resolve("conf"));
  86         }
  87 
  88         if (Files.exists(MODS_DIR))
  89             FileUtils.deleteFileTreeWithRetry(MODS_DIR);
  90         Files.createDirectories(MODS_DIR);
  91     }
  92 
  93     // JDK-8166286 - jmod fails on symlink to directory
  94     @Test
  95     public void testSymlinks() throws IOException {
  96         Path apaDir = EXPLODED_DIR.resolve("apa");
  97         Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes");
  98         assertTrue(compileModule("apa", classesDir));
  99         Path libDir = apaDir.resolve("lib");
 100         createFiles(libDir, List.of("foo/bar/libfoo.so"));
 101         try {
 102             Path link = Files.createSymbolicLink(
 103                 libDir.resolve("baz"), libDir.resolve("foo").toAbsolutePath());
 104             assertTrue(Files.exists(link));
 105         } catch (UnsupportedOperationException uoe) {
 106             // OS does not support symlinks. Nothing to test!
 107             return;
 108         }
 109 
 110         Path jmod = MODS_DIR.resolve("apa.jmod");
 111         jmod("create",
 112              "--libs=", libDir.toString(),
 113              "--class-path", classesDir.toString(),
 114              jmod.toString())
 115             .assertSuccess();
 116     }
 117 
 118     // JDK-8170618 - jmod should validate if any exported or open package is missing
 119     @Test
 120     public void testMissingPackages() throws IOException {
 121         Path apaDir = EXPLODED_DIR.resolve("apa");
 122         Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes");
 123         if (Files.exists(classesDir))
 124             FileUtils.deleteFileTreeWithRetry(classesDir);
 125         assertTrue(compileModule("apa", classesDir));
 126         FileUtils.deleteFileTreeWithRetry(classesDir.resolve("jdk"));
 127         Path jmod = MODS_DIR.resolve("apa.jmod");
 128         jmod("create",
 129              "--class-path", classesDir.toString(),
 130              jmod.toString())
 131             .assertFailure()
 132             .resultChecker(r -> {
 133                 assertContains(r.output, "Packages that are exported or open in apa are not present: [jdk.test.apa]");
 134             });
 135         if (Files.exists(classesDir))
 136             FileUtils.deleteFileTreeWithRetry(classesDir);
 137     }
 138 
 139     @Test
 140     public void testList() throws IOException {
 141         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 142         jmod("create",
 143              "--class-path", cp,
 144              MODS_DIR.resolve("foo.jmod").toString())
 145             .assertSuccess();
 146 
 147         jmod("list",
 148              MODS_DIR.resolve("foo.jmod").toString())
 149             .assertSuccess()
 150             .resultChecker(r -> {
 151                 // asserts dependent on the exact contents of foo
 152                 assertContains(r.output, CLASSES_PREFIX + "module-info.class");
 153                 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/Foo.class");
 154                 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/internal/Message.class");
 155                 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties");
 156             });
 157     }
 158 
 159     @Test
 160     public void testExtractCWD() throws IOException {
 161         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 162         jmod("create",
 163              "--class-path", cp.toString(),
 164              MODS_DIR.resolve("fooExtractCWD.jmod").toString())
 165             .assertSuccess();
 166 
 167         jmod("extract",
 168              MODS_DIR.resolve("fooExtractCWD.jmod").toString())
 169             .assertSuccess()
 170             .resultChecker(r -> {
 171                 // module-info should exist, but jmod will have added its Packages attr.
 172                 assertTrue(Files.exists(Paths.get("classes/module-info.class")));
 173                 assertSameContent(cp.resolve("jdk/test/foo/Foo.class"),
 174                                   Paths.get("classes/jdk/test/foo/Foo.class"));
 175                 assertSameContent(cp.resolve("jdk/test/foo/internal/Message.class"),
 176                                   Paths.get("classes/jdk/test/foo/internal/Message.class"));
 177                 assertSameContent(cp.resolve("jdk/test/foo/resources/foo.properties"),
 178                                   Paths.get("classes/jdk/test/foo/resources/foo.properties"));
 179             });
 180     }
 181 
 182     @Test
 183     public void testExtractDir() throws IOException {
 184         if (Files.exists(Paths.get("extractTestDir")))
 185             FileUtils.deleteFileTreeWithRetry(Paths.get("extractTestDir"));
 186         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 187         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 188         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 189         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 190 
 191         jmod("create",
 192              "--conf", cf.toString(),
 193              "--cmds", bp.toString(),
 194              "--libs", lp.toString(),
 195              "--class-path", cp.toString(),
 196              MODS_DIR.resolve("fooExtractDir.jmod").toString())
 197             .assertSuccess();
 198 
 199         jmod("extract",
 200              "--dir", "extractTestDir",
 201              MODS_DIR.resolve("fooExtractDir.jmod").toString())
 202             .assertSuccess();
 203 
 204         jmod("extract",
 205              "--dir", "extractTestDir",
 206              MODS_DIR.resolve("fooExtractDir.jmod").toString())
 207             .assertSuccess()
 208             .resultChecker(r -> {
 209                 // check a sample of the extracted files
 210                 Path p = Paths.get("extractTestDir");
 211                 assertTrue(Files.exists(p.resolve("classes/module-info.class")));
 212                 assertSameContent(cp.resolve("jdk/test/foo/Foo.class"),
 213                                   p.resolve("classes/jdk/test/foo/Foo.class"));
 214                 assertSameContent(bp.resolve("first"),
 215                                   p.resolve(CMDS_PREFIX).resolve("first"));
 216                 assertSameContent(lp.resolve("first.so"),
 217                                   p.resolve(LIBS_PREFIX).resolve("second.so"));
 218                 assertSameContent(cf.resolve("second.cfg"),
 219                                   p.resolve(CONFIGS_PREFIX).resolve("second.cfg"));
 220             });
 221     }
 222 
 223     @Test
 224     public void testMainClass() throws IOException {
 225         Path jmod = MODS_DIR.resolve("fooMainClass.jmod");
 226         FileUtils.deleteFileIfExistsWithRetry(jmod);
 227         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 228 
 229         jmod("create",
 230              "--class-path", cp,
 231              "--main-class", "jdk.test.foo.Foo",
 232              jmod.toString())
 233             .assertSuccess()
 234             .resultChecker(r -> {
 235                 Optional<String> omc = getModuleDescriptor(jmod).mainClass();
 236                 assertTrue(omc.isPresent());
 237                 assertEquals(omc.get(), "jdk.test.foo.Foo");
 238             });
 239     }
 240 
 241     @Test
 242     public void testModuleVersion() throws IOException {
 243         Path jmod = MODS_DIR.resolve("fooVersion.jmod");
 244         FileUtils.deleteFileIfExistsWithRetry(jmod);
 245         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 246 
 247         jmod("create",
 248              "--class-path", cp,
 249              "--module-version", "5.4.3",
 250              jmod.toString())
 251             .assertSuccess()
 252             .resultChecker(r -> {
 253                 Optional<Version> ov = getModuleDescriptor(jmod).version();
 254                 assertTrue(ov.isPresent());
 255                 assertEquals(ov.get().toString(), "5.4.3");
 256             });
 257     }
 258 
 259     @Test
 260     public void testConfig() throws IOException {
 261         Path jmod = MODS_DIR.resolve("fooConfig.jmod");
 262         FileUtils.deleteFileIfExistsWithRetry(jmod);
 263         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 264         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 265 
 266         jmod("create",
 267              "--class-path", cp.toString(),
 268              "--config", cf.toString(),
 269              jmod.toString())
 270             .assertSuccess()
 271             .resultChecker(r -> {
 272                 try (Stream<String> s1 = findFiles(cf).map(p -> CONFIGS_PREFIX + p);
 273                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 274                     Set<String> expectedFilenames = Stream.concat(s1, s2)
 275                                                           .collect(toSet());
 276                     assertJmodContent(jmod, expectedFilenames);
 277                 }
 278             });
 279     }
 280 
 281     @Test
 282     public void testCmds() throws IOException {
 283         Path jmod = MODS_DIR.resolve("fooCmds.jmod");
 284         FileUtils.deleteFileIfExistsWithRetry(jmod);
 285         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 286         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 287 
 288         jmod("create",
 289              "--cmds", bp.toString(),
 290              "--class-path", cp.toString(),
 291              jmod.toString())
 292             .assertSuccess()
 293             .resultChecker(r -> {
 294                 try (Stream<String> s1 = findFiles(bp).map(p -> CMDS_PREFIX + p);
 295                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 296                     Set<String> expectedFilenames = Stream.concat(s1,s2)
 297                                                           .collect(toSet());
 298                     assertJmodContent(jmod, expectedFilenames);
 299                 }
 300             });
 301     }
 302 
 303     @Test
 304     public void testLibs() throws IOException {
 305         Path jmod = MODS_DIR.resolve("fooLibs.jmod");
 306         FileUtils.deleteFileIfExistsWithRetry(jmod);
 307         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 308         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 309 
 310         jmod("create",
 311              "--libs=", lp.toString(),
 312              "--class-path", cp.toString(),
 313              jmod.toString())
 314             .assertSuccess()
 315             .resultChecker(r -> {
 316                 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
 317                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) {
 318                     Set<String> expectedFilenames = Stream.concat(s1,s2)
 319                                                           .collect(toSet());
 320                     assertJmodContent(jmod, expectedFilenames);
 321                 }
 322             });
 323     }
 324 
 325     @Test
 326     public void testAll() throws IOException {
 327         Path jmod = MODS_DIR.resolve("fooAll.jmod");
 328         FileUtils.deleteFileIfExistsWithRetry(jmod);
 329         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 330         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 331         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 332         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 333 
 334         jmod("create",
 335              "--conf", cf.toString(),
 336              "--cmds=", bp.toString(),
 337              "--libs=", lp.toString(),
 338              "--class-path", cp.toString(),
 339              jmod.toString())
 340             .assertSuccess()
 341             .resultChecker(r -> {
 342                 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
 343                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p);
 344                      Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p);
 345                      Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) {
 346                     Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2),
 347                                                                   Stream.concat(s3, s4))
 348                                                           .collect(toSet());
 349                     assertJmodContent(jmod, expectedFilenames);
 350                 }
 351             });
 352     }
 353 
 354     @Test
 355     public void testExcludes() throws IOException {
 356         Path jmod = MODS_DIR.resolve("fooLibs.jmod");
 357         FileUtils.deleteFileIfExistsWithRetry(jmod);
 358         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 359         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 360 
 361         jmod("create",
 362              "--libs=", lp.toString(),
 363              "--class-path", cp.toString(),
 364              "--exclude", "**internal**",
 365              "--exclude", "first.so",
 366              jmod.toString())
 367              .assertSuccess()
 368              .resultChecker(r -> {
 369                  Set<String> expectedFilenames = new HashSet<>();
 370                  expectedFilenames.add(CLASSES_PREFIX + "module-info.class");
 371                  expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/Foo.class");
 372                  expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties");
 373                  expectedFilenames.add(LIBS_PREFIX + "second.so");
 374                  expectedFilenames.add(LIBS_PREFIX + "third/third.so");
 375                  assertJmodContent(jmod, expectedFilenames);
 376 
 377                  Set<String> unexpectedFilenames = new HashSet<>();
 378                  unexpectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class");
 379                  unexpectedFilenames.add(LIBS_PREFIX + "first.so");
 380                  assertJmodDoesNotContain(jmod, unexpectedFilenames);
 381              });
 382     }
 383 
 384     @Test
 385     public void describe() throws IOException {
 386         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 387         jmod("create",
 388              "--class-path", cp,
 389               MODS_DIR.resolve("describeFoo.jmod").toString())
 390              .assertSuccess();
 391 
 392         jmod("describe",
 393              MODS_DIR.resolve("describeFoo.jmod").toString())
 394              .assertSuccess()
 395              .resultChecker(r -> {
 396                  // Expect similar output: "foo,  requires mandated java.base
 397                  // exports jdk.test.foo,  contains jdk.test.foo.internal"
 398                  Pattern p = Pattern.compile("\\s+foo\\s+requires\\s+mandated\\s+java.base");
 399                  assertTrue(p.matcher(r.output).find(),
 400                            "Expecting to find \"foo, requires java.base\"" +
 401                                 "in output, but did not: [" + r.output + "]");
 402                  p = Pattern.compile(
 403                         "exports\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal");
 404                  assertTrue(p.matcher(r.output).find(),
 405                            "Expecting to find \"exports ..., contains ...\"" +
 406                                 "in output, but did not: [" + r.output + "]");
 407              });
 408     }
 409 
 410     @Test
 411     public void testDuplicateEntries() throws IOException {
 412         Path jmod = MODS_DIR.resolve("testDuplicates.jmod");
 413         FileUtils.deleteFileIfExistsWithRetry(jmod);
 414         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 415         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 416 
 417         jmod("create",
 418              "--class-path", cp + pathSeparator + cp,
 419              jmod.toString())
 420              .assertSuccess()
 421              .resultChecker(r ->
 422                  assertContains(r.output, "Warning: ignoring duplicate entry")
 423              );
 424 
 425         FileUtils.deleteFileIfExistsWithRetry(jmod);
 426         jmod("create",
 427              "--class-path", cp,
 428              "--libs", lp.toString() + pathSeparator + lp.toString(),
 429              jmod.toString())
 430              .assertSuccess()
 431              .resultChecker(r ->
 432                  assertContains(r.output, "Warning: ignoring duplicate entry")
 433              );
 434     }
 435 
 436     @Test
 437     public void testIgnoreModuleInfoInOtherSections() throws IOException {
 438         Path jmod = MODS_DIR.resolve("testIgnoreModuleInfoInOtherSections.jmod");
 439         FileUtils.deleteFileIfExistsWithRetry(jmod);
 440         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 441 
 442         jmod("create",
 443             "--class-path", cp,
 444             "--libs", cp,
 445             jmod.toString())
 446             .assertSuccess()
 447             .resultChecker(r ->
 448                 assertContains(r.output, "Warning: ignoring entry")
 449             );
 450 
 451         FileUtils.deleteFileIfExistsWithRetry(jmod);
 452         jmod("create",
 453              "--class-path", cp,
 454              "--cmds", cp,
 455              jmod.toString())
 456              .assertSuccess()
 457              .resultChecker(r ->
 458                  assertContains(r.output, "Warning: ignoring entry")
 459              );
 460     }
 461 
 462     @Test
 463     public void testLastOneWins() throws IOException {
 464         Path workDir = Paths.get("lastOneWins");
 465         if (Files.exists(workDir))
 466             FileUtils.deleteFileTreeWithRetry(workDir);
 467         Files.createDirectory(workDir);
 468         Path jmod = MODS_DIR.resolve("lastOneWins.jmod");
 469         FileUtils.deleteFileIfExistsWithRetry(jmod);
 470         Path cp = EXPLODED_DIR.resolve("foo").resolve("classes");
 471         Path bp = EXPLODED_DIR.resolve("foo").resolve("bin");
 472         Path lp = EXPLODED_DIR.resolve("foo").resolve("lib");
 473         Path cf = EXPLODED_DIR.resolve("foo").resolve("conf");
 474 
 475         Path shouldNotBeAdded = workDir.resolve("shouldNotBeAdded");
 476         Files.createDirectory(shouldNotBeAdded);
 477         Files.write(shouldNotBeAdded.resolve("aFile"), "hello".getBytes(UTF_8));
 478 
 479         // Pairs of options. For options with required arguments the last one
 480         // should win ( first should be effectively ignored, but may still be
 481         // validated ).
 482         jmod("create",
 483              "--conf", shouldNotBeAdded.toString(),
 484              "--conf", cf.toString(),
 485              "--cmds", shouldNotBeAdded.toString(),
 486              "--cmds", bp.toString(),
 487              "--libs", shouldNotBeAdded.toString(),
 488              "--libs", lp.toString(),
 489              "--class-path", shouldNotBeAdded.toString(),
 490              "--class-path", cp.toString(),
 491              "--main-class", "does.NotExist",
 492              "--main-class", "jdk.test.foo.Foo",
 493              "--module-version", "00001",
 494              "--module-version", "5.4.3",
 495              "--do-not-resolve-by-default",
 496              "--do-not-resolve-by-default",
 497              "--warn-if-resolved=incubating",
 498              "--warn-if-resolved=deprecated",
 499              MODS_DIR.resolve("lastOneWins.jmod").toString())
 500             .assertSuccess()
 501             .resultChecker(r -> {
 502                 ModuleDescriptor md = getModuleDescriptor(jmod);
 503                 Optional<String> omc = md.mainClass();
 504                 assertTrue(omc.isPresent());
 505                 assertEquals(omc.get(), "jdk.test.foo.Foo");
 506                 Optional<Version> ov = md.version();
 507                 assertTrue(ov.isPresent());
 508                 assertEquals(ov.get().toString(), "5.4.3");
 509 
 510                 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p);
 511                      Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p);
 512                      Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p);
 513                      Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) {
 514                     Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2),
 515                                                                   Stream.concat(s3, s4))
 516                                                           .collect(toSet());
 517                     assertJmodContent(jmod, expectedFilenames);
 518                 }
 519             });
 520 
 521         jmod("extract",
 522              "--dir", "blah",
 523              "--dir", "lastOneWinsExtractDir",
 524              jmod.toString())
 525             .assertSuccess()
 526             .resultChecker(r -> {
 527                 assertTrue(Files.exists(Paths.get("lastOneWinsExtractDir")));
 528                 assertTrue(Files.notExists(Paths.get("blah")));
 529             });
 530     }
 531 
 532     @Test
 533     public void testPackagesAttribute() throws IOException {
 534         Path jmod = MODS_DIR.resolve("foo.jmod");
 535         FileUtils.deleteFileIfExistsWithRetry(jmod);
 536         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 537 
 538         Set<String> expectedPackages = Set.of("jdk.test.foo",
 539                                               "jdk.test.foo.internal",
 540                                               "jdk.test.foo.resources");
 541 
 542         jmod("create",
 543              "--class-path", cp,
 544              jmod.toString())
 545              .assertSuccess()
 546              .resultChecker(r -> {
 547                  Set<String> pkgs = getModuleDescriptor(jmod).packages();
 548                  assertEquals(pkgs, expectedPackages);
 549              });
 550         }
 551 
 552     @Test
 553     public void testVersion() {
 554         jmod("--version")
 555             .assertSuccess()
 556             .resultChecker(r -> {
 557                 assertContains(r.output, System.getProperty("java.version"));
 558             });
 559     }
 560 
 561     @Test
 562     public void testHelp() {
 563         jmod("--help")
 564             .assertSuccess()
 565             .resultChecker(r -> {
 566                 assertTrue(r.output.startsWith("Usage: jmod"), "Help not printed");
 567                 assertFalse(r.output.contains("--do-not-resolve-by-default"));
 568                 assertFalse(r.output.contains("--warn-if-resolved"));
 569             });
 570     }
 571 
 572     @Test
 573     public void testHelpExtra() {
 574         jmod("--help-extra")
 575             .assertSuccess()
 576             .resultChecker(r -> {
 577                 assertTrue(r.output.startsWith("Usage: jmod"), "Extra help not printed");
 578                 assertContains(r.output, "--do-not-resolve-by-default");
 579                 assertContains(r.output, "--warn-if-resolved");
 580             });
 581     }
 582 
 583     @Test
 584     public void testTmpFileRemoved() throws IOException {
 585         // Implementation detail: jmod tool creates <jmod-file>.tmp
 586         // Ensure that it is removed in the event of a failure.
 587         // The failure in this case is a class in the unnamed package.
 588 
 589         Path jmod = MODS_DIR.resolve("testTmpFileRemoved.jmod");
 590         Path tmp = MODS_DIR.resolve(".testTmpFileRemoved.jmod.tmp");
 591         FileUtils.deleteFileIfExistsWithRetry(jmod);
 592         FileUtils.deleteFileIfExistsWithRetry(tmp);
 593         String cp = EXPLODED_DIR.resolve("foo").resolve("classes") + File.pathSeparator +
 594                     EXPLODED_DIR.resolve("foo").resolve("classes")
 595                                 .resolve("jdk").resolve("test").resolve("foo").toString();
 596 
 597         jmod("create",
 598              "--class-path", cp,
 599              jmod.toString())
 600             .assertFailure()
 601             .resultChecker(r -> {
 602                 assertContains(r.output, "unnamed package");
 603                 assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp);
 604             });
 605     }
 606 
 607     // ---
 608 
 609     static boolean compileModule(String name, Path dest) throws IOException {
 610         return CompilerUtils.compile(SRC_DIR.resolve(name), dest);
 611     }
 612 
 613     static void assertContains(String output, String subString) {
 614         if (output.contains(subString))
 615             assertTrue(true);
 616         else
 617             assertTrue(false,"Expected to find [" + subString + "], in output ["
 618                            + output + "]" + "\n");
 619     }
 620 
 621     static ModuleDescriptor getModuleDescriptor(Path jmod) {
 622         ClassLoader cl = ClassLoader.getSystemClassLoader();
 623         try (FileSystem fs = FileSystems.newFileSystem(jmod, cl)) {
 624             String p = "/classes/module-info.class";
 625             try (InputStream is = Files.newInputStream(fs.getPath(p))) {
 626                 return ModuleDescriptor.read(is);
 627             }
 628         } catch (IOException ioe) {
 629             throw new UncheckedIOException(ioe);
 630         }
 631     }
 632 
 633     static Stream<String> findFiles(Path dir) {
 634         try {
 635             return Files.find(dir, Integer.MAX_VALUE, (p, a) -> a.isRegularFile())
 636                         .map(dir::relativize)
 637                         .map(Path::toString)
 638                         .map(p -> p.replace(File.separator, "/"));
 639         } catch (IOException x) {
 640             throw new UncheckedIOException(x);
 641         }
 642     }
 643 
 644     static Set<String> getJmodContent(Path jmod) {
 645         JmodResult r = jmod("list", jmod.toString()).assertSuccess();
 646         return Stream.of(r.output.split("\r?\n")).collect(toSet());
 647     }
 648 
 649     static void assertJmodContent(Path jmod, Set<String> expected) {
 650         Set<String> actual = getJmodContent(jmod);
 651         if (!Objects.equals(actual, expected)) {
 652             Set<String> unexpected = new HashSet<>(actual);
 653             unexpected.removeAll(expected);
 654             Set<String> notFound = new HashSet<>(expected);
 655             notFound.removeAll(actual);
 656             StringBuilder sb = new StringBuilder();
 657             sb.append("Unexpected but found:\n");
 658             unexpected.forEach(s -> sb.append("\t" + s + "\n"));
 659             sb.append("Expected but not found:\n");
 660             notFound.forEach(s -> sb.append("\t" + s + "\n"));
 661             assertTrue(false, "Jmod content check failed.\n" + sb.toString());
 662         }
 663     }
 664 
 665     static void assertJmodDoesNotContain(Path jmod, Set<String> unexpectedNames) {
 666         Set<String> actual = getJmodContent(jmod);
 667         Set<String> unexpected = new HashSet<>();
 668         for (String name : unexpectedNames) {
 669             if (actual.contains(name))
 670                 unexpected.add(name);
 671         }
 672         if (!unexpected.isEmpty()) {
 673             StringBuilder sb = new StringBuilder();
 674             for (String s : unexpected)
 675                 sb.append("Unexpected but found: " + s + "\n");
 676             sb.append("In :");
 677             for (String s : actual)
 678                 sb.append("\t" + s + "\n");
 679             assertTrue(false, "Jmod content check failed.\n" + sb.toString());
 680         }
 681     }
 682 
 683     static void assertSameContent(Path p1, Path p2) {
 684         try {
 685             byte[] ba1 = Files.readAllBytes(p1);
 686             byte[] ba2 = Files.readAllBytes(p2);
 687             assertEquals(ba1, ba2);
 688         } catch (IOException x) {
 689             throw new UncheckedIOException(x);
 690         }
 691     }
 692 
 693     static JmodResult jmod(String... args) {
 694         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 695         PrintStream ps = new PrintStream(baos);
 696         System.out.println("jmod " + Arrays.asList(args));
 697         int ec = JMOD_TOOL.run(ps, ps, args);
 698         return new JmodResult(ec, new String(baos.toByteArray(), UTF_8));
 699     }
 700 
 701     static class JmodResult {
 702         final int exitCode;
 703         final String output;
 704 
 705         JmodResult(int exitValue, String output) {
 706             this.exitCode = exitValue;
 707             this.output = output;
 708         }
 709         JmodResult assertSuccess() { assertTrue(exitCode == 0, output); return this; }
 710         JmodResult assertFailure() { assertTrue(exitCode != 0, output); return this; }
 711         JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; }
 712     }
 713 
 714     static void createCmds(Path dir) throws IOException {
 715         List<String> files = Arrays.asList(
 716                 "first", "second", "third" + File.separator + "third");
 717         createFiles(dir, files);
 718     }
 719 
 720     static void createLibs(Path dir) throws IOException {
 721         List<String> files = Arrays.asList(
 722                 "first.so", "second.so", "third" + File.separator + "third.so");
 723         createFiles(dir, files);
 724     }
 725 
 726     static void createConfigs(Path dir) throws IOException {
 727         List<String> files = Arrays.asList(
 728                 "first.cfg", "second.cfg", "third" + File.separator + "third.cfg");
 729         createFiles(dir, files);
 730     }
 731 
 732     static void createFiles(Path dir, List<String> filenames) throws IOException {
 733         for (String name : filenames) {
 734             Path file = dir.resolve(name);
 735             Files.createDirectories(file.getParent());
 736             Files.createFile(file);
 737             try (OutputStream os  = Files.newOutputStream(file)) {
 738                 os.write("blahblahblah".getBytes(UTF_8));
 739             }
 740         }
 741     }
 742 
 743     static void copyResource(Path srcDir, Path dir, String resource) throws IOException {
 744         Path dest = dir.resolve(resource);
 745         Files.deleteIfExists(dest);
 746 
 747         Files.createDirectories(dest.getParent());
 748         Files.copy(srcDir.resolve(resource), dest);
 749     }
 750 
 751     // Standalone entry point.
 752     public static void main(String[] args) throws Throwable {
 753         JmodTest test = new JmodTest();
 754         test.buildExplodedModules();
 755         for (Method m : JmodTest.class.getDeclaredMethods()) {
 756             if (m.getAnnotation(Test.class) != null) {
 757                 System.out.println("Invoking " + m.getName());
 758                 m.invoke(test);
 759             }
 760         }
 761     }
 762 }