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