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