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