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