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 8165640 8174248 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 -> 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 similar output: "bar, requires mandated foo, ... 481 // conceals jdk.test.foo, conceals jdk.test.foo.internal" 482 Pattern p = Pattern.compile("module bar \\(module-info.class\\)\\s+requires\\s++foo"); 483 assertTrue(p.matcher(r.output).find(), 484 "Expecting to find \"bar, requires foo,...\"", 485 "in output, but did not: [" + r.output + "]"); 486 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[] {"--print-module-descriptor", "-d" }) { 758 759 jar(option, 760 "--file=" + modularJar.toString()) 761 .assertSuccess() 762 .resultChecker(r -> 763 assertTrue(r.output.contains("main-class jdk.test.baz.Baz"), 764 "Expected to find ", "main-class jdk.test.baz.Baz", 765 " in [", r.output, "]")); 766 767 jarWithStdin(modularJar.toFile(), option) 768 .assertSuccess() 769 .resultChecker(r -> 770 assertTrue(r.output.contains("main-class jdk.test.baz.Baz"), 771 "Expected to find ", "main-class jdk.test.baz.Baz", 772 " in [", r.output, "]")); 773 774 } 775 // run module maain class 776 java(mp, "baz/jdk.test.baz.Baz") 777 .assertSuccess() 778 .resultChecker(r -> 779 assertTrue(r.output.contains("mainClass:jdk.test.baz.Baz"), 780 "Expected to find ", "mainClass:jdk.test.baz.Baz", 781 " in [", r.output, "]")); 782 } 783 784 @Test 785 public void exportCreateWithMissingPkg() throws IOException { 786 787 Path foobar = TEST_SRC.resolve("src").resolve("foobar"); 788 Path dst = Files.createDirectories(MODULE_CLASSES.resolve("foobar")); 789 javac(dst, null, sourceList(foobar)); 790 791 Path mp = Paths.get("exportWithMissingPkg"); 792 createTestDir(mp); 793 Path modClasses = dst; 794 Path modularJar = mp.resolve("foofoo.jar"); 795 796 jar("--create", 797 "--file=" + modularJar.toString(), 798 "-C", modClasses.toString(), "module-info.class", 799 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 800 .assertFailure(); 801 } 802 803 @Test 804 public void printModuleDescriptorFoo() throws IOException { 805 Path mp = Paths.get("printModuleDescriptorFoo"); 806 createTestDir(mp); 807 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 808 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 809 810 jar("--create", 811 "--file=" + modularJar.toString(), 812 "--main-class=" + FOO.mainClass, 813 "--module-version=" + FOO.version, 814 "--no-manifest", 815 "-C", modClasses.toString(), ".") 816 .assertSuccess(); 817 818 for (String option : new String[] {"--print-module-descriptor", "-d" }) { 819 jar(option, 820 "--file=" + modularJar.toString()) 821 .assertSuccess() 822 .resultChecker(r -> 823 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 824 "Expected to find ", FOO.moduleName + "@" + FOO.version, 825 " in [", r.output, "]") 826 ); 827 828 jar(option, 829 "--file=" + modularJar.toString(), 830 modularJar.toString()) 831 .assertFailure(); 832 833 jar(option, modularJar.toString()) 834 .assertFailure(); 835 } 836 } 837 838 @Test 839 public void printModuleDescriptorFooFromStdin() throws IOException { 840 Path mp = Paths.get("printModuleDescriptorFooFromStdin"); 841 createTestDir(mp); 842 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 843 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 844 845 jar("--create", 846 "--file=" + modularJar.toString(), 847 "--main-class=" + FOO.mainClass, 848 "--module-version=" + FOO.version, 849 "--no-manifest", 850 "-C", modClasses.toString(), ".") 851 .assertSuccess(); 852 853 for (String option : new String[] {"--print-module-descriptor", "-d" }) { 854 jarWithStdin(modularJar.toFile(), 855 option) 856 .assertSuccess() 857 .resultChecker(r -> 858 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 859 "Expected to find ", FOO.moduleName + "@" + FOO.version, 860 " in [", r.output, "]") 861 ); 862 } 863 } 864 865 // -- Infrastructure 866 867 static Result jarWithStdin(File stdinSource, String... args) { 868 String jar = getJDKTool("jar"); 869 List<String> commands = new ArrayList<>(); 870 commands.add(jar); 871 if (!TOOL_VM_OPTIONS.isEmpty()) { 872 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 873 } 874 Stream.of(args).forEach(commands::add); 875 ProcessBuilder p = new ProcessBuilder(commands); 876 if (stdinSource != null) 877 p.redirectInput(stdinSource); 878 return run(p); 879 } 880 881 static Result jar(String... args) { 882 return jarWithStdin(null, args); 883 } 884 885 static Path compileModule(String mn) throws IOException { 886 return compileModule(mn, null); 887 } 888 889 static Path compileModule(String mn, Path mp) 890 throws IOException 891 { 892 Path sourcePath = TEST_SRC.resolve("src").resolve(mn); 893 Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn)); 894 javac(build, mp, sourceList(sourcePath)); 895 return build; 896 } 897 898 static void copyResource(Path srcDir, Path dir, String resource) 899 throws IOException 900 { 901 Path dest = dir.resolve(resource); 902 Files.deleteIfExists(dest); 903 904 Files.createDirectories(dest.getParent()); 905 Files.copy(srcDir.resolve(resource), dest); 906 } 907 908 static void setupMRJARModuleInfo(String moduleName) throws IOException { 909 Path modClasses = MODULE_CLASSES.resolve(moduleName); 910 Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF"); 911 Path versionSection = metaInfDir.resolve("versions").resolve("9"); 912 createTestDir(versionSection); 913 914 Path versionModuleInfo = versionSection.resolve("module-info.class"); 915 System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo); 916 Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo); 917 918 Manifest manifest = new Manifest(); 919 manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); 920 manifest.getMainAttributes().putValue("Multi-Release", "true"); 921 try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) { 922 manifest.write(os); 923 } 924 } 925 926 static ModuleDescriptor getModuleDescriptor(Path jar) { 927 ClassLoader cl = ClassLoader.getSystemClassLoader(); 928 try (JarFile jf = new JarFile(jar.toFile())) { 929 JarEntry entry = jf.getJarEntry("module-info.class"); 930 try (InputStream in = jf.getInputStream(entry)) { 931 return ModuleDescriptor.read(in); 932 } 933 } catch (IOException ioe) { 934 throw new UncheckedIOException(ioe); 935 } 936 } 937 938 // Re-enable when there is support in javax.tools for module path 939 // static void javac(Path dest, Path... sourceFiles) throws IOException { 940 // out.printf("Compiling %d source files %s%n", sourceFiles.length, 941 // Arrays.asList(sourceFiles)); 942 // JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 943 // try (StandardJavaFileManager fileManager = 944 // compiler.getStandardFileManager(null, null, null)) { 945 // 946 // List<File> files = Stream.of(sourceFiles) 947 // .map(p -> p.toFile()) 948 // .collect(Collectors.toList()); 949 // List<File> dests = Stream.of(dest) 950 // .map(p -> p.toFile()) 951 // .collect(Collectors.toList()); 952 // Iterable<? extends JavaFileObject> compilationUnits = 953 // fileManager.getJavaFileObjectsFromFiles(files); 954 // fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests); 955 // JavaCompiler.CompilationTask task = 956 // compiler.getTask(null, fileManager, null, null, null, compilationUnits); 957 // boolean passed = task.call(); 958 // if (!passed) 959 // throw new RuntimeException("Error compiling " + files); 960 // } 961 // } 962 963 static void javac(Path dest, Path... sourceFiles) throws IOException { 964 javac(dest, null, sourceFiles); 965 } 966 967 static void javac(Path dest, Path modulePath, Path... sourceFiles) 968 throws IOException 969 { 970 String javac = getJDKTool("javac"); 971 972 List<String> commands = new ArrayList<>(); 973 commands.add(javac); 974 if (!TOOL_VM_OPTIONS.isEmpty()) { 975 commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1))); 976 } 977 commands.add("-d"); 978 commands.add(dest.toString()); 979 if (dest.toString().contains("bar")) { 980 commands.add("--add-exports"); 981 commands.add("java.base/jdk.internal.misc=bar"); 982 commands.add("--add-exports"); 983 commands.add("java.base/jdk.internal.module=bar"); 984 } 985 if (modulePath != null) { 986 commands.add("--module-path"); 987 commands.add(modulePath.toString()); 988 } 989 Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x)); 990 991 quickFail(run(new ProcessBuilder(commands))); 992 } 993 994 static Result java(Path modulePath, String entryPoint, String... args) { 995 String java = getJDKTool("java"); 996 997 List<String> commands = new ArrayList<>(); 998 commands.add(java); 999 if (!VM_OPTIONS.isEmpty()) { 1000 commands.addAll(Arrays.asList(VM_OPTIONS.split("\\s+", -1))); 1001 } 1002 if (!JAVA_OPTIONS.isEmpty()) { 1003 commands.addAll(Arrays.asList(JAVA_OPTIONS.split("\\s+", -1))); 1004 } 1005 Stream.of(args).forEach(x -> commands.add(x)); 1006 commands.add("--module-path"); 1007 commands.add(modulePath.toString()); 1008 commands.add("-m"); 1009 commands.add(entryPoint); 1010 1011 return run(new ProcessBuilder(commands)); 1012 } 1013 1014 static Path[] sourceList(Path directory) throws IOException { 1015 return Files.find(directory, Integer.MAX_VALUE, 1016 (file, attrs) -> (file.toString().endsWith(".java"))) 1017 .toArray(Path[]::new); 1018 } 1019 1020 static void createTestDir(Path p) throws IOException{ 1021 if (Files.exists(p)) 1022 FileUtils.deleteFileTreeWithRetry(p); 1023 Files.createDirectories(p); 1024 } 1025 1026 static boolean jarContains(JarInputStream jis, String entryName) 1027 throws IOException 1028 { 1029 JarEntry e; 1030 while((e = jis.getNextJarEntry()) != null) { 1031 if (e.getName().equals(entryName)) 1032 return true; 1033 } 1034 return false; 1035 } 1036 1037 static void quickFail(Result r) { 1038 if (r.ec != 0) 1039 throw new RuntimeException(r.output); 1040 } 1041 1042 static Result run(ProcessBuilder pb) { 1043 Process p; 1044 out.printf("Running: %s%n", pb.command()); 1045 try { 1046 p = pb.start(); 1047 } catch (IOException e) { 1048 throw new RuntimeException( 1049 format("Couldn't start process '%s'", pb.command()), e); 1050 } 1051 1052 String output; 1053 try { 1054 output = toString(p.getInputStream(), p.getErrorStream()); 1055 } catch (IOException e) { 1056 throw new RuntimeException( 1057 format("Couldn't read process output '%s'", pb.command()), e); 1058 } 1059 1060 try { 1061 p.waitFor(); 1062 } catch (InterruptedException e) { 1063 throw new RuntimeException( 1064 format("Process hasn't finished '%s'", pb.command()), e); 1065 } 1066 return new Result(p.exitValue(), output); 1067 } 1068 1069 static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home") 1070 + File.separator + "bin" + File.separator; 1071 1072 static String getJDKTool(String name) { 1073 try { 1074 return JDKToolFinder.getJDKTool(name); 1075 } catch (Exception x) { 1076 return DEFAULT_IMAGE_BIN + name; 1077 } 1078 } 1079 1080 static String toString(InputStream in1, InputStream in2) throws IOException { 1081 try (ByteArrayOutputStream dst = new ByteArrayOutputStream(); 1082 InputStream concatenated = new SequenceInputStream(in1, in2)) { 1083 concatenated.transferTo(dst); 1084 return new String(dst.toByteArray(), "UTF-8"); 1085 } 1086 } 1087 1088 static class Result { 1089 final int ec; 1090 final String output; 1091 1092 private Result(int ec, String output) { 1093 this.ec = ec; 1094 this.output = output; 1095 } 1096 Result assertSuccess() { 1097 assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]"); 1098 return this; 1099 } 1100 Result assertFailure() { 1101 assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]"); 1102 return this; 1103 } 1104 Result resultChecker(Consumer<Result> r) { r.accept(this); return this; } 1105 } 1106 1107 static void assertTrue(boolean cond, Object ... failedArgs) { 1108 if (cond) 1109 return; 1110 StringBuilder sb = new StringBuilder(); 1111 for (Object o : failedArgs) 1112 sb.append(o); 1113 org.testng.Assert.assertTrue(false, sb.toString()); 1114 } 1115 1116 // Standalone entry point. 1117 public static void main(String[] args) throws Throwable { 1118 Basic test = new Basic(); 1119 test.compileModules(); 1120 for (Method m : Basic.class.getDeclaredMethods()) { 1121 if (m.getAnnotation(Test.class) != null) { 1122 System.out.println("Invoking " + m.getName()); 1123 m.invoke(test); 1124 } 1125 } 1126 } 1127 }