1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @library /lib/testlibrary
  27  * @build AutomaticModulesTest ModuleUtils JarUtils
  28  * @run testng AutomaticModulesTest
  29  * @summary Basic tests for automatic modules
  30  */
  31 
  32 import java.io.IOException;
  33 import java.lang.module.Configuration;
  34 import java.lang.module.FindException;
  35 import java.lang.module.ModuleDescriptor;
  36 import java.lang.module.ModuleDescriptor.Requires.Modifier;
  37 import java.lang.module.ModuleFinder;
  38 import java.lang.module.ModuleReference;
  39 import java.lang.module.ResolutionException;
  40 import java.lang.module.ResolvedModule;
  41 import java.lang.reflect.Layer;
  42 import java.lang.reflect.Module;
  43 import java.nio.file.Files;
  44 import java.nio.file.Path;
  45 import java.nio.file.Paths;
  46 import java.util.Optional;
  47 import java.util.Set;
  48 import java.util.jar.Attributes;
  49 import java.util.jar.Manifest;
  50 import java.util.stream.Collectors;
  51 import java.util.stream.Stream;
  52 
  53 import org.testng.annotations.DataProvider;
  54 import org.testng.annotations.Test;
  55 import static org.testng.Assert.*;
  56 
  57 @Test
  58 public class AutomaticModulesTest {
  59 
  60     private static final Path USER_DIR
  61          = Paths.get(System.getProperty("user.dir"));
  62 
  63     @DataProvider(name = "names")
  64     public Object[][] createNames() {
  65         return new Object[][] {
  66 
  67             // JAR file name                module-name[/version]
  68 
  69             { "foo.jar",                    "foo" },
  70             { "foo4j.jar",                  "foo4j", },
  71 
  72             { "foo1.jar",                   "foo" },
  73             { "foo1.2.jar",                 "foo" },
  74             { "foo1.2.3.jar",               "foo" },
  75 
  76             { "foo10.jar",                  "foo" },
  77             { "foo10.20.jar",               "foo" },
  78             { "foo10.20.30.jar",            "foo" },
  79 
  80             { "foo-1.jar",                  "foo/1" },
  81             { "foo-1.2.jar",                "foo/1.2" },
  82             { "foo-1.2.3.jar",              "foo/1.2.3" },
  83             { "foo-1.2.3.4.jar",            "foo/1.2.3.4" },
  84 
  85             { "foo-10.jar",                 "foo/10" },
  86             { "foo-10.20.jar",              "foo/10.20" },
  87             { "foo-10.20.30.jar",           "foo/10.20.30" },
  88             { "foo-10.20.30.40.jar",        "foo/10.20.30.40" },
  89 
  90             { "foo-bar.jar",                "foo.bar" },
  91             { "foo-bar-1.jar",              "foo.bar/1" },
  92             { "foo-bar-1.2.jar",            "foo.bar/1.2"},
  93             { "foo-bar-10.jar",             "foo.bar/10" },
  94             { "foo-bar-10.20.jar",          "foo.bar/10.20" },
  95 
  96             { "foo-1.2-SNAPSHOT.jar",       "foo/1.2-SNAPSHOT" },
  97             { "foo-bar-1.2-SNAPSHOT.jar",   "foo.bar/1.2-SNAPSHOT" },
  98 
  99             { "foo--bar-1.0.jar",           "foo.bar/1.0" },
 100             { "-foo-bar-1.0.jar",           "foo.bar/1.0" },
 101             { "foo-bar--1.0.jar",           "foo.bar/1.0" },
 102 
 103         };
 104     }
 105 
 106     // JAR file names that do not map to a legal module name
 107     @DataProvider(name = "badnames")
 108     public Object[][] createBadNames() {
 109         return new Object[][]{
 110 
 111             { ".jar",     null },
 112             { "_.jar",    null }
 113 
 114         };
 115     }
 116 
 117     /**
 118      * Test mapping of JAR file names to module names
 119      */
 120     @Test(dataProvider = "names")
 121     public void testNames(String fn, String mid) throws IOException {
 122         String[] s = mid.split("/");
 123         String mn = s[0];
 124         String vs = (s.length == 2) ? s[1] : null;
 125 
 126         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 127         Path jf = dir.resolve(fn);
 128 
 129         // create empty JAR file
 130         createDummyJarFile(jf);
 131 
 132         // create a ModuleFinder to find modules in the directory
 133         ModuleFinder finder = ModuleFinder.of(dir);
 134 
 135         // a module with the expected name should be found
 136         Optional<ModuleReference> mref = finder.find(mn);
 137         assertTrue(mref.isPresent(), mn + " not found");
 138 
 139         ModuleDescriptor descriptor = mref.get().descriptor();
 140         assertTrue(descriptor.isAutomatic());
 141         assertEquals(descriptor.name(), mn);
 142         if (vs == null) {
 143             assertFalse(descriptor.version().isPresent());
 144         } else {
 145             assertEquals(descriptor.version().get().toString(), vs);
 146         }
 147     }
 148 
 149 
 150     /**
 151      * Test impossible mapping of JAR files to modules names
 152      */
 153     @Test(dataProvider = "badnames", expectedExceptions = FindException.class)
 154     public void testBadNames(String fn, String ignore) throws IOException {
 155         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 156         Path jf = dir.resolve(fn);
 157 
 158         // create empty JAR file
 159         createDummyJarFile(jf);
 160 
 161         // should throw FindException
 162         ModuleFinder.of(dir).findAll();
 163     }
 164 
 165 
 166     /**
 167      * Test all packages are exported
 168      */
 169     public void testPackages() throws IOException {
 170         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 171         createDummyJarFile(dir.resolve("m.jar"),
 172                            "p/C1.class", "p/C2.class", "q/C1.class");
 173 
 174         ModuleFinder finder = ModuleFinder.of(dir);
 175         Optional<ModuleReference> mref = finder.find("m");
 176         assertTrue(mref.isPresent(), "m not found");
 177 
 178         ModuleDescriptor descriptor = mref.get().descriptor();
 179         assertTrue(descriptor.isAutomatic());
 180 
 181         assertTrue(descriptor.packages().size() == 2);
 182         assertTrue(descriptor.packages().contains("p"));
 183         assertTrue(descriptor.packages().contains("q"));
 184 
 185         assertTrue(descriptor.exports().isEmpty());
 186         assertTrue(descriptor.opens().isEmpty());
 187     }
 188 
 189     /**
 190      * Test class files in JAR file where the entry does not correspond to a
 191      * legal package name.
 192      */
 193     public void testBadPackage() throws IOException {
 194         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 195         createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
 196 
 197         ModuleFinder finder = ModuleFinder.of(dir);
 198         Optional<ModuleReference> mref = finder.find("m");
 199         assertTrue(mref.isPresent(), "m not found");
 200 
 201         ModuleDescriptor descriptor = mref.get().descriptor();
 202         assertTrue(descriptor.isAutomatic());
 203 
 204         assertTrue(descriptor.packages().size() == 1);
 205         assertTrue(descriptor.packages().contains("p"));
 206 
 207         assertTrue(descriptor.exports().isEmpty());
 208         assertTrue(descriptor.opens().isEmpty());
 209     }
 210 
 211     /**
 212      * Test non-class resources in a JAR file.
 213      */
 214     public void testNonClassResources() throws IOException {
 215         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 216         createDummyJarFile(dir.resolve("m.jar"),
 217                 "LICENSE",
 218                 "README",
 219                 "WEB-INF/tags",
 220                 "p/Type.class",
 221                 "p/resources/m.properties");
 222 
 223         ModuleFinder finder = ModuleFinder.of(dir);
 224         Optional<ModuleReference> mref = finder.find("m");
 225         assertTrue(mref.isPresent(), "m not found");
 226 
 227         ModuleDescriptor descriptor = mref.get().descriptor();
 228         assertTrue(descriptor.isAutomatic());
 229 
 230         assertTrue(descriptor.packages().size() == 1);
 231         assertTrue(descriptor.packages().contains("p"));
 232     }
 233 
 234     /**
 235      * Test .class file in unnamed package (top-level directory)
 236      */
 237     @Test(expectedExceptions = FindException.class)
 238     public void testClassInUnnamedPackage() throws IOException {
 239         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 240         createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
 241         ModuleFinder finder = ModuleFinder.of(dir);
 242         finder.findAll();
 243     }
 244 
 245     /**
 246      * Test JAR file with META-INF/services configuration file
 247      */
 248     public void testServicesConfiguration() throws IOException {
 249         String service = "p.S";
 250         String provider = "p.S1";
 251 
 252         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 253 
 254         // provider class
 255         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
 256         Files.createDirectories(providerClass.getParent());
 257         Files.createFile(providerClass);
 258 
 259         // services configuration file
 260         Path services = tmpdir.resolve("META-INF").resolve("services");
 261         Files.createDirectories(services);
 262         Files.write(services.resolve(service), Set.of(provider));
 263 
 264         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 265         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 266 
 267         ModuleFinder finder = ModuleFinder.of(dir);
 268 
 269         Optional<ModuleReference> mref = finder.find("m");
 270         assertTrue(mref.isPresent(), "m not found");
 271 
 272         ModuleDescriptor descriptor = mref.get().descriptor();
 273         assertTrue(descriptor.provides().size() == 1);
 274         ModuleDescriptor.Provides provides = descriptor.provides().iterator().next();
 275         assertEquals(provides.service(), service);
 276         assertTrue(provides.providers().size() == 1);
 277         assertTrue(provides.providers().contains((provider)));
 278     }
 279 
 280 
 281     // META-INF/services files that don't map to legal service names
 282     @DataProvider(name = "badservices")
 283     public Object[][] createBadServices() {
 284         return new Object[][] {
 285 
 286                 // service type         provider type
 287                 { "-",                  "p.S1" },
 288                 { ".S",                 "p.S1" },
 289         };
 290     }
 291 
 292     /**
 293      * Test JAR file with META-INF/services configuration file with bad
 294      * values or names.
 295      */
 296     @Test(dataProvider = "badservices")
 297     public void testBadServicesNames(String service, String provider)
 298         throws IOException
 299     {
 300         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 301         Path services = tmpdir.resolve("META-INF").resolve("services");
 302         Files.createDirectories(services);
 303         Files.write(services.resolve(service), Set.of(provider));
 304         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 305         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 306 
 307         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
 308         assertTrue(omref.isPresent());
 309         ModuleDescriptor descriptor = omref.get().descriptor();
 310         assertTrue(descriptor.provides().isEmpty());
 311     }
 312 
 313 
 314     // META-INF/services configuration file entries that are not legal
 315     @DataProvider(name = "badproviders")
 316     public Object[][] createBadProviders() {
 317         return new Object[][] {
 318 
 319                 // service type         provider type
 320                 { "p.S",                "-" },
 321                 { "p.S",                "p..S1" },
 322                 { "p.S",                "S1." },
 323         };
 324     }
 325 
 326     /**
 327      * Test JAR file with META-INF/services configuration file with bad
 328      * values or names.
 329      */
 330     @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
 331     public void testBadProviderNames(String service, String provider)
 332         throws IOException
 333     {
 334         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 335 
 336         // provider class
 337         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
 338         Files.createDirectories(providerClass.getParent());
 339         Files.createFile(providerClass);
 340 
 341         // services configuration file
 342         Path services = tmpdir.resolve("META-INF").resolve("services");
 343         Files.createDirectories(services);
 344         Files.write(services.resolve(service), Set.of(provider));
 345 
 346         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 347         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 348 
 349         // should throw FindException
 350         ModuleFinder.of(dir).findAll();
 351     }
 352 
 353     /**
 354      * Test JAR file with META-INF/services configuration file listing a
 355      * provider that is not in the module.
 356      */
 357     @Test(expectedExceptions = FindException.class)
 358     public void testMissingProviderPackage() throws IOException {
 359         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 360 
 361         // services configuration file
 362         Path services = tmpdir.resolve("META-INF").resolve("services");
 363         Files.createDirectories(services);
 364         Files.write(services.resolve("p.S"), Set.of("q.P"));
 365 
 366         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 367         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 368 
 369         // should throw FindException
 370         ModuleFinder.of(dir).findAll();
 371     }
 372 
 373 
 374     /**
 375      * Test that a JAR file with a Main-Class attribute results
 376      * in a module with a main class.
 377      */
 378     public void testMainClass() throws IOException {
 379         String mainClass = "p.Main";
 380 
 381         Manifest man = new Manifest();
 382         Attributes attrs = man.getMainAttributes();
 383         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 384         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 385 
 386         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 387         String entry = mainClass.replace('.', '/') + ".class";
 388         createDummyJarFile(dir.resolve("m.jar"), man, entry);
 389 
 390         ModuleFinder finder = ModuleFinder.of(dir);
 391 
 392         Configuration parent = Layer.boot().configuration();
 393         Configuration cf = resolve(parent, finder, "m");
 394 
 395         ModuleDescriptor descriptor = findDescriptor(cf, "m");
 396 
 397         assertTrue(descriptor.mainClass().isPresent());
 398         assertEquals(descriptor.mainClass().get(), mainClass);
 399     }
 400 
 401 
 402     // Main-Class files that do not map to a legal qualified type name
 403     @DataProvider(name = "badmainclass")
 404     public Object[][] createBadMainClass() {
 405         return new Object[][]{
 406 
 407             { "Main",        null },
 408             { "p..Main",     null },
 409             { "p-.Main",     null },
 410 
 411         };
 412     }
 413 
 414     /**
 415      * Test that a JAR file with a Main-Class attribute that is not a qualified
 416      * type name.
 417      */
 418     @Test(dataProvider = "badmainclass", expectedExceptions = FindException.class)
 419     public void testBadMainClass(String mainClass, String ignore) throws IOException {
 420         Manifest man = new Manifest();
 421         Attributes attrs = man.getMainAttributes();
 422         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 423         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 424 
 425         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 426         String entry = mainClass.replace('.', '/') + ".class";
 427         createDummyJarFile(dir.resolve("m.jar"), man, entry);
 428 
 429         // should throw FindException
 430         ModuleFinder.of(dir).findAll();
 431     }
 432 
 433     /**
 434      * Test that a JAR file with a Main-Class attribute that is not in the module
 435      */
 436     @Test(expectedExceptions = FindException.class)
 437     public void testMissingMainClassPackage() throws IOException {
 438         Manifest man = new Manifest();
 439         Attributes attrs = man.getMainAttributes();
 440         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 441         attrs.put(Attributes.Name.MAIN_CLASS, "p.Main");
 442 
 443         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 444         createDummyJarFile(dir.resolve("m.jar"), man);
 445 
 446         // should throw FindException
 447         ModuleFinder.of(dir).findAll();
 448     }
 449 
 450 
 451     /**
 452      * Basic test of a configuration created with automatic modules.
 453      *   a requires b*
 454      *   a requires c*
 455      *   b*
 456      *   c*
 457      */
 458     public void testConfiguration1() throws Exception {
 459         ModuleDescriptor descriptor1
 460             = ModuleDescriptor.newModule("a")
 461                 .requires("b")
 462                 .requires("c")
 463                 .requires("java.base")
 464                 .build();
 465 
 466         // b and c are automatic modules
 467         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 468         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 469         createDummyJarFile(dir.resolve("c.jar"), "q/T.class");
 470 
 471         // module finder locates a and the modules in the directory
 472         ModuleFinder finder
 473             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1),
 474                 ModuleFinder.of(dir));
 475 
 476         Configuration parent = Layer.boot().configuration();
 477         Configuration cf = resolve(parent, finder, "a");
 478 
 479         assertTrue(cf.modules().size() == 3);
 480         assertTrue(cf.findModule("a").isPresent());
 481         assertTrue(cf.findModule("b").isPresent());
 482         assertTrue(cf.findModule("c").isPresent());
 483 
 484         ResolvedModule base = cf.findModule("java.base").get();
 485         assertTrue(base.configuration() == Layer.boot().configuration());
 486         ResolvedModule a = cf.findModule("a").get();
 487         ResolvedModule b = cf.findModule("b").get();
 488         ResolvedModule c = cf.findModule("c").get();
 489 
 490         // b && c only require java.base
 491         assertTrue(b.reference().descriptor().requires().size() == 1);
 492         assertTrue(c.reference().descriptor().requires().size() == 1);
 493 
 494         // readability
 495 
 496         assertTrue(a.reads().size() == 3);
 497         assertTrue(a.reads().contains(base));
 498         assertTrue(a.reads().contains(b));
 499         assertTrue(a.reads().contains(c));
 500 
 501         assertTrue(b.reads().contains(a));
 502         assertTrue(b.reads().contains(c));
 503         testReadAllBootModules(cf, "b");  // b reads all modules in boot layer
 504 
 505         assertTrue(c.reads().contains(a));
 506         assertTrue(c.reads().contains(b));
 507         testReadAllBootModules(cf, "c");  // c reads all modules in boot layer
 508 
 509     }
 510 
 511     /**
 512      * Basic test of a configuration created with automatic modules
 513      *   a requires b
 514      *   b requires c*
 515      *   c*
 516      *   d*
 517      */
 518     public void testInConfiguration2() throws IOException {
 519         ModuleDescriptor descriptor1
 520             = ModuleDescriptor.newModule("a")
 521                 .requires("b")
 522                 .requires("java.base")
 523                 .build();
 524 
 525         ModuleDescriptor descriptor2
 526             = ModuleDescriptor.newModule("b")
 527                 .requires("c")
 528                 .requires("java.base")
 529                 .build();
 530 
 531         // c and d are automatic modules
 532         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 533         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 534         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 535 
 536         // module finder locates a and the modules in the directory
 537         ModuleFinder finder
 538             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
 539                                    ModuleFinder.of(dir));
 540 
 541         Configuration parent = Layer.boot().configuration();
 542         Configuration cf = resolve(parent, finder, "a", "d");
 543 
 544         assertTrue(cf.modules().size() == 4);
 545         assertTrue(cf.findModule("a").isPresent());
 546         assertTrue(cf.findModule("b").isPresent());
 547         assertTrue(cf.findModule("c").isPresent());
 548         assertTrue(cf.findModule("d").isPresent());
 549 
 550         // c && d should only require java.base
 551         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 552         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 553 
 554         // readability
 555 
 556         ResolvedModule base = cf.findModule("java.base").get();
 557         assertTrue(base.configuration() == Layer.boot().configuration());
 558         ResolvedModule a = cf.findModule("a").get();
 559         ResolvedModule b = cf.findModule("b").get();
 560         ResolvedModule c = cf.findModule("c").get();
 561         ResolvedModule d = cf.findModule("d").get();
 562 
 563         assertTrue(a.reads().size() == 2);
 564         assertTrue(a.reads().contains(b));
 565         assertTrue(a.reads().contains(base));
 566 
 567         assertTrue(b.reads().size() == 3);
 568         assertTrue(b.reads().contains(c));
 569         assertTrue(b.reads().contains(d));
 570         assertTrue(b.reads().contains(base));
 571 
 572         assertTrue(c.reads().contains(a));
 573         assertTrue(c.reads().contains(b));
 574         assertTrue(c.reads().contains(d));
 575         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 576 
 577         assertTrue(d.reads().contains(a));
 578         assertTrue(d.reads().contains(b));
 579         assertTrue(d.reads().contains(c));
 580         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 581     }
 582 
 583 
 584     /**
 585      * Basic test of a configuration created with automatic modules
 586      *   a requires b
 587      *   b requires transitive c*
 588      *   c*
 589      *   d*
 590      */
 591     public void testInConfiguration3() throws IOException {
 592         ModuleDescriptor descriptor1
 593             = ModuleDescriptor.newModule("a")
 594                 .requires("b")
 595                 .requires("java.base")
 596                 .build();
 597 
 598         ModuleDescriptor descriptor2
 599             = ModuleDescriptor.newModule("b")
 600                 .requires(Set.of(Modifier.TRANSITIVE), "c")
 601                 .requires("java.base")
 602                 .build();
 603 
 604         // c and d are automatic modules
 605         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 606         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 607         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 608 
 609         // module finder locates a and the modules in the directory
 610         ModuleFinder finder
 611             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor1, descriptor2),
 612                 ModuleFinder.of(dir));
 613 
 614         Configuration parent = Layer.boot().configuration();
 615         Configuration cf = resolve(parent, finder, "a", "d");
 616 
 617         assertTrue(cf.modules().size() == 4);
 618         assertTrue(cf.findModule("a").isPresent());
 619         assertTrue(cf.findModule("b").isPresent());
 620         assertTrue(cf.findModule("c").isPresent());
 621         assertTrue(cf.findModule("d").isPresent());
 622 
 623         ResolvedModule base = cf.findModule("java.base").get();
 624         assertTrue(base.configuration() == Layer.boot().configuration());
 625         ResolvedModule a = cf.findModule("a").get();
 626         ResolvedModule b = cf.findModule("b").get();
 627         ResolvedModule c = cf.findModule("c").get();
 628         ResolvedModule d = cf.findModule("d").get();
 629 
 630         // c && d should only require java.base
 631         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 632         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 633 
 634         // readability
 635 
 636         assertTrue(a.reads().size() == 4);
 637         assertTrue(a.reads().contains(b));
 638         assertTrue(a.reads().contains(c));
 639         assertTrue(a.reads().contains(d));
 640         assertTrue(a.reads().contains(base));
 641 
 642         assertTrue(b.reads().size() == 3);
 643         assertTrue(b.reads().contains(c));
 644         assertTrue(b.reads().contains(d));
 645         assertTrue(b.reads().contains(base));
 646 
 647         assertTrue(reads(cf, "b", "c"));
 648         assertTrue(reads(cf, "b", "d"));
 649         assertTrue(reads(cf, "b", "java.base"));
 650 
 651         assertTrue(c.reads().contains(a));
 652         assertTrue(c.reads().contains(b));
 653         assertTrue(c.reads().contains(d));
 654         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 655 
 656         assertTrue(d.reads().contains(a));
 657         assertTrue(d.reads().contains(b));
 658         assertTrue(d.reads().contains(c));
 659         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 660     }
 661 
 662 
 663     /**
 664      * Basic test of a configuration created with automatic modules
 665      *   a requires b* and c*
 666      *   b* contains p
 667      *   c* contains p
 668      */
 669     @Test(expectedExceptions = { ResolutionException.class })
 670     public void testDuplicateSuppliers1() throws IOException {
 671         ModuleDescriptor descriptor
 672             = ModuleDescriptor.newModule("a")
 673                 .requires("b")
 674                 .requires("c")
 675                 .build();
 676 
 677         // c and d are automatic modules with the same package
 678         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 679         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 680         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 681 
 682         // module finder locates 'a' and the modules in the directory
 683         ModuleFinder finder
 684             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 685                                    ModuleFinder.of(dir));
 686 
 687         Configuration parent = Layer.boot().configuration();
 688         resolve(parent, finder, "a");
 689     }
 690 
 691 
 692     /**
 693      * Basic test of a configuration created with automatic modules
 694      *   a contains p, requires b*
 695      *   b* contains p
 696      */
 697     @Test(expectedExceptions = { ResolutionException.class })
 698     public void testDuplicateSuppliers2() throws IOException {
 699         ModuleDescriptor descriptor
 700             = ModuleDescriptor.newModule("a")
 701                 .packages(Set.of("p"))
 702                 .requires("b")
 703                 .build();
 704 
 705         // c and d are automatic modules with the same package
 706         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 707         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 708 
 709         // module finder locates 'a' and the modules in the directory
 710         ModuleFinder finder
 711             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 712                                    ModuleFinder.of(dir));
 713 
 714         Configuration parent = Layer.boot().configuration();
 715         resolve(parent, finder, "a");
 716     }
 717 
 718 
 719     /**
 720      * Basic test of Layer containing automatic modules
 721      */
 722     public void testInLayer() throws IOException {
 723         ModuleDescriptor descriptor
 724             = ModuleDescriptor.newModule("a")
 725                 .requires("b")
 726                 .requires("c")
 727                 .build();
 728 
 729         // b and c are simple JAR files
 730         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 731         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 732         createDummyJarFile(dir.resolve("c.jar"), "q/T2.class");
 733 
 734         // module finder locates a and the modules in the directory
 735         ModuleFinder finder
 736             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 737                 ModuleFinder.of(dir));
 738 
 739         Configuration parent = Layer.boot().configuration();
 740         Configuration cf = resolve(parent, finder, "a");
 741         assertTrue(cf.modules().size() == 3);
 742 
 743         // each module gets its own loader
 744         Layer layer = Layer.boot().defineModules(cf, mn -> new ClassLoader() { });
 745 
 746         // an unnamed module
 747         Module unnamed = (new ClassLoader() { }).getUnnamedModule();
 748 
 749         Module b = layer.findModule("b").get();
 750         assertTrue(b.isNamed());
 751         assertTrue(b.canRead(unnamed));
 752         testsReadsAll(b, layer);
 753 
 754         Module c = layer.findModule("c").get();
 755         assertTrue(c.isNamed());
 756         assertTrue(b.canRead(unnamed));
 757         testsReadsAll(c, layer);
 758     }
 759 
 760 
 761     /**
 762      * Test miscellaneous methods.
 763      */
 764     public void testMisc() throws IOException {
 765         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 766         Path m_jar = createDummyJarFile(dir.resolve("m.jar"), "p/T.class");
 767 
 768         ModuleFinder finder = ModuleFinder.of(m_jar);
 769 
 770         assertTrue(finder.find("m").isPresent());
 771         ModuleDescriptor m = finder.find("m").get().descriptor();
 772 
 773         // test miscellaneous methods
 774         assertTrue(m.isAutomatic());
 775         assertFalse(m.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC));
 776     }
 777 
 778 
 779     /**
 780      * Invokes parent.resolve to resolve the given root modules.
 781      */
 782     static Configuration resolve(Configuration parent,
 783                                  ModuleFinder finder,
 784                                  String... roots) {
 785         return parent.resolve(finder, ModuleFinder.of(), Set.of(roots));
 786     }
 787 
 788     /**
 789      * Finds a module in the given configuration or its parents, returning
 790      * the module descriptor (or null if not found)
 791      */
 792     static ModuleDescriptor findDescriptor(Configuration cf, String name) {
 793         Optional<ResolvedModule> om = cf.findModule(name);
 794         if (om.isPresent()) {
 795             return om.get().reference().descriptor();
 796         } else {
 797             return null;
 798         }
 799     }
 800 
 801     /**
 802      * Test that a module in a configuration reads all modules in the boot
 803      * configuration.
 804      */
 805     static void testReadAllBootModules(Configuration cf, String mn) {
 806 
 807         Set<String> bootModules = Layer.boot().modules().stream()
 808                 .map(Module::getName)
 809                 .collect(Collectors.toSet());
 810 
 811         bootModules.forEach(other -> assertTrue(reads(cf, mn, other)));
 812 
 813     }
 814 
 815     /**
 816      * Test that the given Module reads all module in the given Layer
 817      * and its parent Layers.
 818      */
 819     static void testsReadsAll(Module m, Layer layer) {
 820         // check that m reads all modules in the layer
 821         layer.configuration().modules().stream()
 822             .map(ResolvedModule::name)
 823             .map(layer::findModule)
 824             .map(Optional::get)
 825             .forEach(other -> assertTrue(m.canRead(other)));
 826 
 827         // also check parent layers
 828         layer.parents().forEach(l -> testsReadsAll(m, l));
 829     }
 830 
 831     /**
 832      * Returns {@code true} if the configuration contains module mn1
 833      * that reads module mn2.
 834      */
 835     static boolean reads(Configuration cf, String mn1, String mn2) {
 836         Optional<ResolvedModule> om = cf.findModule(mn1);
 837         if (!om.isPresent())
 838             return false;
 839 
 840         return om.get().reads().stream()
 841                 .map(ResolvedModule::name)
 842                 .anyMatch(mn2::equals);
 843     }
 844 
 845     /**
 846      * Creates a JAR file, optionally with a manifest, and with the given
 847      * entries. The entries will be empty in the resulting JAR file.
 848      */
 849     static Path createDummyJarFile(Path jarfile, Manifest man, String... entries)
 850         throws IOException
 851     {
 852         Path dir = Files.createTempDirectory(USER_DIR, "tmp");
 853 
 854         for (String entry : entries) {
 855             Path file = dir.resolve(entry);
 856             Path parent = file.getParent();
 857             if (parent != null)
 858                 Files.createDirectories(parent);
 859             Files.createFile(file);
 860         }
 861 
 862         Path[] paths = Stream.of(entries).map(Paths::get).toArray(Path[]::new);
 863         JarUtils.createJarFile(jarfile, man, dir, paths);
 864         return jarfile;
 865     }
 866 
 867     /**
 868      * Creates a JAR file and with the given entries. The entries will be empty
 869      * in the resulting JAR file.
 870      */
 871     static Path createDummyJarFile(Path jarfile, String... entries)
 872             throws IOException
 873     {
 874         return createDummyJarFile(jarfile, null, entries);
 875     }
 876 
 877 }