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 import java.io.*; 25 import java.lang.module.ModuleDescriptor; 26 import java.lang.reflect.Method; 27 import java.nio.file.*; 28 import java.util.*; 29 import java.util.function.Consumer; 30 import java.util.jar.JarEntry; 31 import java.util.jar.JarFile; 32 import java.util.jar.JarInputStream; 33 import java.util.jar.Manifest; 34 import java.util.regex.Pattern; 35 import java.util.stream.Collectors; 36 import java.util.stream.Stream; 37 38 import jdk.test.lib.util.FileUtils; 39 import jdk.testlibrary.JDKToolFinder; 40 import org.testng.annotations.BeforeTest; 41 import org.testng.annotations.DataProvider; 42 import org.testng.annotations.Test; 43 44 import static java.lang.String.format; 45 import static java.lang.System.out; 46 47 /* 48 * @test 49 * @bug 8167328 8171830 8165640 8174248 8176772 50 * @library /lib/testlibrary /test/lib 51 * @modules jdk.compiler 52 * jdk.jartool 53 * @build jdk.test.lib.Platform 54 * jdk.test.lib.util.FileUtils 55 * jdk.testlibrary.JDKToolFinder 56 * @compile Basic.java 57 * @run testng Basic 58 * @summary Tests for plain Modular jars & Multi-Release Modular jars 59 */ 60 61 public class Basic { 62 static final Path TEST_SRC = Paths.get(System.getProperty("test.src", ".")); 63 static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", ".")); 64 static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build"); 65 static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar"); 66 67 static final String VM_OPTIONS = System.getProperty("test.vm.opts", ""); 68 static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", ""); 69 static final String JAVA_OPTIONS = System.getProperty("test.java.opts", ""); 70 71 // Details based on the checked in module source 72 static TestModuleData FOO = new TestModuleData("foo", 73 "1.123", 74 "jdk.test.foo.Foo", 75 "Hello World!!!", 76 null, // no hashes 77 Set.of("java.base"), 78 Set.of("jdk.test.foo"), 79 null, // no uses 80 null, // no provides 81 Set.of("jdk.test.foo.internal", 82 "jdk.test.foo.resources")); 83 static TestModuleData BAR = new TestModuleData("bar", 84 "4.5.6.7", 85 "jdk.test.bar.Bar", 86 "Hello from Bar!", 87 null, // no hashes 88 Set.of("java.base", "foo"), 89 null, // no exports 90 null, // no uses 91 null, // no provides 92 Set.of("jdk.test.bar", 93 "jdk.test.bar.internal")); 94 95 static class TestModuleData { 96 final String moduleName; 97 final Set<String> requires; 98 final Set<String> exports; 99 final Set<String> uses; 100 final Set<String> provides; 101 final String mainClass; 102 final String version; 103 final String message; 104 final String hashes; 105 final Set<String> packages; 106 107 TestModuleData(String mn, String v, String mc, String m, String h, 108 Set<String> requires, Set<String> exports, Set<String> uses, 109 Set<String> provides, Set<String> contains) { 110 moduleName = mn; mainClass = mc; version = v; message = m; hashes = h; 111 this.requires = requires != null ? requires : Collections.emptySet(); 112 this.exports = exports != null ? exports : Collections.emptySet(); 113 this.uses = uses != null ? uses : Collections.emptySet();; 114 this.provides = provides != null ? provides : Collections.emptySet(); 115 this.packages = Stream.concat(this.exports.stream(), contains.stream()) 116 .collect(Collectors.toSet()); 117 } 118 static TestModuleData from(String s) { 119 try { 120 BufferedReader reader = new BufferedReader(new StringReader(s)); 121 String line; 122 String message = null; 123 String name = null, version = null, mainClass = null; 124 String hashes = null; 125 Set<String> requires, exports, uses, provides, conceals; 126 requires = exports = uses = provides = conceals = null; 127 while ((line = reader.readLine()) != null) { 128 if (line.startsWith("message:")) { 129 message = line.substring("message:".length()); 130 } else if (line.startsWith("nameAndVersion:")) { 131 line = line.substring("nameAndVersion:".length()); 132 int i = line.indexOf('@'); 133 if (i != -1) { 134 name = line.substring(0, i); 135 version = line.substring(i + 1, line.length()); 136 } else { 137 name = line; 138 } 139 } else if (line.startsWith("mainClass:")) { 140 mainClass = line.substring("mainClass:".length()); 141 } else if (line.startsWith("requires:")) { 142 line = line.substring("requires:".length()); 143 requires = stringToSet(line); 144 } else if (line.startsWith("exports:")) { 145 line = line.substring("exports:".length()); 146 exports = stringToSet(line); 147 } else if (line.startsWith("uses:")) { 148 line = line.substring("uses:".length()); 149 uses = stringToSet(line); 150 } else if (line.startsWith("provides:")) { 151 line = line.substring("provides:".length()); 152 provides = stringToSet(line); 153 } else if (line.startsWith("hashes:")) { 154 hashes = line.substring("hashes:".length()); 155 } else if (line.startsWith("contains:")) { 156 line = line.substring("contains:".length()); 157 conceals = stringToSet(line); 158 } else { 159 throw new AssertionError("Unknown value " + line); 160 } 161 } 162 163 return new TestModuleData(name, version, mainClass, message, 164 hashes, requires, exports, uses, 165 provides, conceals); 166 } catch (IOException x) { 167 throw new UncheckedIOException(x); 168 } 169 } 170 static Set<String> stringToSet(String commaList) { 171 Set<String> s = new HashSet<>(); 172 int i = commaList.indexOf(','); 173 if (i != -1) { 174 String[] p = commaList.split(","); 175 Stream.of(p).forEach(s::add); 176 } else { 177 s.add(commaList); 178 } 179 return s; 180 } 181 } 182 183 static void assertModuleData(Result r, TestModuleData expected) { 184 //out.printf("%s%n", r.output); 185 TestModuleData received = TestModuleData.from(r.output); 186 if (expected.message != null) 187 assertTrue(expected.message.equals(received.message), 188 "Expected message:", expected.message, ", got:", received.message); 189 assertTrue(expected.moduleName.equals(received.moduleName), 190 "Expected moduleName: ", expected.moduleName, ", got:", received.moduleName); 191 assertTrue(expected.version.equals(received.version), 192 "Expected version: ", expected.version, ", got:", received.version); 193 assertTrue(expected.mainClass.equals(received.mainClass), 194 "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass); 195 assertSetsEqual(expected.requires, received.requires); 196 assertSetsEqual(expected.exports, received.exports); 197 assertSetsEqual(expected.uses, received.uses); 198 assertSetsEqual(expected.provides, received.provides); 199 assertSetsEqual(expected.packages, received.packages); 200 } 201 202 static void assertSetsEqual(Set<String> s1, Set<String> s2) { 203 if (!s1.equals(s2)) { 204 org.testng.Assert.assertTrue(false, s1 + " vs " + s2); 205 } 206 } 207 208 @BeforeTest 209 public void compileModules() throws Exception { 210 compileModule(FOO.moduleName); 211 compileModule(BAR.moduleName, MODULE_CLASSES); 212 compileModule("baz"); // for service provider consistency checking 213 214 // copy resources 215 copyResource(TEST_SRC.resolve("src").resolve(FOO.moduleName), 216 MODULE_CLASSES.resolve(FOO.moduleName), 217 "jdk/test/foo/resources/foo.properties"); 218 219 setupMRJARModuleInfo(FOO.moduleName); 220 setupMRJARModuleInfo(BAR.moduleName); 221 setupMRJARModuleInfo("baz"); 222 } 223 224 @Test 225 public void createFoo() throws IOException { 226 Path mp = Paths.get("createFoo"); 227 createTestDir(mp); 228 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 229 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 230 231 jar("--create", 232 "--file=" + modularJar.toString(), 233 "--main-class=" + FOO.mainClass, 234 "--module-version=" + FOO.version, 235 "--no-manifest", 236 "-C", modClasses.toString(), ".") 237 .assertSuccess(); 238 239 assertSetsEqual(readPackagesAttribute(modularJar), 240 Set.of("jdk.test.foo", 241 "jdk.test.foo.resources", 242 "jdk.test.foo.internal")); 243 244 java(mp, FOO.moduleName + "/" + FOO.mainClass) 245 .assertSuccess() 246 .resultChecker(r -> assertModuleData(r, FOO)); 247 try (InputStream fis = Files.newInputStream(modularJar); 248 JarInputStream jis = new JarInputStream(fis)) { 249 assertTrue(!jarContains(jis, "./"), 250 "Unexpected ./ found in ", modularJar.toString()); 251 } 252 } 253 254 /** Similar to createFoo, but with a Multi-Release Modular jar. */ 255 @Test 256 public void createMRMJarFoo() throws IOException { 257 Path mp = Paths.get("createMRMJarFoo"); 258 createTestDir(mp); 259 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 260 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 261 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 262 263 // Positive test, create 264 jar("--create", 265 "--file=" + modularJar.toString(), 266 "--main-class=" + FOO.mainClass, 267 "--module-version=" + FOO.version, 268 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 269 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 270 "-C", modClasses.toString(), ".") 271 .assertSuccess(); 272 java(mp, FOO.moduleName + "/" + FOO.mainClass) 273 .assertSuccess() 274 .resultChecker(r -> assertModuleData(r, FOO)); 275 } 276 277 278 @Test 279 public void updateFoo() throws IOException { 280 Path mp = Paths.get("updateFoo"); 281 createTestDir(mp); 282 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 283 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 284 285 jar("--create", 286 "--file=" + modularJar.toString(), 287 "--no-manifest", 288 "-C", modClasses.toString(), "jdk") 289 .assertSuccess(); 290 jar("--update", 291 "--file=" + modularJar.toString(), 292 "--main-class=" + FOO.mainClass, 293 "--module-version=" + FOO.version, 294 "--no-manifest", 295 "-C", modClasses.toString(), "module-info.class") 296 .assertSuccess(); 297 java(mp, FOO.moduleName + "/" + FOO.mainClass) 298 .assertSuccess() 299 .resultChecker(r -> assertModuleData(r, FOO)); 300 } 301 302 @Test 303 public void updateMRMJarFoo() throws IOException { 304 Path mp = Paths.get("updateMRMJarFoo"); 305 createTestDir(mp); 306 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 307 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 308 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 309 310 jar("--create", 311 "--file=" + modularJar.toString(), 312 "--no-manifest", 313 "-C", modClasses.toString(), "jdk") 314 .assertSuccess(); 315 jar("--update", 316 "--file=" + modularJar.toString(), 317 "--main-class=" + FOO.mainClass, 318 "--module-version=" + FOO.version, 319 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 320 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 321 "-C", modClasses.toString(), "module-info.class") 322 .assertSuccess(); 323 java(mp, FOO.moduleName + "/" + FOO.mainClass) 324 .assertSuccess() 325 .resultChecker(r -> assertModuleData(r, FOO)); 326 } 327 328 @Test 329 public void partialUpdateFooMainClass() throws IOException { 330 Path mp = Paths.get("partialUpdateFooMainClass"); 331 createTestDir(mp); 332 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 333 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 334 335 // A "bad" main class in first create ( and no version ) 336 jar("--create", 337 "--file=" + modularJar.toString(), 338 "--main-class=" + "jdk.test.foo.IAmNotTheEntryPoint", 339 "--no-manifest", 340 "-C", modClasses.toString(), ".") // includes module-info.class 341 .assertSuccess(); 342 jar("--update", 343 "--file=" + modularJar.toString(), 344 "--main-class=" + FOO.mainClass, 345 "--module-version=" + FOO.version, 346 "--no-manifest") 347 .assertSuccess(); 348 java(mp, FOO.moduleName + "/" + FOO.mainClass) 349 .assertSuccess() 350 .resultChecker(r -> assertModuleData(r, FOO)); 351 } 352 353 @Test 354 public void partialUpdateFooVersion() throws IOException { 355 Path mp = Paths.get("partialUpdateFooVersion"); 356 createTestDir(mp); 357 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 358 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 359 360 // A "bad" version in first create ( and no main class ) 361 jar("--create", 362 "--file=" + modularJar.toString(), 363 "--module-version=" + "100000000", 364 "--no-manifest", 365 "-C", modClasses.toString(), ".") // includes module-info.class 366 .assertSuccess(); 367 jar("--update", 368 "--file=" + modularJar.toString(), 369 "--main-class=" + FOO.mainClass, 370 "--module-version=" + FOO.version, 371 "--no-manifest") 372 .assertSuccess(); 373 java(mp, FOO.moduleName + "/" + FOO.mainClass) 374 .assertSuccess() 375 .resultChecker(r -> assertModuleData(r, FOO)); 376 } 377 378 @Test 379 public void partialUpdateFooNotAllFiles() throws IOException { 380 Path mp = Paths.get("partialUpdateFooNotAllFiles"); 381 createTestDir(mp); 382 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 383 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 384 385 // Not all files, and none from non-exported packages, 386 // i.e. no concealed list in first create 387 jar("--create", 388 "--file=" + modularJar.toString(), 389 "--no-manifest", 390 "-C", modClasses.toString(), "module-info.class", 391 "-C", modClasses.toString(), "jdk/test/foo/Foo.class", 392 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 393 .assertSuccess(); 394 jar("--update", 395 "--file=" + modularJar.toString(), 396 "--main-class=" + FOO.mainClass, 397 "--module-version=" + FOO.version, 398 "--no-manifest", 399 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 400 .assertSuccess(); 401 java(mp, FOO.moduleName + "/" + FOO.mainClass) 402 .assertSuccess() 403 .resultChecker(r -> assertModuleData(r, FOO)); 404 } 405 406 @Test 407 public void partialUpdateMRMJarFooNotAllFiles() throws IOException { 408 Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles"); 409 createTestDir(mp); 410 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 411 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 412 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 413 414 jar("--create", 415 "--file=" + modularJar.toString(), 416 "--module-version=" + FOO.version, 417 "-C", modClasses.toString(), ".") 418 .assertSuccess(); 419 jar("--update", 420 "--file=" + modularJar.toString(), 421 "--main-class=" + FOO.mainClass, 422 "--module-version=" + FOO.version, 423 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 424 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class") 425 .assertSuccess(); 426 java(mp, FOO.moduleName + "/" + FOO.mainClass) 427 .assertSuccess() 428 .resultChecker(r -> assertModuleData(r, FOO)); 429 } 430 431 @Test 432 public void partialUpdateFooAllFilesAndAttributes() throws IOException { 433 Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes"); 434 createTestDir(mp); 435 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 436 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 437 438 // all attributes and files 439 jar("--create", 440 "--file=" + modularJar.toString(), 441 "--main-class=" + FOO.mainClass, 442 "--module-version=" + FOO.version, 443 "--no-manifest", 444 "-C", modClasses.toString(), ".") 445 .assertSuccess(); 446 jar("--update", 447 "--file=" + modularJar.toString(), 448 "--main-class=" + FOO.mainClass, 449 "--module-version=" + FOO.version, 450 "--no-manifest", 451 "-C", modClasses.toString(), ".") 452 .assertSuccess(); 453 java(mp, FOO.moduleName + "/" + FOO.mainClass) 454 .assertSuccess() 455 .resultChecker(r -> assertModuleData(r, FOO)); 456 } 457 458 @Test 459 public void partialUpdateFooModuleInfo() throws IOException { 460 Path mp = Paths.get("partialUpdateFooModuleInfo"); 461 createTestDir(mp); 462 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 463 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 464 Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName); 465 466 jar("--create", 467 "--file=" + modularJar.toString(), 468 "--main-class=" + FOO.mainClass, 469 "--module-version=" + FOO.version, 470 "--no-manifest", 471 "-C", modClasses.toString(), ".") 472 .assertSuccess(); 473 jar("--update", 474 "--file=" + modularJar.toString(), 475 "--no-manifest", 476 "-C", barModInfo.toString(), "module-info.class") // stuff in bar's info 477 .assertSuccess(); 478 jar("-d", 479 "--file=" + modularJar.toString()) 480 .assertSuccess() 481 .resultChecker(r -> { 482 // Expect "bar jar:file:/.../!module-info.class" 483 // conceals jdk.test.foo, conceals jdk.test.foo.internal" 484 String uri = "jar:" + modularJar.toUri().toString() + "/!module-info.class"; 485 assertTrue(r.output.contains("bar " + uri), 486 "Expecting to find \"bar " + uri + "\"", 487 "in output, but did not: [" + r.output + "]"); 488 Pattern p = Pattern.compile( 489 "contains\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal"); 490 assertTrue(p.matcher(r.output).find(), 491 "Expecting to find \"contains jdk.test.foo,...\"", 492 "in output, but did not: [" + r.output + "]"); 493 }); 494 } 495 496 497 @Test 498 public void partialUpdateFooPackagesAttribute() throws IOException { 499 Path mp = Paths.get("partialUpdateFooPackagesAttribute"); 500 createTestDir(mp); 501 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 502 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 503 504 // Not all files, and none from non-exported packages, 505 // i.e. no concealed list in first create 506 jar("--create", 507 "--file=" + modularJar.toString(), 508 "--no-manifest", 509 "-C", modClasses.toString(), "module-info.class", 510 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 511 .assertSuccess(); 512 513 assertSetsEqual(readPackagesAttribute(modularJar), 514 Set.of("jdk.test.foo")); 515 516 jar("--update", 517 "--file=" + modularJar.toString(), 518 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 519 .assertSuccess(); 520 521 assertSetsEqual(readPackagesAttribute(modularJar), 522 Set.of("jdk.test.foo", "jdk.test.foo.resources")); 523 524 jar("--update", 525 "--file=" + modularJar.toString(), 526 "--main-class=" + FOO.mainClass, 527 "--module-version=" + FOO.version, 528 "--no-manifest", 529 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 530 .assertSuccess(); 531 532 assertSetsEqual(readPackagesAttribute(modularJar), 533 Set.of("jdk.test.foo", 534 "jdk.test.foo.resources", 535 "jdk.test.foo.internal")); 536 537 java(mp, FOO.moduleName + "/" + FOO.mainClass) 538 .assertSuccess() 539 .resultChecker(r -> assertModuleData(r, FOO)); 540 } 541 542 private Set<String> readPackagesAttribute(Path jar) { 543 return getModuleDescriptor(jar).packages(); 544 } 545 546 @Test 547 public void hashBarInFooModule() throws IOException { 548 Path mp = Paths.get("dependencesFooBar"); 549 createTestDir(mp); 550 551 Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName); 552 Path modularJar = mp.resolve(BAR.moduleName + ".jar"); 553 jar("--create", 554 "--file=" + modularJar.toString(), 555 "--main-class=" + BAR.mainClass, 556 "--module-version=" + BAR.version, 557 "--no-manifest", 558 "-C", modClasses.toString(), ".") 559 .assertSuccess(); 560 561 modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 562 modularJar = mp.resolve(FOO.moduleName + ".jar"); 563 jar("--create", 564 "--file=" + modularJar.toString(), 565 "--main-class=" + FOO.mainClass, 566 "--module-version=" + FOO.version, 567 "--module-path=" + mp.toString(), 568 "--hash-modules=" + "bar", 569 "--no-manifest", 570 "-C", modClasses.toString(), ".") 571 .assertSuccess(); 572 573 java(mp, BAR.moduleName + "/" + BAR.mainClass, 574 "--add-exports", "java.base/jdk.internal.misc=bar", 575 "--add-exports", "java.base/jdk.internal.module=bar") 576 .assertSuccess() 577 .resultChecker(r -> { 578 assertModuleData(r, BAR); 579 TestModuleData received = TestModuleData.from(r.output); 580 assertTrue(received.hashes != null, "Expected non-null hashes value."); 581 }); 582 } 583 584 @Test 585 public void invalidHashInFooModule() throws IOException { 586 Path mp = Paths.get("badDependencyFooBar"); 587 createTestDir(mp); 588 589 Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName); 590 Path barJar = mp.resolve(BAR.moduleName + ".jar"); 591 jar("--create", 592 "--file=" + barJar.toString(), 593 "--main-class=" + BAR.mainClass, 594 "--module-version=" + BAR.version, 595 "--no-manifest", 596 "-C", barClasses.toString(), ".").assertSuccess(); 597 598 Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName); 599 Path fooJar = mp.resolve(FOO.moduleName + ".jar"); 600 jar("--create", 601 "--file=" + fooJar.toString(), 602 "--main-class=" + FOO.mainClass, 603 "--module-version=" + FOO.version, 604 "-p", mp.toString(), // test short-form 605 "--hash-modules=" + "bar", 606 "--no-manifest", 607 "-C", fooClasses.toString(), ".").assertSuccess(); 608 609 // Rebuild bar.jar with a change that will cause its hash to be different 610 FileUtils.deleteFileWithRetry(barJar); 611 jar("--create", 612 "--file=" + barJar.toString(), 613 "--main-class=" + BAR.mainClass, 614 "--module-version=" + BAR.version + ".1", // a newer version 615 "--no-manifest", 616 "-C", barClasses.toString(), ".").assertSuccess(); 617 618 java(mp, BAR.moduleName + "/" + BAR.mainClass, 619 "--add-exports", "java.base/jdk.internal.misc=bar", 620 "--add-exports", "java.base/jdk.internal.module=bar") 621 .assertFailure() 622 .resultChecker(r -> { 623 // Expect similar output: "java.lang.module.ResolutionException: Hash 624 // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to 625 // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)" 626 Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*"); 627 assertTrue(p.matcher(r.output).find(), 628 "Expecting error message containing \"Hash of bar ... differs to" 629 + " expected hash...\" but got: [", r.output + "]"); 630 }); 631 } 632 633 @Test 634 public void badOptionsFoo() throws IOException { 635 Path mp = Paths.get("badOptionsFoo"); 636 createTestDir(mp); 637 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 638 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 639 640 jar("--create", 641 "--file=" + modularJar.toString(), 642 "--module-version=" + 1.1, // no module-info.class 643 "-C", modClasses.toString(), "jdk") 644 .assertFailure(); // TODO: expected failure message 645 646 jar("--create", 647 "--file=" + modularJar.toString(), 648 "--hash-modules=" + ".*", // no module-info.class 649 "-C", modClasses.toString(), "jdk") 650 .assertFailure(); // TODO: expected failure message 651 } 652 653 @Test 654 public void servicesCreateWithoutFailure() throws IOException { 655 Path mp = Paths.get("servicesCreateWithoutFailure"); 656 createTestDir(mp); 657 Path modClasses = MODULE_CLASSES.resolve("baz"); 658 Path modularJar = mp.resolve("baz" + ".jar"); 659 660 // Positive test, create 661 jar("--create", 662 "--file=" + modularJar.toString(), 663 "-C", modClasses.toString(), "module-info.class", 664 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 665 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 666 .assertSuccess(); 667 } 668 669 @Test 670 public void servicesCreateWithoutServiceImpl() throws IOException { 671 Path mp = Paths.get("servicesWithoutServiceImpl"); 672 createTestDir(mp); 673 Path modClasses = MODULE_CLASSES.resolve("baz"); 674 Path modularJar = mp.resolve("baz" + ".jar"); 675 676 // Omit service impl 677 jar("--create", 678 "--file=" + modularJar.toString(), 679 "-C", modClasses.toString(), "module-info.class", 680 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 681 .assertFailure(); 682 } 683 684 @Test 685 public void servicesUpdateWithoutFailure() throws IOException { 686 Path mp = Paths.get("servicesUpdateWithoutFailure"); 687 createTestDir(mp); 688 Path modClasses = MODULE_CLASSES.resolve("baz"); 689 Path modularJar = mp.resolve("baz" + ".jar"); 690 691 // Positive test, update 692 jar("--create", 693 "--file=" + modularJar.toString(), 694 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 695 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 696 .assertSuccess(); 697 jar("--update", 698 "--file=" + modularJar.toString(), 699 "-C", modClasses.toString(), "module-info.class") 700 .assertSuccess(); 701 } 702 703 @Test 704 public void servicesUpdateWithoutServiceImpl() throws IOException { 705 Path mp = Paths.get("servicesUpdateWithoutServiceImpl"); 706 createTestDir(mp); 707 Path modClasses = MODULE_CLASSES.resolve("baz"); 708 Path modularJar = mp.resolve("baz" + ".jar"); 709 710 // Omit service impl 711 jar("--create", 712 "--file=" + modularJar.toString(), 713 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 714 .assertSuccess(); 715 jar("--update", 716 "--file=" + modularJar.toString(), 717 "-C", modClasses.toString(), "module-info.class") 718 .assertFailure(); 719 } 720 721 @Test 722 public void servicesCreateWithoutFailureMRMJAR() throws IOException { 723 Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR"); 724 createTestDir(mp); 725 Path modClasses = MODULE_CLASSES.resolve("baz"); 726 Path mrjarDir = MRJAR_DIR.resolve("baz"); 727 Path modularJar = mp.resolve("baz" + ".jar"); 728 729 jar("--create", 730 "--file=" + modularJar.toString(), 731 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 732 "-C", modClasses.toString(), "module-info.class", 733 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 734 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 735 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 736 .assertSuccess(); 737 } 738 739 @Test 740 public void servicesCreateWithoutFailureNonRootMRMJAR() throws IOException { 741 // without a root module-info.class 742 Path mp = Paths.get("servicesCreateWithoutFailureNonRootMRMJAR"); 743 createTestDir(mp); 744 Path modClasses = MODULE_CLASSES.resolve("baz"); 745 Path mrjarDir = MRJAR_DIR.resolve("baz"); 746 Path modularJar = mp.resolve("baz.jar"); 747 748 jar("--create", 749 "--file=" + modularJar.toString(), 750 "--main-class=" + "jdk.test.baz.Baz", 751 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 752 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 753 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 754 "-C", modClasses.toString(), "jdk/test/baz/Baz.class", 755 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 756 .assertSuccess(); 757 758 759 for (String option : new String[] {"--describe-module", "-d" }) { 760 761 jar(option, 762 "--file=" + modularJar.toString(), 763 "--release", "9") 764 .assertSuccess() 765 .resultChecker(r -> 766 assertTrue(r.output.contains("main-class jdk.test.baz.Baz"), 767 "Expected to find ", "main-class jdk.test.baz.Baz", 768 " in [", r.output, "]")); 769 770 jarWithStdin(modularJar.toFile(), option, "--release", "9") 771 .assertSuccess() 772 .resultChecker(r -> 773 assertTrue(r.output.contains("main-class jdk.test.baz.Baz"), 774 "Expected to find ", "main-class jdk.test.baz.Baz", 775 " in [", r.output, "]")); 776 777 } 778 // run module main class 779 java(mp, "baz/jdk.test.baz.Baz") 780 .assertSuccess() 781 .resultChecker(r -> 782 assertTrue(r.output.contains("mainClass:jdk.test.baz.Baz"), 783 "Expected to find ", "mainClass:jdk.test.baz.Baz", 784 " in [", r.output, "]")); 785 } 786 787 @Test 788 public void exportCreateWithMissingPkg() throws IOException { 789 790 Path foobar = TEST_SRC.resolve("src").resolve("foobar"); 791 Path dst = Files.createDirectories(MODULE_CLASSES.resolve("foobar")); 792 javac(dst, null, sourceList(foobar)); 793 794 Path mp = Paths.get("exportWithMissingPkg"); 795 createTestDir(mp); 796 Path modClasses = dst; 797 Path modularJar = mp.resolve("foofoo.jar"); 798 799 jar("--create", 800 "--file=" + modularJar.toString(), 801 "-C", modClasses.toString(), "module-info.class", 802 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 803 .assertFailure(); 804 } 805 806 @Test 807 public void describeModuleFoo() throws IOException { 808 Path mp = Paths.get("describeModuleFoo"); 809 createTestDir(mp); 810 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 811 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 812 813 jar("--create", 814 "--file=" + modularJar.toString(), 815 "--main-class=" + FOO.mainClass, 816 "--module-version=" + FOO.version, 817 "--no-manifest", 818 "-C", modClasses.toString(), ".") 819 .assertSuccess(); 820 821 for (String option : new String[] {"--describe-module", "-d" }) { 822 jar(option, 823 "--file=" + modularJar.toString()) 824 .assertSuccess() 825 .resultChecker(r -> 826 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 827 "Expected to find ", FOO.moduleName + "@" + FOO.version, 828 " in [", r.output, "]") 829 ); 830 831 jar(option, 832 "--file=" + modularJar.toString(), 833 modularJar.toString()) 834 .assertFailure(); 835 836 jar(option, modularJar.toString()) 837 .assertFailure(); 838 } 839 } 840 841 @Test 842 public void describeModuleFooFromStdin() throws IOException { 843 Path mp = Paths.get("describeModuleFooFromStdin"); 844 createTestDir(mp); 845 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 846 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 847 848 jar("--create", 849 "--file=" + modularJar.toString(), 850 "--main-class=" + FOO.mainClass, 851 "--module-version=" + FOO.version, 852 "--no-manifest", 853 "-C", modClasses.toString(), ".") 854 .assertSuccess(); 855 856 for (String option : new String[] {"--describe-module", "-d" }) { 857 jarWithStdin(modularJar.toFile(), 858 option) 859 .assertSuccess() 860 .resultChecker(r -> 861 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 862 "Expected to find ", FOO.moduleName + "@" + FOO.version, 863 " in [", r.output, "]") 864 ); 865 } 866 } 867 868 869 @DataProvider(name = "autoNames") 870 public Object[][] autoNames() { 871 return new Object[][] { 872 // JAR file name module-name[@version] 873 { "foo.jar", "foo" }, 874 { "foo1.jar", "foo1" }, 875 { "foo4j.jar", "foo4j", }, 876 { "foo-1.2.3.4.jar", "foo@1.2.3.4" }, 877 { "foo-bar.jar", "foo.bar" }, 878 { "foo-1.2-SNAPSHOT.jar", "foo@1.2-SNAPSHOT" }, 879 }; 880 } 881 882 @Test(dataProvider = "autoNames") 883 public void describeAutomaticModule(String jarName, String mid) 884 throws IOException 885 { 886 Path mp = Paths.get("describeAutomaticModule"); 887 createTestDir(mp); 888 Path regularJar = mp.resolve(jarName); 889 Path t = Paths.get("t"); 890 if (Files.notExists(t)) 891 Files.createFile(t); 892 893 jar("--create", 894 "--file=" + regularJar.toString(), 895 t.toString()) 896 .assertSuccess(); 897 898 for (String option : new String[] {"--describe-module", "-d" }) { 899 jar(option, 900 "--file=" + regularJar.toString()) 901 .assertSuccess() 902 .resultChecker(r -> { 903 assertTrue(r.output.contains("No module descriptor found")); 904 assertTrue(r.output.contains("Derived automatic module")); 905 assertTrue(r.output.contains(mid + " automatic"), 906 "Expected [", "module " + mid,"] in [", r.output, "]"); 907 } 908 ); 909 } 910 } 911 912 // -- Infrastructure 913 914 static Result jarWithStdin(File stdinSource, String... args) { 915 String jar = getJDKTool("jar"); 916 List<String> commands = new ArrayList<>(); 917 commands.add(jar); 918 if (!TOOL_VM_OPTIONS.isEmpty()) { 919 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 920 } 921 Stream.of(args).forEach(commands::add); 922 ProcessBuilder p = new ProcessBuilder(commands); 923 if (stdinSource != null) 924 p.redirectInput(stdinSource); 925 return run(p); 926 } 927 928 static Result jar(String... args) { 929 return jarWithStdin(null, args); 930 } 931 932 static Path compileModule(String mn) throws IOException { 933 return compileModule(mn, null); 934 } 935 936 static Path compileModule(String mn, Path mp) 937 throws IOException 938 { 939 Path sourcePath = TEST_SRC.resolve("src").resolve(mn); 940 Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn)); 941 javac(build, mp, sourceList(sourcePath)); 942 return build; 943 } 944 945 static void copyResource(Path srcDir, Path dir, String resource) 946 throws IOException 947 { 948 Path dest = dir.resolve(resource); 949 Files.deleteIfExists(dest); 950 951 Files.createDirectories(dest.getParent()); 952 Files.copy(srcDir.resolve(resource), dest); 953 } 954 955 static void setupMRJARModuleInfo(String moduleName) throws IOException { 956 Path modClasses = MODULE_CLASSES.resolve(moduleName); 957 Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF"); 958 Path versionSection = metaInfDir.resolve("versions").resolve("9"); 959 createTestDir(versionSection); 960 961 Path versionModuleInfo = versionSection.resolve("module-info.class"); 962 System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo); 963 Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo); 964 965 Manifest manifest = new Manifest(); 966 manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); 967 manifest.getMainAttributes().putValue("Multi-Release", "true"); 968 try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) { 969 manifest.write(os); 970 } 971 } 972 973 static ModuleDescriptor getModuleDescriptor(Path jar) { 974 ClassLoader cl = ClassLoader.getSystemClassLoader(); 975 try (JarFile jf = new JarFile(jar.toFile())) { 976 JarEntry entry = jf.getJarEntry("module-info.class"); 977 try (InputStream in = jf.getInputStream(entry)) { 978 return ModuleDescriptor.read(in); 979 } 980 } catch (IOException ioe) { 981 throw new UncheckedIOException(ioe); 982 } 983 } 984 985 // Re-enable when there is support in javax.tools for module path 986 // static void javac(Path dest, Path... sourceFiles) throws IOException { 987 // out.printf("Compiling %d source files %s%n", sourceFiles.length, 988 // Arrays.asList(sourceFiles)); 989 // JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 990 // try (StandardJavaFileManager fileManager = 991 // compiler.getStandardFileManager(null, null, null)) { 992 // 993 // List<File> files = Stream.of(sourceFiles) 994 // .map(p -> p.toFile()) 995 // .collect(Collectors.toList()); 996 // List<File> dests = Stream.of(dest) 997 // .map(p -> p.toFile()) 998 // .collect(Collectors.toList()); 999 // Iterable<? extends JavaFileObject> compilationUnits = 1000 // fileManager.getJavaFileObjectsFromFiles(files); 1001 // fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests); 1002 // JavaCompiler.CompilationTask task = 1003 // compiler.getTask(null, fileManager, null, null, null, compilationUnits); 1004 // boolean passed = task.call(); 1005 // if (!passed) 1006 // throw new RuntimeException("Error compiling " + files); 1007 // } 1008 // } 1009 1010 static void javac(Path dest, Path... sourceFiles) throws IOException { 1011 javac(dest, null, sourceFiles); 1012 } 1013 1014 static void javac(Path dest, Path modulePath, Path... sourceFiles) 1015 throws IOException 1016 { 1017 String javac = getJDKTool("javac"); 1018 1019 List<String> commands = new ArrayList<>(); 1020 commands.add(javac); 1021 if (!TOOL_VM_OPTIONS.isEmpty()) { 1022 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 1023 } 1024 commands.add("-d"); 1025 commands.add(dest.toString()); 1026 if (dest.toString().contains("bar")) { 1027 commands.add("--add-exports"); 1028 commands.add("java.base/jdk.internal.misc=bar"); 1029 commands.add("--add-exports"); 1030 commands.add("java.base/jdk.internal.module=bar"); 1031 } 1032 if (modulePath != null) { 1033 commands.add("--module-path"); 1034 commands.add(modulePath.toString()); 1035 } 1036 Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x)); 1037 1038 quickFail(run(new ProcessBuilder(commands))); 1039 } 1040 1041 static Result java(Path modulePath, String entryPoint, String... args) { 1042 String java = getJDKTool("java"); 1043 1044 List<String> commands = new ArrayList<>(); 1045 commands.add(java); 1046 if (!VM_OPTIONS.isEmpty()) { 1047 commands.addAll(Arrays.asList(VM_OPTIONS.split("\\s+", -1))); 1048 } 1049 if (!JAVA_OPTIONS.isEmpty()) { 1050 commands.addAll(Arrays.asList(JAVA_OPTIONS.split("\\s+", -1))); 1051 } 1052 Stream.of(args).forEach(x -> commands.add(x)); 1053 commands.add("--module-path"); 1054 commands.add(modulePath.toString()); 1055 commands.add("-m"); 1056 commands.add(entryPoint); 1057 1058 return run(new ProcessBuilder(commands)); 1059 } 1060 1061 static Path[] sourceList(Path directory) throws IOException { 1062 return Files.find(directory, Integer.MAX_VALUE, 1063 (file, attrs) -> (file.toString().endsWith(".java"))) 1064 .toArray(Path[]::new); 1065 } 1066 1067 static void createTestDir(Path p) throws IOException{ 1068 if (Files.exists(p)) 1069 FileUtils.deleteFileTreeWithRetry(p); 1070 Files.createDirectories(p); 1071 } 1072 1073 static boolean jarContains(JarInputStream jis, String entryName) 1074 throws IOException 1075 { 1076 JarEntry e; 1077 while((e = jis.getNextJarEntry()) != null) { 1078 if (e.getName().equals(entryName)) 1079 return true; 1080 } 1081 return false; 1082 } 1083 1084 static void quickFail(Result r) { 1085 if (r.ec != 0) 1086 throw new RuntimeException(r.output); 1087 } 1088 1089 static Result run(ProcessBuilder pb) { 1090 Process p; 1091 out.printf("Running: %s%n", pb.command()); 1092 try { 1093 p = pb.start(); 1094 } catch (IOException e) { 1095 throw new RuntimeException( 1096 format("Couldn't start process '%s'", pb.command()), e); 1097 } 1098 1099 String output; 1100 try { 1101 output = toString(p.getInputStream(), p.getErrorStream()); 1102 } catch (IOException e) { 1103 throw new RuntimeException( 1104 format("Couldn't read process output '%s'", pb.command()), e); 1105 } 1106 1107 try { 1108 p.waitFor(); 1109 } catch (InterruptedException e) { 1110 throw new RuntimeException( 1111 format("Process hasn't finished '%s'", pb.command()), e); 1112 } 1113 return new Result(p.exitValue(), output); 1114 } 1115 1116 static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home") 1117 + File.separator + "bin" + File.separator; 1118 1119 static String getJDKTool(String name) { 1120 try { 1121 return JDKToolFinder.getJDKTool(name); 1122 } catch (Exception x) { 1123 return DEFAULT_IMAGE_BIN + name; 1124 } 1125 } 1126 1127 static String toString(InputStream in1, InputStream in2) throws IOException { 1128 try (ByteArrayOutputStream dst = new ByteArrayOutputStream(); 1129 InputStream concatenated = new SequenceInputStream(in1, in2)) { 1130 concatenated.transferTo(dst); 1131 return new String(dst.toByteArray(), "UTF-8"); 1132 } 1133 } 1134 1135 static class Result { 1136 final int ec; 1137 final String output; 1138 1139 private Result(int ec, String output) { 1140 this.ec = ec; 1141 this.output = output; 1142 } 1143 Result assertSuccess() { 1144 assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]"); 1145 return this; 1146 } 1147 Result assertFailure() { 1148 assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]"); 1149 return this; 1150 } 1151 Result resultChecker(Consumer<Result> r) { r.accept(this); return this; } 1152 } 1153 1154 static void assertTrue(boolean cond, Object ... failedArgs) { 1155 if (cond) 1156 return; 1157 StringBuilder sb = new StringBuilder(); 1158 for (Object o : failedArgs) 1159 sb.append(o); 1160 org.testng.Assert.assertTrue(false, sb.toString()); 1161 } 1162 1163 // Standalone entry point. 1164 public static void main(String[] args) throws Throwable { 1165 Basic test = new Basic(); 1166 test.compileModules(); 1167 for (Method m : Basic.class.getDeclaredMethods()) { 1168 if (m.getAnnotation(Test.class) != null) { 1169 System.out.println("Invoking " + m.getName()); 1170 m.invoke(test); 1171 } 1172 } 1173 } 1174 }