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.nio.file.Files;
  42 import java.nio.file.Path;
  43 import java.nio.file.Paths;
  44 import java.util.Optional;
  45 import java.util.Set;
  46 import java.util.jar.Attributes;
  47 import java.util.jar.Manifest;
  48 import java.util.stream.Collectors;
  49 import java.util.stream.Stream;
  50 
  51 import org.testng.annotations.DataProvider;
  52 import org.testng.annotations.Test;
  53 import static org.testng.Assert.*;
  54 
  55 @Test
  56 public class AutomaticModulesTest {
  57 
  58     private static final Path USER_DIR
  59          = Paths.get(System.getProperty("user.dir"));
  60 
  61     @DataProvider(name = "jarnames")
  62     public Object[][] createJarNames() {
  63         return new Object[][] {
  64 
  65             // JAR file name                module-name[/version]
  66 
  67             { "foo.jar",                    "foo" },
  68             { "foo4j.jar",                  "foo4j", },
  69 
  70             { "foo1.jar",                   "foo1" },
  71             { "foo10.jar",                  "foo10" },
  72 
  73             { "foo-1.jar",                  "foo/1" },
  74             { "foo-1.2.jar",                "foo/1.2" },
  75             { "foo-1.2.3.jar",              "foo/1.2.3" },
  76             { "foo-1.2.3.4.jar",            "foo/1.2.3.4" },
  77 
  78             { "foo-10.jar",                 "foo/10" },
  79             { "foo-10.20.jar",              "foo/10.20" },
  80             { "foo-10.20.30.jar",           "foo/10.20.30" },
  81             { "foo-10.20.30.40.jar",        "foo/10.20.30.40" },
  82 
  83             { "foo-bar.jar",                "foo.bar" },
  84             { "foo-bar-1.jar",              "foo.bar/1" },
  85             { "foo-bar-1.2.jar",            "foo.bar/1.2"},
  86             { "foo-bar-10.jar",             "foo.bar/10" },
  87             { "foo-bar-10.20.jar",          "foo.bar/10.20" },
  88 
  89             { "foo.bar1.jar",               "foo.bar1" },
  90             { "foo.bar10.jar",              "foo.bar10" },
  91 
  92             { "foo-1.2-SNAPSHOT.jar",       "foo/1.2-SNAPSHOT" },
  93             { "foo-bar-1.2-SNAPSHOT.jar",   "foo.bar/1.2-SNAPSHOT" },
  94 
  95             { "foo--bar-1.0.jar",           "foo.bar/1.0" },
  96             { "-foo-bar-1.0.jar",           "foo.bar/1.0" },
  97             { "foo-bar--1.0.jar",           "foo.bar/1.0" },
  98 
  99         };
 100     }
 101 
 102     // JAR file names that do not map to a legal module name
 103     @DataProvider(name = "badjarnames")
 104     public Object[][] createBadNames() {
 105         return new Object[][]{
 106 
 107             { ".jar",          null },
 108             { "_.jar",         null },
 109 
 110             { "foo.1.jar",     null },
 111             { "1foo.jar",      null },
 112             { "foo.1bar.jar",  null },
 113 
 114         };
 115     }
 116 
 117     /**
 118      * Test mapping of JAR file names to module names
 119      */
 120     @Test(dataProvider = "jarnames")
 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      * Test impossible mapping of JAR files to modules names
 151      */
 152     @Test(dataProvider = "badjarnames", expectedExceptions = FindException.class)
 153     public void testBadNames(String fn, String ignore) throws IOException {
 154         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 155         Path jf = dir.resolve(fn);
 156 
 157         // create empty JAR file
 158         createDummyJarFile(jf);
 159 
 160         // should throw FindException
 161         ModuleFinder.of(dir).findAll();
 162     }
 163 
 164 
 165     @DataProvider(name = "modulenames")
 166     public Object[][] createModuleNames() {
 167         return new Object[][] {
 168             { "foo",        null },
 169             { "foo",        "1.0" },
 170             { "foo.bar",    null },
 171             { "foo.bar",    "1.0" },
 172             { "class_",     null },
 173             { "class_",     "1.0" },
 174         };
 175     }
 176 
 177     @DataProvider(name = "badmodulenames")
 178     public Object[][] createBadModuleNames() {
 179         return new Object[][] {
 180             { "",            null },
 181             { "",            "1.0" },
 182             { "666",         null },
 183             { "666",         "1.0" },
 184             { "foo.class",   null },
 185             { "foo.class",   "1.0" },
 186         };
 187     }
 188 
 189     /**
 190      * Test JAR files with the Automatic-Module-Name attribute
 191      */
 192     @Test(dataProvider = "modulenames")
 193     public void testAutomaticModuleNameAttribute(String name, String vs)
 194         throws IOException
 195     {
 196         Manifest man = new Manifest();
 197         Attributes attrs = man.getMainAttributes();
 198         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 199         attrs.put(new Attributes.Name("Automatic-Module-Name"), name);
 200 
 201         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 202         String jar;
 203         if (vs == null) {
 204             jar = "m.jar";
 205         } else {
 206             jar = "m-" + vs + ".jar";
 207         }
 208         createDummyJarFile(dir.resolve(jar), man);
 209 
 210         ModuleFinder finder = ModuleFinder.of(dir);
 211 
 212         assertTrue(finder.findAll().size() == 1);
 213         assertTrue(finder.find(name).isPresent());
 214 
 215         ModuleReference mref = finder.find(name).get();
 216         ModuleDescriptor descriptor = mref.descriptor();
 217         assertEquals(descriptor.name(), name);
 218         assertEquals(descriptor.version()
 219                 .map(ModuleDescriptor.Version::toString)
 220                 .orElse(null), vs);
 221     }
 222 
 223     /**
 224      * Test JAR files with the Automatic-Module-Name attribute with a value
 225      * that is not a legal module name.
 226      */
 227     @Test(dataProvider = "badmodulenames", expectedExceptions = FindException.class)
 228     public void testBadAutomaticModuleNameAttribute(String name, String ignore)
 229         throws IOException
 230     {
 231         // should throw FindException
 232         testAutomaticModuleNameAttribute(name, null);
 233     }
 234 
 235     /**
 236      * Test all packages are exported
 237      */
 238     public void testPackages() throws IOException {
 239         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 240         createDummyJarFile(dir.resolve("m.jar"),
 241                            "p/C1.class", "p/C2.class", "q/C1.class");
 242 
 243         ModuleFinder finder = ModuleFinder.of(dir);
 244         Optional<ModuleReference> mref = finder.find("m");
 245         assertTrue(mref.isPresent(), "m not found");
 246 
 247         ModuleDescriptor descriptor = mref.get().descriptor();
 248         assertTrue(descriptor.isAutomatic());
 249 
 250         assertTrue(descriptor.packages().size() == 2);
 251         assertTrue(descriptor.packages().contains("p"));
 252         assertTrue(descriptor.packages().contains("q"));
 253 
 254         assertTrue(descriptor.exports().isEmpty());
 255         assertTrue(descriptor.opens().isEmpty());
 256     }
 257 
 258     /**
 259      * Test class files in JAR file where the entry does not correspond to a
 260      * legal package name.
 261      */
 262     public void testBadPackage() throws IOException {
 263         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 264         createDummyJarFile(dir.resolve("m.jar"), "p/C1.class", "p-/C2.class");
 265 
 266         ModuleFinder finder = ModuleFinder.of(dir);
 267         Optional<ModuleReference> mref = finder.find("m");
 268         assertTrue(mref.isPresent(), "m not found");
 269 
 270         ModuleDescriptor descriptor = mref.get().descriptor();
 271         assertTrue(descriptor.isAutomatic());
 272 
 273         assertTrue(descriptor.packages().size() == 1);
 274         assertTrue(descriptor.packages().contains("p"));
 275 
 276         assertTrue(descriptor.exports().isEmpty());
 277         assertTrue(descriptor.opens().isEmpty());
 278     }
 279 
 280     /**
 281      * Test non-class resources in a JAR file.
 282      */
 283     public void testNonClassResources() throws IOException {
 284         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 285         createDummyJarFile(dir.resolve("m.jar"),
 286                 "LICENSE",
 287                 "README",
 288                 "WEB-INF/tags",
 289                 "p/Type.class",
 290                 "p/resources/m.properties");
 291 
 292         ModuleFinder finder = ModuleFinder.of(dir);
 293         Optional<ModuleReference> mref = finder.find("m");
 294         assertTrue(mref.isPresent(), "m not found");
 295 
 296         ModuleDescriptor descriptor = mref.get().descriptor();
 297         assertTrue(descriptor.isAutomatic());
 298 
 299         assertTrue(descriptor.packages().size() == 1);
 300         assertTrue(descriptor.packages().contains("p"));
 301     }
 302 
 303     /**
 304      * Test .class file in unnamed package (top-level directory)
 305      */
 306     @Test(expectedExceptions = FindException.class)
 307     public void testClassInUnnamedPackage() throws IOException {
 308         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 309         createDummyJarFile(dir.resolve("m.jar"), "Mojo.class");
 310         ModuleFinder finder = ModuleFinder.of(dir);
 311         finder.findAll();
 312     }
 313 
 314     /**
 315      * Test JAR file with META-INF/services configuration file
 316      */
 317     public void testServicesConfiguration() throws IOException {
 318         String service = "p.S";
 319         String provider = "p.S1";
 320 
 321         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 322 
 323         // provider class
 324         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
 325         Files.createDirectories(providerClass.getParent());
 326         Files.createFile(providerClass);
 327 
 328         // services configuration file
 329         Path services = tmpdir.resolve("META-INF").resolve("services");
 330         Files.createDirectories(services);
 331         Files.write(services.resolve(service), Set.of(provider));
 332 
 333         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 334         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 335 
 336         ModuleFinder finder = ModuleFinder.of(dir);
 337 
 338         Optional<ModuleReference> mref = finder.find("m");
 339         assertTrue(mref.isPresent(), "m not found");
 340 
 341         ModuleDescriptor descriptor = mref.get().descriptor();
 342         assertTrue(descriptor.provides().size() == 1);
 343         ModuleDescriptor.Provides provides = descriptor.provides().iterator().next();
 344         assertEquals(provides.service(), service);
 345         assertTrue(provides.providers().size() == 1);
 346         assertTrue(provides.providers().contains((provider)));
 347     }
 348 
 349     // META-INF/services files that don't map to legal service names
 350     @DataProvider(name = "badservices")
 351     public Object[][] createBadServices() {
 352         return new Object[][] {
 353 
 354                 // service type         provider type
 355                 { "-",                  "p.S1" },
 356                 { ".S",                 "p.S1" },
 357         };
 358     }
 359 
 360     /**
 361      * Test JAR file with META-INF/services configuration file with bad
 362      * values or names.
 363      */
 364     @Test(dataProvider = "badservices")
 365     public void testBadServicesNames(String service, String provider)
 366         throws IOException
 367     {
 368         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 369         Path services = tmpdir.resolve("META-INF").resolve("services");
 370         Files.createDirectories(services);
 371         Files.write(services.resolve(service), Set.of(provider));
 372         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 373         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 374 
 375         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
 376         assertTrue(omref.isPresent());
 377         ModuleDescriptor descriptor = omref.get().descriptor();
 378         assertTrue(descriptor.provides().isEmpty());
 379     }
 380 
 381     // META-INF/services configuration file entries that are not legal
 382     @DataProvider(name = "badproviders")
 383     public Object[][] createBadProviders() {
 384         return new Object[][] {
 385 
 386                 // service type         provider type
 387                 { "p.S",                "-" },
 388                 { "p.S",                "p..S1" },
 389                 { "p.S",                "S1." },
 390         };
 391     }
 392 
 393     /**
 394      * Test JAR file with META-INF/services configuration file with bad
 395      * values or names.
 396      */
 397     @Test(dataProvider = "badproviders", expectedExceptions = FindException.class)
 398     public void testBadProviderNames(String service, String provider)
 399         throws IOException
 400     {
 401         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 402 
 403         // provider class
 404         Path providerClass = tmpdir.resolve(provider.replace('.', '/') + ".class");
 405         Files.createDirectories(providerClass.getParent());
 406         Files.createFile(providerClass);
 407 
 408         // services configuration file
 409         Path services = tmpdir.resolve("META-INF").resolve("services");
 410         Files.createDirectories(services);
 411         Files.write(services.resolve(service), Set.of(provider));
 412 
 413         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 414         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 415 
 416         // should throw FindException
 417         ModuleFinder.of(dir).findAll();
 418     }
 419 
 420     /**
 421      * Test JAR file with META-INF/services configuration file listing a
 422      * provider that is not in the module.
 423      */
 424     @Test(expectedExceptions = FindException.class)
 425     public void testMissingProviderPackage() throws IOException {
 426         Path tmpdir = Files.createTempDirectory(USER_DIR, "tmp");
 427 
 428         // services configuration file
 429         Path services = tmpdir.resolve("META-INF").resolve("services");
 430         Files.createDirectories(services);
 431         Files.write(services.resolve("p.S"), Set.of("q.P"));
 432 
 433         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 434         JarUtils.createJarFile(dir.resolve("m.jar"), tmpdir);
 435 
 436         // should throw FindException
 437         ModuleFinder.of(dir).findAll();
 438     }
 439 
 440     /**
 441      * Test that a JAR file with a Main-Class attribute results
 442      * in a module with a main class.
 443      */
 444     public void testMainClass() throws IOException {
 445         String mainClass = "p.Main";
 446 
 447         Manifest man = new Manifest();
 448         Attributes attrs = man.getMainAttributes();
 449         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 450         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 451 
 452         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 453         String entry = mainClass.replace('.', '/') + ".class";
 454         createDummyJarFile(dir.resolve("m.jar"), man, entry);
 455 
 456         ModuleFinder finder = ModuleFinder.of(dir);
 457 
 458         Configuration parent = ModuleLayer.boot().configuration();
 459         Configuration cf = resolve(parent, finder, "m");
 460 
 461         ModuleDescriptor descriptor = findDescriptor(cf, "m");
 462 
 463         assertTrue(descriptor.mainClass().isPresent());
 464         assertEquals(descriptor.mainClass().get(), mainClass);
 465     }
 466 
 467     // Main-Class files that do not map to a legal qualified type name
 468     @DataProvider(name = "badmainclass")
 469     public Object[][] createBadMainClass() {
 470         return new Object[][] {
 471             { "p..Main",     null },
 472             { "p-.Main",     null },
 473 
 474         };
 475     }
 476 
 477     /**
 478      * Test that a JAR file with a Main-Class attribute that is not a qualified
 479      * type name.
 480      */
 481     @Test(dataProvider = "badmainclass")
 482     public void testBadMainClass(String mainClass, String ignore) throws IOException {
 483         Manifest man = new Manifest();
 484         Attributes attrs = man.getMainAttributes();
 485         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 486         attrs.put(Attributes.Name.MAIN_CLASS, mainClass);
 487 
 488         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 489         String entry = mainClass.replace('.', '/') + ".class";
 490         createDummyJarFile(dir.resolve("m.jar"), man, entry);
 491 
 492         // bad Main-Class value should be ignored
 493         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
 494         assertTrue(omref.isPresent());
 495         ModuleDescriptor descriptor = omref.get().descriptor();
 496         assertFalse(descriptor.mainClass().isPresent());
 497     }
 498 
 499     /**
 500      * Test that a JAR file with a Main-Class attribute that is not in the module
 501      */
 502     public void testMissingMainClassPackage() throws IOException {
 503         Manifest man = new Manifest();
 504         Attributes attrs = man.getMainAttributes();
 505         attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0.0");
 506         attrs.put(Attributes.Name.MAIN_CLASS, "p.Main");
 507 
 508         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 509         createDummyJarFile(dir.resolve("m.jar"), man);
 510 
 511         // Main-Class should be ignored because package p is not in module
 512         Optional<ModuleReference> omref = ModuleFinder.of(dir).find("m");
 513         assertTrue(omref.isPresent());
 514         ModuleDescriptor descriptor = omref.get().descriptor();
 515         assertFalse(descriptor.mainClass().isPresent());
 516     }
 517 
 518     /**
 519      * Basic test of a configuration created with automatic modules.
 520      *   a requires b*
 521      *   a requires c*
 522      *   b*
 523      *   c*
 524      */
 525     public void testConfiguration1() throws Exception {
 526         ModuleDescriptor descriptor1
 527             = ModuleDescriptor.newModule("a")
 528                 .requires("b")
 529                 .requires("c")
 530                 .requires("java.base")
 531                 .build();
 532 
 533         // b and c are automatic modules
 534         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 535         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 536         createDummyJarFile(dir.resolve("c.jar"), "q/T.class");
 537 
 538         // module finder locates a and the modules in the directory
 539         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
 540         ModuleFinder finder2 = ModuleFinder.of(dir);
 541         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 542 
 543         Configuration parent = ModuleLayer.boot().configuration();
 544         Configuration cf = resolve(parent, finder, "a");
 545 
 546         assertTrue(cf.modules().size() == 3);
 547         assertTrue(cf.findModule("a").isPresent());
 548         assertTrue(cf.findModule("b").isPresent());
 549         assertTrue(cf.findModule("c").isPresent());
 550 
 551         ResolvedModule base = cf.findModule("java.base").get();
 552         assertTrue(base.configuration() == ModuleLayer.boot().configuration());
 553         ResolvedModule a = cf.findModule("a").get();
 554         ResolvedModule b = cf.findModule("b").get();
 555         ResolvedModule c = cf.findModule("c").get();
 556 
 557         // b && c only require java.base
 558         assertTrue(b.reference().descriptor().requires().size() == 1);
 559         assertTrue(c.reference().descriptor().requires().size() == 1);
 560 
 561         // readability
 562 
 563         assertTrue(a.reads().size() == 3);
 564         assertTrue(a.reads().contains(base));
 565         assertTrue(a.reads().contains(b));
 566         assertTrue(a.reads().contains(c));
 567 
 568         assertTrue(b.reads().contains(a));
 569         assertTrue(b.reads().contains(c));
 570         testReadAllBootModules(cf, "b");  // b reads all modules in boot layer
 571 
 572         assertTrue(c.reads().contains(a));
 573         assertTrue(c.reads().contains(b));
 574         testReadAllBootModules(cf, "c");  // c reads all modules in boot layer
 575 
 576     }
 577 
 578     /**
 579      * Basic test of a configuration created with automatic modules
 580      *   a requires b
 581      *   b requires c*
 582      *   c*
 583      *   d*
 584      */
 585     public void testInConfiguration2() throws IOException {
 586         ModuleDescriptor descriptor1
 587             = ModuleDescriptor.newModule("a")
 588                 .requires("b")
 589                 .requires("java.base")
 590                 .build();
 591 
 592         ModuleDescriptor descriptor2
 593             = ModuleDescriptor.newModule("b")
 594                 .requires("c")
 595                 .requires("java.base")
 596                 .build();
 597 
 598         // c and d are automatic modules
 599         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 600         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 601         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 602 
 603         // module finder locates a and the modules in the directory
 604         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 605         ModuleFinder finder2 = ModuleFinder.of(dir);
 606         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 607 
 608         Configuration parent = ModuleLayer.boot().configuration();
 609         Configuration cf = resolve(parent, finder, "a", "d");
 610 
 611         assertTrue(cf.modules().size() == 4);
 612         assertTrue(cf.findModule("a").isPresent());
 613         assertTrue(cf.findModule("b").isPresent());
 614         assertTrue(cf.findModule("c").isPresent());
 615         assertTrue(cf.findModule("d").isPresent());
 616 
 617         // c && d should only require java.base
 618         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 619         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 620 
 621         // readability
 622 
 623         ResolvedModule base = cf.findModule("java.base").get();
 624         assertTrue(base.configuration() == ModuleLayer.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         assertTrue(a.reads().size() == 2);
 631         assertTrue(a.reads().contains(b));
 632         assertTrue(a.reads().contains(base));
 633 
 634         assertTrue(b.reads().size() == 3);
 635         assertTrue(b.reads().contains(c));
 636         assertTrue(b.reads().contains(d));
 637         assertTrue(b.reads().contains(base));
 638 
 639         assertTrue(c.reads().contains(a));
 640         assertTrue(c.reads().contains(b));
 641         assertTrue(c.reads().contains(d));
 642         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 643 
 644         assertTrue(d.reads().contains(a));
 645         assertTrue(d.reads().contains(b));
 646         assertTrue(d.reads().contains(c));
 647         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 648     }
 649 
 650     /**
 651      * Basic test of a configuration created with automatic modules
 652      *   a requires b
 653      *   b requires transitive c*
 654      *   c*
 655      *   d*
 656      */
 657     public void testInConfiguration3() throws IOException {
 658         ModuleDescriptor descriptor1
 659             = ModuleDescriptor.newModule("a")
 660                 .requires("b")
 661                 .requires("java.base")
 662                 .build();
 663 
 664         ModuleDescriptor descriptor2
 665             = ModuleDescriptor.newModule("b")
 666                 .requires(Set.of(Modifier.TRANSITIVE), "c")
 667                 .requires("java.base")
 668                 .build();
 669 
 670         // c and d are automatic modules
 671         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 672         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 673         createDummyJarFile(dir.resolve("d.jar"), "q/T.class");
 674 
 675         // module finder locates a and the modules in the directory
 676         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 677         ModuleFinder finder2 = ModuleFinder.of(dir);
 678         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 679 
 680         Configuration parent = ModuleLayer.boot().configuration();
 681         Configuration cf = resolve(parent, finder, "a", "d");
 682 
 683         assertTrue(cf.modules().size() == 4);
 684         assertTrue(cf.findModule("a").isPresent());
 685         assertTrue(cf.findModule("b").isPresent());
 686         assertTrue(cf.findModule("c").isPresent());
 687         assertTrue(cf.findModule("d").isPresent());
 688 
 689         ResolvedModule base = cf.findModule("java.base").get();
 690         assertTrue(base.configuration() == ModuleLayer.boot().configuration());
 691         ResolvedModule a = cf.findModule("a").get();
 692         ResolvedModule b = cf.findModule("b").get();
 693         ResolvedModule c = cf.findModule("c").get();
 694         ResolvedModule d = cf.findModule("d").get();
 695 
 696         // c && d should only require java.base
 697         assertTrue(findDescriptor(cf, "c").requires().size() == 1);
 698         assertTrue(findDescriptor(cf, "d").requires().size() == 1);
 699 
 700         // readability
 701 
 702         assertTrue(a.reads().size() == 4);
 703         assertTrue(a.reads().contains(b));
 704         assertTrue(a.reads().contains(c));
 705         assertTrue(a.reads().contains(d));
 706         assertTrue(a.reads().contains(base));
 707 
 708         assertTrue(b.reads().size() == 3);
 709         assertTrue(b.reads().contains(c));
 710         assertTrue(b.reads().contains(d));
 711         assertTrue(b.reads().contains(base));
 712 
 713         assertTrue(reads(cf, "b", "c"));
 714         assertTrue(reads(cf, "b", "d"));
 715         assertTrue(reads(cf, "b", "java.base"));
 716 
 717         assertTrue(c.reads().contains(a));
 718         assertTrue(c.reads().contains(b));
 719         assertTrue(c.reads().contains(d));
 720         testReadAllBootModules(cf, "c");   // c reads all modules in boot layer
 721 
 722         assertTrue(d.reads().contains(a));
 723         assertTrue(d.reads().contains(b));
 724         assertTrue(d.reads().contains(c));
 725         testReadAllBootModules(cf, "d");    // d reads all modules in boot layer
 726     }
 727 
 728     /**
 729      * Basic test to ensure that no automatic modules are resolved when
 730      * an automatic module is not a root or required by other modules.
 731      */
 732     public void testInConfiguration4() throws IOException {
 733         ModuleDescriptor descriptor1
 734             = ModuleDescriptor.newModule("m1")
 735                 .requires("java.base")
 736                 .build();
 737 
 738         // automatic modules
 739         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 740         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
 741         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
 742         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
 743 
 744         // module finder locates m1 and the modules in the directory
 745         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
 746         ModuleFinder finder2 =  ModuleFinder.of(dir);
 747         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 748 
 749         Configuration parent = ModuleLayer.boot().configuration();
 750         Configuration cf = resolve(parent, finder, "m1");
 751 
 752         // ensure that no automatic module is resolved
 753         assertTrue(cf.modules().size() == 1);
 754         assertTrue(cf.findModule("m1").isPresent());
 755     }
 756 
 757     /**
 758      * Basic test to ensure that if an automatic module is resolved then
 759      * all observable automatic modules are resolved.
 760      */
 761     public void testInConfiguration5() throws IOException {
 762         // m1 requires m2
 763         ModuleDescriptor descriptor1
 764             = ModuleDescriptor.newModule("m1")
 765                 .requires("m2").build();
 766 
 767         // m2 requires automatic module
 768         ModuleDescriptor descriptor2
 769             = ModuleDescriptor.newModule("m2")
 770                 .requires("auto1")
 771                 .build();
 772 
 773         // automatic modules
 774         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 775         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
 776         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
 777         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
 778 
 779         // module finder locates m1, m2, and the modules in the directory
 780         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1, descriptor2);
 781         ModuleFinder finder2 =  ModuleFinder.of(dir);
 782         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 783 
 784         Configuration parent = ModuleLayer.boot().configuration();
 785         Configuration cf = resolve(parent, finder, "m1");
 786 
 787         // all automatic modules should be resolved
 788         assertTrue(cf.modules().size() == 5);
 789         assertTrue(cf.findModule("m1").isPresent());
 790         assertTrue(cf.findModule("m2").isPresent());
 791         assertTrue(cf.findModule("auto1").isPresent());
 792         assertTrue(cf.findModule("auto2").isPresent());
 793         assertTrue(cf.findModule("auto3").isPresent());
 794 
 795         ResolvedModule base = parent.findModule("java.base")
 796                                     .orElseThrow(() -> new RuntimeException());
 797         ResolvedModule m1 = cf.findModule("m1").get();
 798         ResolvedModule m2 = cf.findModule("m2").get();
 799         ResolvedModule auto1 = cf.findModule("auto1").get();
 800         ResolvedModule auto2 = cf.findModule("auto2").get();
 801         ResolvedModule auto3 = cf.findModule("auto3").get();
 802 
 803         // m1 does not read the automatic modules
 804         assertTrue(m1.reads().size() == 2);
 805         assertTrue(m1.reads().contains(m2));
 806         assertTrue(m1.reads().contains(base));
 807 
 808         // m2 should read all the automatic modules
 809         assertTrue(m2.reads().size() == 4);
 810         assertTrue(m2.reads().contains(auto1));
 811         assertTrue(m2.reads().contains(auto2));
 812         assertTrue(m2.reads().contains(auto3));
 813         assertTrue(m2.reads().contains(base));
 814 
 815         assertTrue(auto1.reads().contains(m1));
 816         assertTrue(auto1.reads().contains(m2));
 817         assertTrue(auto1.reads().contains(auto2));
 818         assertTrue(auto1.reads().contains(auto3));
 819         assertTrue(auto1.reads().contains(base));
 820 
 821         assertTrue(auto2.reads().contains(m1));
 822         assertTrue(auto2.reads().contains(m2));
 823         assertTrue(auto2.reads().contains(auto1));
 824         assertTrue(auto2.reads().contains(auto3));
 825         assertTrue(auto2.reads().contains(base));
 826 
 827         assertTrue(auto3.reads().contains(m1));
 828         assertTrue(auto3.reads().contains(m2));
 829         assertTrue(auto3.reads().contains(auto1));
 830         assertTrue(auto3.reads().contains(auto2));
 831         assertTrue(auto3.reads().contains(base));
 832     }
 833 
 834     /**
 835      * Basic test of automatic modules in a child configuration. All automatic
 836      * modules that are found with the before finder should be resolved. The
 837      * automatic modules that are found by the after finder and not shadowed
 838      * by the before finder, or parent configurations, should also be resolved.
 839      */
 840     public void testInConfiguration6() throws IOException {
 841         // m1 requires auto1
 842         ModuleDescriptor descriptor1
 843             = ModuleDescriptor.newModule("m1")
 844                 .requires("auto1")
 845                 .build();
 846 
 847         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 848         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
 849 
 850         // module finder locates m1 and auto1
 851         ModuleFinder finder1 = ModuleUtils.finderOf(descriptor1);
 852         ModuleFinder finder2 =  ModuleFinder.of(dir);
 853         ModuleFinder finder = ModuleFinder.compose(finder1, finder2);
 854 
 855         Configuration parent = ModuleLayer.boot().configuration();
 856         Configuration cf1 = resolve(parent, finder, "m1");
 857 
 858         assertTrue(cf1.modules().size() == 2);
 859         assertTrue(cf1.findModule("m1").isPresent());
 860         assertTrue(cf1.findModule("auto1").isPresent());
 861 
 862         ResolvedModule base = parent.findModule("java.base")
 863                                     .orElseThrow(() -> new RuntimeException());
 864         ResolvedModule m1 = cf1.findModule("m1").get();
 865         ResolvedModule auto1 = cf1.findModule("auto1").get();
 866 
 867         assertTrue(m1.reads().size() == 2);
 868         assertTrue(m1.reads().contains(auto1));
 869         assertTrue(m1.reads().contains(base));
 870 
 871         assertTrue(auto1.reads().contains(m1));
 872         assertTrue(auto1.reads().contains(base));
 873 
 874 
 875         // create child configuration - the after finder locates auto1
 876 
 877         dir = Files.createTempDirectory(USER_DIR, "mods");
 878         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
 879         ModuleFinder beforeFinder =  ModuleFinder.of(dir);
 880 
 881         dir = Files.createTempDirectory(USER_DIR, "mods");
 882         createDummyJarFile(dir.resolve("auto1.jar"), "p1/C.class");
 883         createDummyJarFile(dir.resolve("auto2.jar"), "p2/C.class");
 884         createDummyJarFile(dir.resolve("auto3.jar"), "p3/C.class");
 885         ModuleFinder afterFinder =  ModuleFinder.of(dir);
 886 
 887         Configuration cf2 = cf1.resolve(beforeFinder, afterFinder, Set.of("auto2"));
 888 
 889         // auto1 should be found in parent and should not be in cf2
 890         assertTrue(cf2.modules().size() == 2);
 891         assertTrue(cf2.findModule("auto2").isPresent());
 892         assertTrue(cf2.findModule("auto3").isPresent());
 893 
 894         ResolvedModule auto2 = cf2.findModule("auto2").get();
 895         ResolvedModule auto3 = cf2.findModule("auto3").get();
 896 
 897         assertTrue(auto2.reads().contains(m1));
 898         assertTrue(auto2.reads().contains(auto1));
 899         assertTrue(auto2.reads().contains(auto3));
 900         assertTrue(auto2.reads().contains(base));
 901 
 902         assertTrue(auto3.reads().contains(m1));
 903         assertTrue(auto3.reads().contains(auto1));
 904         assertTrue(auto3.reads().contains(auto2));
 905         assertTrue(auto3.reads().contains(base));
 906     }
 907 
 908     /**
 909      * Basic test of a configuration created with automatic modules
 910      *   a requires b* and c*
 911      *   b* contains p
 912      *   c* contains p
 913      */
 914     @Test(expectedExceptions = { ResolutionException.class })
 915     public void testDuplicateSuppliers1() throws IOException {
 916         ModuleDescriptor descriptor
 917             = ModuleDescriptor.newModule("a")
 918                 .requires("b")
 919                 .requires("c")
 920                 .build();
 921 
 922         // c and d are automatic modules with the same package
 923         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 924         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 925         createDummyJarFile(dir.resolve("c.jar"), "p/T.class");
 926 
 927         // module finder locates 'a' and the modules in the directory
 928         ModuleFinder finder
 929             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 930                                    ModuleFinder.of(dir));
 931 
 932         Configuration parent = ModuleLayer.boot().configuration();
 933         resolve(parent, finder, "a");
 934     }
 935 
 936     /**
 937      * Basic test of a configuration created with automatic modules
 938      *   a contains p, requires b*
 939      *   b* contains p
 940      */
 941     @Test(expectedExceptions = { ResolutionException.class })
 942     public void testDuplicateSuppliers2() throws IOException {
 943         ModuleDescriptor descriptor
 944             = ModuleDescriptor.newModule("a")
 945                 .packages(Set.of("p"))
 946                 .requires("b")
 947                 .build();
 948 
 949         // c and d are automatic modules with the same package
 950         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 951         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 952 
 953         // module finder locates 'a' and the modules in the directory
 954         ModuleFinder finder
 955             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 956                                    ModuleFinder.of(dir));
 957 
 958         Configuration parent = ModuleLayer.boot().configuration();
 959         resolve(parent, finder, "a");
 960     }
 961 
 962     /**
 963      * Basic test of layer containing automatic modules
 964      */
 965     public void testInLayer() throws IOException {
 966         ModuleDescriptor descriptor
 967             = ModuleDescriptor.newModule("a")
 968                 .requires("b")
 969                 .requires("c")
 970                 .build();
 971 
 972         // b and c are simple JAR files
 973         Path dir = Files.createTempDirectory(USER_DIR, "mods");
 974         createDummyJarFile(dir.resolve("b.jar"), "p/T.class");
 975         createDummyJarFile(dir.resolve("c.jar"), "q/T2.class");
 976 
 977         // module finder locates a and the modules in the directory
 978         ModuleFinder finder
 979             = ModuleFinder.compose(ModuleUtils.finderOf(descriptor),
 980                 ModuleFinder.of(dir));
 981 
 982         Configuration parent = ModuleLayer.boot().configuration();
 983         Configuration cf = resolve(parent, finder, "a");
 984         assertTrue(cf.modules().size() == 3);
 985 
 986         // each module gets its own loader
 987         ModuleLayer layer = ModuleLayer.boot().defineModules(cf, mn -> new ClassLoader() { });
 988 
 989         // an unnamed module
 990         Module unnamed = (new ClassLoader() { }).getUnnamedModule();
 991 
 992         Module b = layer.findModule("b").get();
 993         assertTrue(b.isNamed());
 994         assertTrue(b.canRead(unnamed));
 995         testsReadsAll(b, layer);
 996 
 997         Module c = layer.findModule("c").get();
 998         assertTrue(c.isNamed());
 999         assertTrue(b.canRead(unnamed));
1000         testsReadsAll(c, layer);
1001     }
1002 
1003     /**
1004      * Test miscellaneous methods.
1005      */
1006     public void testMisc() throws IOException {
1007         Path dir = Files.createTempDirectory(USER_DIR, "mods");
1008         Path m_jar = createDummyJarFile(dir.resolve("m.jar"), "p/T.class");
1009 
1010         ModuleFinder finder = ModuleFinder.of(m_jar);
1011 
1012         assertTrue(finder.find("m").isPresent());
1013         ModuleDescriptor m = finder.find("m").get().descriptor();
1014 
1015         // test miscellaneous methods
1016         assertTrue(m.isAutomatic());
1017         assertFalse(m.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC));
1018     }
1019 
1020     /**
1021      * Invokes parent.resolve to resolve the given root modules.
1022      */
1023     static Configuration resolve(Configuration parent,
1024                                  ModuleFinder finder,
1025                                  String... roots) {
1026         return parent.resolve(finder, ModuleFinder.of(), Set.of(roots));
1027     }
1028 
1029     /**
1030      * Finds a module in the given configuration or its parents, returning
1031      * the module descriptor (or null if not found)
1032      */
1033     static ModuleDescriptor findDescriptor(Configuration cf, String name) {
1034         Optional<ResolvedModule> om = cf.findModule(name);
1035         if (om.isPresent()) {
1036             return om.get().reference().descriptor();
1037         } else {
1038             return null;
1039         }
1040     }
1041 
1042     /**
1043      * Test that a module in a configuration reads all modules in the boot
1044      * configuration.
1045      */
1046     static void testReadAllBootModules(Configuration cf, String mn) {
1047 
1048         Set<String> bootModules = ModuleLayer.boot().modules().stream()
1049                 .map(Module::getName)
1050                 .collect(Collectors.toSet());
1051 
1052         bootModules.forEach(other -> assertTrue(reads(cf, mn, other)));
1053 
1054     }
1055 
1056     /**
1057      * Test that the given Module reads all module in the given layer
1058      * and its parent layers.
1059      */
1060     static void testsReadsAll(Module m, ModuleLayer layer) {
1061         // check that m reads all modules in the layer
1062         layer.configuration().modules().stream()
1063             .map(ResolvedModule::name)
1064             .map(layer::findModule)
1065             .map(Optional::get)
1066             .forEach(other -> assertTrue(m.canRead(other)));
1067 
1068         // also check parent layers
1069         layer.parents().forEach(l -> testsReadsAll(m, l));
1070     }
1071 
1072     /**
1073      * Returns {@code true} if the configuration contains module mn1
1074      * that reads module mn2.
1075      */
1076     static boolean reads(Configuration cf, String mn1, String mn2) {
1077         Optional<ResolvedModule> om = cf.findModule(mn1);
1078         if (!om.isPresent())
1079             return false;
1080 
1081         return om.get().reads().stream()
1082                 .map(ResolvedModule::name)
1083                 .anyMatch(mn2::equals);
1084     }
1085 
1086     /**
1087      * Creates a JAR file, optionally with a manifest, and with the given
1088      * entries. The entries will be empty in the resulting JAR file.
1089      */
1090     static Path createDummyJarFile(Path jarfile, Manifest man, String... entries)
1091         throws IOException
1092     {
1093         Path dir = Files.createTempDirectory(USER_DIR, "tmp");
1094 
1095         for (String entry : entries) {
1096             Path file = dir.resolve(entry);
1097             Path parent = file.getParent();
1098             if (parent != null)
1099                 Files.createDirectories(parent);
1100             Files.createFile(file);
1101         }
1102 
1103         Path[] paths = Stream.of(entries).map(Paths::get).toArray(Path[]::new);
1104         JarUtils.createJarFile(jarfile, man, dir, paths);
1105         return jarfile;
1106     }
1107 
1108     /**
1109      * Creates a JAR file and with the given entries. The entries will be empty
1110      * in the resulting JAR file.
1111      */
1112     static Path createDummyJarFile(Path jarfile, String... entries)
1113         throws IOException
1114     {
1115         return createDummyJarFile(jarfile, null, entries);
1116     }
1117 
1118 }