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") createJarNames()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") createBadNames()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") testNames(String fn, String mid)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) testBadNames(String fn, String ignore)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") createModuleNames()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") createBadModuleNames()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") testAutomaticModuleNameAttribute(String name, String vs)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) testBadAutomaticModuleNameAttribute(String name, String ignore)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 */ testPackages()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 */ testBadPackage()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 */ testNonClassResources()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) testClassInUnnamedPackage()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 */ testServicesConfiguration()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") createBadServices()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") testBadServicesNames(String service, String provider)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") createBadProviders()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) testBadProviderNames(String service, String provider)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) testMissingProviderPackage()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 */ testMainClass()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") createBadMainClass()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") testBadMainClass(String mainClass, String ignore)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 */ testMissingMainClassPackage()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 */ testConfiguration1()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 */ testInConfiguration2()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 */ testInConfiguration3()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 */ testInConfiguration4()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 */ testInConfiguration5()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 */ testInConfiguration6()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 }) testDuplicateSuppliers1()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 }) testDuplicateSuppliers2()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 */ testInLayer()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 */ testMisc()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 */ resolve(Configuration parent, ModuleFinder finder, String... roots)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 */ findDescriptor(Configuration cf, String name)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 */ testReadAllBootModules(Configuration cf, String mn)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 */ testsReadsAll(Module m, ModuleLayer layer)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 */ reads(Configuration cf, String mn1, String mn2)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 */ createDummyJarFile(Path jarfile, Manifest man, String... entries)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 */ createDummyJarFile(Path jarfile, String... entries)1117 static Path createDummyJarFile(Path jarfile, String... entries) 1118 throws IOException 1119 { 1120 return createDummyJarFile(jarfile, null, entries); 1121 } 1122 1123 } 1124