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.nio.file.attribute.BasicFileAttributes; 29 import java.util.*; 30 import java.util.function.Consumer; 31 import java.util.jar.JarEntry; 32 import java.util.jar.JarFile; 33 import java.util.jar.JarInputStream; 34 import java.util.jar.Manifest; 35 import java.util.regex.Pattern; 36 import java.util.stream.Collectors; 37 import java.util.stream.Stream; 38 39 import jdk.testlibrary.FileUtils; 40 import jdk.testlibrary.JDKToolFinder; 41 import org.testng.annotations.BeforeTest; 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 50 * @library /lib/testlibrary 51 * @modules jdk.compiler 52 * jdk.jartool 53 * @build jdk.testlibrary.FileUtils 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 -> { 245 System.out.println("==================================="); 246 System.out.println(r.output); 247 System.out.println("==================================="); 248 }) 249 .resultChecker(r -> assertModuleData(r, FOO)); 250 try (InputStream fis = Files.newInputStream(modularJar); 251 JarInputStream jis = new JarInputStream(fis)) { 252 assertTrue(!jarContains(jis, "./"), 253 "Unexpected ./ found in ", modularJar.toString()); 254 } 255 } 256 257 /** Similar to createFoo, but with a Multi-Release Modular jar. */ 258 @Test 259 public void createMRMJarFoo() throws IOException { 260 Path mp = Paths.get("createMRMJarFoo"); 261 createTestDir(mp); 262 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 263 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 264 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 265 266 // Positive test, create 267 jar("--create", 268 "--file=" + modularJar.toString(), 269 "--main-class=" + FOO.mainClass, 270 "--module-version=" + FOO.version, 271 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 272 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 273 "-C", modClasses.toString(), ".") 274 .assertSuccess(); 275 java(mp, FOO.moduleName + "/" + FOO.mainClass) 276 .assertSuccess() 277 .resultChecker(r -> assertModuleData(r, FOO)); 278 } 279 280 281 @Test 282 public void updateFoo() throws IOException { 283 Path mp = Paths.get("updateFoo"); 284 createTestDir(mp); 285 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 286 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 287 288 jar("--create", 289 "--file=" + modularJar.toString(), 290 "--no-manifest", 291 "-C", modClasses.toString(), "jdk") 292 .assertSuccess(); 293 jar("--update", 294 "--file=" + modularJar.toString(), 295 "--main-class=" + FOO.mainClass, 296 "--module-version=" + FOO.version, 297 "--no-manifest", 298 "-C", modClasses.toString(), "module-info.class") 299 .assertSuccess(); 300 java(mp, FOO.moduleName + "/" + FOO.mainClass) 301 .assertSuccess() 302 .resultChecker(r -> assertModuleData(r, FOO)); 303 } 304 305 @Test 306 public void updateMRMJarFoo() throws IOException { 307 Path mp = Paths.get("updateMRMJarFoo"); 308 createTestDir(mp); 309 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 310 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 311 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 312 313 jar("--create", 314 "--file=" + modularJar.toString(), 315 "--no-manifest", 316 "-C", modClasses.toString(), "jdk") 317 .assertSuccess(); 318 jar("--update", 319 "--file=" + modularJar.toString(), 320 "--main-class=" + FOO.mainClass, 321 "--module-version=" + FOO.version, 322 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 323 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 324 "-C", modClasses.toString(), "module-info.class") 325 .assertSuccess(); 326 java(mp, FOO.moduleName + "/" + FOO.mainClass) 327 .assertSuccess() 328 .resultChecker(r -> assertModuleData(r, FOO)); 329 } 330 331 @Test 332 public void partialUpdateFooMainClass() throws IOException { 333 Path mp = Paths.get("partialUpdateFooMainClass"); 334 createTestDir(mp); 335 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 336 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 337 338 // A "bad" main class in first create ( and no version ) 339 jar("--create", 340 "--file=" + modularJar.toString(), 341 "--main-class=" + "IAmNotTheEntryPoint", 342 "--no-manifest", 343 "-C", modClasses.toString(), ".") // includes module-info.class 344 .assertSuccess(); 345 jar("--update", 346 "--file=" + modularJar.toString(), 347 "--main-class=" + FOO.mainClass, 348 "--module-version=" + FOO.version, 349 "--no-manifest") 350 .assertSuccess(); 351 java(mp, FOO.moduleName + "/" + FOO.mainClass) 352 .assertSuccess() 353 .resultChecker(r -> assertModuleData(r, FOO)); 354 } 355 356 @Test 357 public void partialUpdateFooVersion() throws IOException { 358 Path mp = Paths.get("partialUpdateFooVersion"); 359 createTestDir(mp); 360 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 361 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 362 363 // A "bad" version in first create ( and no main class ) 364 jar("--create", 365 "--file=" + modularJar.toString(), 366 "--module-version=" + "100000000", 367 "--no-manifest", 368 "-C", modClasses.toString(), ".") // includes module-info.class 369 .assertSuccess(); 370 jar("--update", 371 "--file=" + modularJar.toString(), 372 "--main-class=" + FOO.mainClass, 373 "--module-version=" + FOO.version, 374 "--no-manifest") 375 .assertSuccess(); 376 java(mp, FOO.moduleName + "/" + FOO.mainClass) 377 .assertSuccess() 378 .resultChecker(r -> assertModuleData(r, FOO)); 379 } 380 381 @Test 382 public void partialUpdateFooNotAllFiles() throws IOException { 383 Path mp = Paths.get("partialUpdateFooNotAllFiles"); 384 createTestDir(mp); 385 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 386 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 387 388 // Not all files, and none from non-exported packages, 389 // i.e. no concealed list in first create 390 jar("--create", 391 "--file=" + modularJar.toString(), 392 "--no-manifest", 393 "-C", modClasses.toString(), "module-info.class", 394 "-C", modClasses.toString(), "jdk/test/foo/Foo.class", 395 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 396 .assertSuccess(); 397 jar("--update", 398 "--file=" + modularJar.toString(), 399 "--main-class=" + FOO.mainClass, 400 "--module-version=" + FOO.version, 401 "--no-manifest", 402 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 403 .assertSuccess(); 404 java(mp, FOO.moduleName + "/" + FOO.mainClass) 405 .assertSuccess() 406 .resultChecker(r -> assertModuleData(r, FOO)); 407 } 408 409 @Test 410 public void partialUpdateMRMJarFooNotAllFiles() throws IOException { 411 Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles"); 412 createTestDir(mp); 413 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 414 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 415 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 416 417 jar("--create", 418 "--file=" + modularJar.toString(), 419 "--module-version=" + FOO.version, 420 "-C", modClasses.toString(), ".") 421 .assertSuccess(); 422 jar("--update", 423 "--file=" + modularJar.toString(), 424 "--main-class=" + FOO.mainClass, 425 "--module-version=" + FOO.version, 426 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 427 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class") 428 .assertSuccess(); 429 java(mp, FOO.moduleName + "/" + FOO.mainClass) 430 .assertSuccess() 431 .resultChecker(r -> assertModuleData(r, FOO)); 432 } 433 434 @Test 435 public void partialUpdateFooAllFilesAndAttributes() throws IOException { 436 Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes"); 437 createTestDir(mp); 438 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 439 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 440 441 // all attributes and files 442 jar("--create", 443 "--file=" + modularJar.toString(), 444 "--main-class=" + FOO.mainClass, 445 "--module-version=" + FOO.version, 446 "--no-manifest", 447 "-C", modClasses.toString(), ".") 448 .assertSuccess(); 449 jar("--update", 450 "--file=" + modularJar.toString(), 451 "--main-class=" + FOO.mainClass, 452 "--module-version=" + FOO.version, 453 "--no-manifest", 454 "-C", modClasses.toString(), ".") 455 .assertSuccess(); 456 java(mp, FOO.moduleName + "/" + FOO.mainClass) 457 .assertSuccess() 458 .resultChecker(r -> assertModuleData(r, FOO)); 459 } 460 461 @Test 462 public void partialUpdateFooModuleInfo() throws IOException { 463 Path mp = Paths.get("partialUpdateFooModuleInfo"); 464 createTestDir(mp); 465 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 466 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 467 Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName); 468 469 jar("--create", 470 "--file=" + modularJar.toString(), 471 "--main-class=" + FOO.mainClass, 472 "--module-version=" + FOO.version, 473 "--no-manifest", 474 "-C", modClasses.toString(), ".") 475 .assertSuccess(); 476 jar("--update", 477 "--file=" + modularJar.toString(), 478 "--no-manifest", 479 "-C", barModInfo.toString(), "module-info.class") // stuff in bar's info 480 .assertSuccess(); 481 jar("-d", 482 "--file=" + modularJar.toString()) 483 .assertSuccess() 484 .resultChecker(r -> { 485 // Expect similar output: "bar, requires mandated foo, ... 486 // conceals jdk.test.foo, conceals jdk.test.foo.internal" 487 Pattern p = Pattern.compile("\\s+bar\\s+requires\\s++foo"); 488 assertTrue(p.matcher(r.output).find(), 489 "Expecting to find \"bar, requires foo,...\"", 490 "in output, but did not: [" + r.output + "]"); 491 p = Pattern.compile( 492 "contains\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal"); 493 assertTrue(p.matcher(r.output).find(), 494 "Expecting to find \"contains jdk.test.foo,...\"", 495 "in output, but did not: [" + r.output + "]"); 496 }); 497 } 498 499 500 @Test 501 public void partialUpdateFooPackagesAttribute() throws IOException { 502 Path mp = Paths.get("partialUpdateFooPackagesAttribute"); 503 createTestDir(mp); 504 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 505 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 506 507 // Not all files, and none from non-exported packages, 508 // i.e. no concealed list in first create 509 jar("--create", 510 "--file=" + modularJar.toString(), 511 "--no-manifest", 512 "-C", modClasses.toString(), "module-info.class", 513 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 514 .assertSuccess(); 515 516 assertSetsEqual(readPackagesAttribute(modularJar), 517 Set.of("jdk.test.foo")); 518 519 jar("--update", 520 "--file=" + modularJar.toString(), 521 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 522 .assertSuccess(); 523 524 assertSetsEqual(readPackagesAttribute(modularJar), 525 Set.of("jdk.test.foo", "jdk.test.foo.resources")); 526 527 jar("--update", 528 "--file=" + modularJar.toString(), 529 "--main-class=" + FOO.mainClass, 530 "--module-version=" + FOO.version, 531 "--no-manifest", 532 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 533 .assertSuccess(); 534 535 assertSetsEqual(readPackagesAttribute(modularJar), 536 Set.of("jdk.test.foo", 537 "jdk.test.foo.resources", 538 "jdk.test.foo.internal")); 539 540 java(mp, FOO.moduleName + "/" + FOO.mainClass) 541 .assertSuccess() 542 .resultChecker(r -> assertModuleData(r, FOO)); 543 } 544 545 private Set<String> readPackagesAttribute(Path jar) { 546 return getModuleDescriptor(jar).packages(); 547 } 548 549 @Test 550 public void hashBarInFooModule() throws IOException { 551 Path mp = Paths.get("dependencesFooBar"); 552 createTestDir(mp); 553 554 Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName); 555 Path modularJar = mp.resolve(BAR.moduleName + ".jar"); 556 jar("--create", 557 "--file=" + modularJar.toString(), 558 "--main-class=" + BAR.mainClass, 559 "--module-version=" + BAR.version, 560 "--no-manifest", 561 "-C", modClasses.toString(), ".") 562 .assertSuccess(); 563 564 modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 565 modularJar = mp.resolve(FOO.moduleName + ".jar"); 566 jar("--create", 567 "--file=" + modularJar.toString(), 568 "--main-class=" + FOO.mainClass, 569 "--module-version=" + FOO.version, 570 "--module-path=" + mp.toString(), 571 "--hash-modules=" + "bar", 572 "--no-manifest", 573 "-C", modClasses.toString(), ".") 574 .assertSuccess(); 575 576 java(mp, BAR.moduleName + "/" + BAR.mainClass, 577 "--add-exports", "java.base/jdk.internal.misc=bar", 578 "--add-exports", "java.base/jdk.internal.module=bar") 579 .assertSuccess() 580 .resultChecker(r -> { 581 assertModuleData(r, BAR); 582 TestModuleData received = TestModuleData.from(r.output); 583 assertTrue(received.hashes != null, "Expected non-null hashes value."); 584 }); 585 } 586 587 @Test 588 public void invalidHashInFooModule() throws IOException { 589 Path mp = Paths.get("badDependencyFooBar"); 590 createTestDir(mp); 591 592 Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName); 593 Path barJar = mp.resolve(BAR.moduleName + ".jar"); 594 jar("--create", 595 "--file=" + barJar.toString(), 596 "--main-class=" + BAR.mainClass, 597 "--module-version=" + BAR.version, 598 "--no-manifest", 599 "-C", barClasses.toString(), ".").assertSuccess(); 600 601 Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName); 602 Path fooJar = mp.resolve(FOO.moduleName + ".jar"); 603 jar("--create", 604 "--file=" + fooJar.toString(), 605 "--main-class=" + FOO.mainClass, 606 "--module-version=" + FOO.version, 607 "-p", mp.toString(), // test short-form 608 "--hash-modules=" + "bar", 609 "--no-manifest", 610 "-C", fooClasses.toString(), ".").assertSuccess(); 611 612 // Rebuild bar.jar with a change that will cause its hash to be different 613 FileUtils.deleteFileWithRetry(barJar); 614 jar("--create", 615 "--file=" + barJar.toString(), 616 "--main-class=" + BAR.mainClass, 617 "--module-version=" + BAR.version + ".1", // a newer version 618 "--no-manifest", 619 "-C", barClasses.toString(), ".").assertSuccess(); 620 621 java(mp, BAR.moduleName + "/" + BAR.mainClass, 622 "--add-exports", "java.base/jdk.internal.misc=bar", 623 "--add-exports", "java.base/jdk.internal.module=bar") 624 .assertFailure() 625 .resultChecker(r -> { 626 // Expect similar output: "java.lang.module.ResolutionException: Hash 627 // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to 628 // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)" 629 Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*"); 630 assertTrue(p.matcher(r.output).find(), 631 "Expecting error message containing \"Hash of bar ... differs to" 632 + " expected hash...\" but got: [", r.output + "]"); 633 }); 634 } 635 636 @Test 637 public void badOptionsFoo() throws IOException { 638 Path mp = Paths.get("badOptionsFoo"); 639 createTestDir(mp); 640 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 641 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 642 643 jar("--create", 644 "--file=" + modularJar.toString(), 645 "--module-version=" + 1.1, // no module-info.class 646 "-C", modClasses.toString(), "jdk") 647 .assertFailure(); // TODO: expected failure message 648 649 jar("--create", 650 "--file=" + modularJar.toString(), 651 "--hash-modules=" + ".*", // no module-info.class 652 "-C", modClasses.toString(), "jdk") 653 .assertFailure(); // TODO: expected failure message 654 } 655 656 @Test 657 public void servicesCreateWithoutFailure() throws IOException { 658 Path mp = Paths.get("servicesCreateWithoutFailure"); 659 createTestDir(mp); 660 Path modClasses = MODULE_CLASSES.resolve("baz"); 661 Path modularJar = mp.resolve("baz" + ".jar"); 662 663 // Positive test, create 664 jar("--create", 665 "--file=" + modularJar.toString(), 666 "-C", modClasses.toString(), "module-info.class", 667 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 668 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 669 .assertSuccess(); 670 } 671 672 @Test 673 public void servicesCreateWithoutServiceImpl() throws IOException { 674 Path mp = Paths.get("servicesWithoutServiceImpl"); 675 createTestDir(mp); 676 Path modClasses = MODULE_CLASSES.resolve("baz"); 677 Path modularJar = mp.resolve("baz" + ".jar"); 678 679 // Omit service impl 680 jar("--create", 681 "--file=" + modularJar.toString(), 682 "-C", modClasses.toString(), "module-info.class", 683 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 684 .assertFailure(); 685 } 686 687 @Test 688 public void servicesUpdateWithoutFailure() throws IOException { 689 Path mp = Paths.get("servicesUpdateWithoutFailure"); 690 createTestDir(mp); 691 Path modClasses = MODULE_CLASSES.resolve("baz"); 692 Path modularJar = mp.resolve("baz" + ".jar"); 693 694 // Positive test, update 695 jar("--create", 696 "--file=" + modularJar.toString(), 697 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 698 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 699 .assertSuccess(); 700 jar("--update", 701 "--file=" + modularJar.toString(), 702 "-C", modClasses.toString(), "module-info.class") 703 .assertSuccess(); 704 } 705 706 @Test 707 public void servicesUpdateWithoutServiceImpl() throws IOException { 708 Path mp = Paths.get("servicesUpdateWithoutServiceImpl"); 709 createTestDir(mp); 710 Path modClasses = MODULE_CLASSES.resolve("baz"); 711 Path modularJar = mp.resolve("baz" + ".jar"); 712 713 // Omit service impl 714 jar("--create", 715 "--file=" + modularJar.toString(), 716 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 717 .assertSuccess(); 718 jar("--update", 719 "--file=" + modularJar.toString(), 720 "-C", modClasses.toString(), "module-info.class") 721 .assertFailure(); 722 } 723 724 @Test 725 public void servicesCreateWithoutFailureMRMJAR() throws IOException { 726 Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR"); 727 createTestDir(mp); 728 Path modClasses = MODULE_CLASSES.resolve("baz"); 729 Path mrjarDir = MRJAR_DIR.resolve("baz"); 730 Path modularJar = mp.resolve("baz" + ".jar"); 731 732 jar("--create", 733 "--file=" + modularJar.toString(), 734 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 735 "-C", modClasses.toString(), "module-info.class", 736 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 737 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 738 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 739 .assertSuccess(); 740 } 741 742 @Test 743 public void exportCreateWithMissingPkg() throws IOException { 744 745 Path foobar = TEST_SRC.resolve("src").resolve("foobar"); 746 Path dst = Files.createDirectories(MODULE_CLASSES.resolve("foobar")); 747 javac(dst, null, sourceList(foobar)); 748 749 Path mp = Paths.get("exportWithMissingPkg"); 750 createTestDir(mp); 751 Path modClasses = dst; 752 Path modularJar = mp.resolve("foofoo.jar"); 753 754 jar("--create", 755 "--file=" + modularJar.toString(), 756 "-C", modClasses.toString(), "module-info.class", 757 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 758 .assertFailure(); 759 } 760 761 @Test 762 public void printModuleDescriptorFoo() throws IOException { 763 Path mp = Paths.get("printModuleDescriptorFoo"); 764 createTestDir(mp); 765 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 766 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 767 768 jar("--create", 769 "--file=" + modularJar.toString(), 770 "--main-class=" + FOO.mainClass, 771 "--module-version=" + FOO.version, 772 "--no-manifest", 773 "-C", modClasses.toString(), ".") 774 .assertSuccess(); 775 776 for (String option : new String[] {"--print-module-descriptor", "-d" }) { 777 jar(option, 778 "--file=" + modularJar.toString()) 779 .assertSuccess() 780 .resultChecker(r -> 781 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 782 "Expected to find ", FOO.moduleName + "@" + FOO.version, 783 " in [", r.output, "]") 784 ); 785 786 jar(option, 787 "--file=" + modularJar.toString(), 788 modularJar.toString()) 789 .assertFailure(); 790 791 jar(option, modularJar.toString()) 792 .assertFailure(); 793 } 794 } 795 796 @Test 797 public void printModuleDescriptorFooFromStdin() throws IOException { 798 Path mp = Paths.get("printModuleDescriptorFooFromStdin"); 799 createTestDir(mp); 800 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 801 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 802 803 jar("--create", 804 "--file=" + modularJar.toString(), 805 "--main-class=" + FOO.mainClass, 806 "--module-version=" + FOO.version, 807 "--no-manifest", 808 "-C", modClasses.toString(), ".") 809 .assertSuccess(); 810 811 for (String option : new String[] {"--print-module-descriptor", "-d" }) { 812 jarWithStdin(modularJar.toFile(), 813 option) 814 .assertSuccess() 815 .resultChecker(r -> 816 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 817 "Expected to find ", FOO.moduleName + "@" + FOO.version, 818 " in [", r.output, "]") 819 ); 820 } 821 } 822 823 // -- Infrastructure 824 825 static Result jarWithStdin(File stdinSource, String... args) { 826 String jar = getJDKTool("jar"); 827 List<String> commands = new ArrayList<>(); 828 commands.add(jar); 829 if (!TOOL_VM_OPTIONS.isEmpty()) { 830 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 831 } 832 Stream.of(args).forEach(commands::add); 833 ProcessBuilder p = new ProcessBuilder(commands); 834 if (stdinSource != null) 835 p.redirectInput(stdinSource); 836 return run(p); 837 } 838 839 static Result jar(String... args) { 840 return jarWithStdin(null, args); 841 } 842 843 static Path compileModule(String mn) throws IOException { 844 return compileModule(mn, null); 845 } 846 847 static Path compileModule(String mn, Path mp) 848 throws IOException 849 { 850 Path sourcePath = TEST_SRC.resolve("src").resolve(mn); 851 Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn)); 852 javac(build, mp, sourceList(sourcePath)); 853 return build; 854 } 855 856 static void copyResource(Path srcDir, Path dir, String resource) 857 throws IOException 858 { 859 Path dest = dir.resolve(resource); 860 Files.deleteIfExists(dest); 861 862 Files.createDirectories(dest.getParent()); 863 Files.copy(srcDir.resolve(resource), dest); 864 } 865 866 static void setupMRJARModuleInfo(String moduleName) throws IOException { 867 Path modClasses = MODULE_CLASSES.resolve(moduleName); 868 Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF"); 869 Path versionSection = metaInfDir.resolve("versions").resolve("9"); 870 createTestDir(versionSection); 871 872 Path versionModuleInfo = versionSection.resolve("module-info.class"); 873 System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo); 874 Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo); 875 876 Manifest manifest = new Manifest(); 877 manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); 878 manifest.getMainAttributes().putValue("Multi-Release", "true"); 879 try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) { 880 manifest.write(os); 881 } 882 } 883 884 static ModuleDescriptor getModuleDescriptor(Path jar) { 885 ClassLoader cl = ClassLoader.getSystemClassLoader(); 886 try (JarFile jf = new JarFile(jar.toFile())) { 887 JarEntry entry = jf.getJarEntry("module-info.class"); 888 try (InputStream in = jf.getInputStream(entry)) { 889 return ModuleDescriptor.read(in); 890 } 891 } catch (IOException ioe) { 892 throw new UncheckedIOException(ioe); 893 } 894 } 895 896 // Re-enable when there is support in javax.tools for module path 897 // static void javac(Path dest, Path... sourceFiles) throws IOException { 898 // out.printf("Compiling %d source files %s%n", sourceFiles.length, 899 // Arrays.asList(sourceFiles)); 900 // JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 901 // try (StandardJavaFileManager fileManager = 902 // compiler.getStandardFileManager(null, null, null)) { 903 // 904 // List<File> files = Stream.of(sourceFiles) 905 // .map(p -> p.toFile()) 906 // .collect(Collectors.toList()); 907 // List<File> dests = Stream.of(dest) 908 // .map(p -> p.toFile()) 909 // .collect(Collectors.toList()); 910 // Iterable<? extends JavaFileObject> compilationUnits = 911 // fileManager.getJavaFileObjectsFromFiles(files); 912 // fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests); 913 // JavaCompiler.CompilationTask task = 914 // compiler.getTask(null, fileManager, null, null, null, compilationUnits); 915 // boolean passed = task.call(); 916 // if (!passed) 917 // throw new RuntimeException("Error compiling " + files); 918 // } 919 // } 920 921 static void javac(Path dest, Path... sourceFiles) throws IOException { 922 javac(dest, null, sourceFiles); 923 } 924 925 static void javac(Path dest, Path modulePath, Path... sourceFiles) 926 throws IOException 927 { 928 String javac = getJDKTool("javac"); 929 930 List<String> commands = new ArrayList<>(); 931 commands.add(javac); 932 if (!TOOL_VM_OPTIONS.isEmpty()) { 933 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 934 } 935 commands.add("-d"); 936 commands.add(dest.toString()); 937 if (dest.toString().contains("bar")) { 938 commands.add("--add-exports"); 939 commands.add("java.base/jdk.internal.misc=bar"); 940 commands.add("--add-exports"); 941 commands.add("java.base/jdk.internal.module=bar"); 942 } 943 if (modulePath != null) { 944 commands.add("--module-path"); 945 commands.add(modulePath.toString()); 946 } 947 Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x)); 948 949 quickFail(run(new ProcessBuilder(commands))); 950 } 951 952 static Result java(Path modulePath, String entryPoint, String... args) { 953 String java = getJDKTool("java"); 954 955 List<String> commands = new ArrayList<>(); 956 commands.add(java); 957 if (!VM_OPTIONS.isEmpty()) { 958 commands.addAll(Arrays.asList(VM_OPTIONS.split("\\s+", -1))); 959 } 960 if (!JAVA_OPTIONS.isEmpty()) { 961 commands.addAll(Arrays.asList(JAVA_OPTIONS.split("\\s+", -1))); 962 } 963 Stream.of(args).forEach(x -> commands.add(x)); 964 commands.add("--module-path"); 965 commands.add(modulePath.toString()); 966 commands.add("-m"); 967 commands.add(entryPoint); 968 969 return run(new ProcessBuilder(commands)); 970 } 971 972 static Path[] sourceList(Path directory) throws IOException { 973 return Files.find(directory, Integer.MAX_VALUE, 974 (file, attrs) -> (file.toString().endsWith(".java"))) 975 .toArray(Path[]::new); 976 } 977 978 static void createTestDir(Path p) throws IOException{ 979 if (Files.exists(p)) 980 FileUtils.deleteFileTreeWithRetry(p); 981 Files.createDirectories(p); 982 } 983 984 static boolean jarContains(JarInputStream jis, String entryName) 985 throws IOException 986 { 987 JarEntry e; 988 while((e = jis.getNextJarEntry()) != null) { 989 if (e.getName().equals(entryName)) 990 return true; 991 } 992 return false; 993 } 994 995 static void quickFail(Result r) { 996 if (r.ec != 0) 997 throw new RuntimeException(r.output); 998 } 999 1000 static Result run(ProcessBuilder pb) { 1001 Process p; 1002 out.printf("Running: %s%n", pb.command()); 1003 try { 1004 p = pb.start(); 1005 } catch (IOException e) { 1006 throw new RuntimeException( 1007 format("Couldn't start process '%s'", pb.command()), e); 1008 } 1009 1010 String output; 1011 try { 1012 output = toString(p.getInputStream(), p.getErrorStream()); 1013 } catch (IOException e) { 1014 throw new RuntimeException( 1015 format("Couldn't read process output '%s'", pb.command()), e); 1016 } 1017 1018 try { 1019 p.waitFor(); 1020 } catch (InterruptedException e) { 1021 throw new RuntimeException( 1022 format("Process hasn't finished '%s'", pb.command()), e); 1023 } 1024 return new Result(p.exitValue(), output); 1025 } 1026 1027 static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home") 1028 + File.separator + "bin" + File.separator; 1029 1030 static String getJDKTool(String name) { 1031 try { 1032 return JDKToolFinder.getJDKTool(name); 1033 } catch (Exception x) { 1034 return DEFAULT_IMAGE_BIN + name; 1035 } 1036 } 1037 1038 static String toString(InputStream in1, InputStream in2) throws IOException { 1039 try (ByteArrayOutputStream dst = new ByteArrayOutputStream(); 1040 InputStream concatenated = new SequenceInputStream(in1, in2)) { 1041 concatenated.transferTo(dst); 1042 return new String(dst.toByteArray(), "UTF-8"); 1043 } 1044 } 1045 1046 static class Result { 1047 final int ec; 1048 final String output; 1049 1050 private Result(int ec, String output) { 1051 this.ec = ec; 1052 this.output = output; 1053 } 1054 Result assertSuccess() { 1055 assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]"); 1056 return this; 1057 } 1058 Result assertFailure() { 1059 assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]"); 1060 return this; 1061 } 1062 Result resultChecker(Consumer<Result> r) { r.accept(this); return this; } 1063 } 1064 1065 static void assertTrue(boolean cond, Object ... failedArgs) { 1066 if (cond) 1067 return; 1068 StringBuilder sb = new StringBuilder(); 1069 for (Object o : failedArgs) 1070 sb.append(o); 1071 org.testng.Assert.assertTrue(false, sb.toString()); 1072 } 1073 1074 // Standalone entry point. 1075 public static void main(String[] args) throws Throwable { 1076 Basic test = new Basic(); 1077 test.compileModules(); 1078 for (Method m : Basic.class.getDeclaredMethods()) { 1079 if (m.getAnnotation(Test.class) != null) { 1080 System.out.println("Invoking " + m.getName()); 1081 m.invoke(test); 1082 } 1083 } 1084 } 1085 }