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