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.DataProvider; 43 import org.testng.annotations.Test; 44 45 import static java.lang.String.format; 46 import static java.lang.System.out; 47 48 /* 49 * @test 50 * @bug 8167328 8171830 8165640 8174248 8176772 51 * @library /lib/testlibrary 52 * @modules jdk.compiler 53 * jdk.jartool 54 * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder 55 * @compile Basic.java 56 * @run testng Basic 57 * @summary Tests for plain Modular jars & Multi-Release Modular jars 58 */ 59 60 public class Basic { 61 static final Path TEST_SRC = Paths.get(System.getProperty("test.src", ".")); 62 static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", ".")); 63 static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build"); 64 static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar"); 65 66 static final String VM_OPTIONS = System.getProperty("test.vm.opts", ""); 67 static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", ""); 68 static final String JAVA_OPTIONS = System.getProperty("test.java.opts", ""); 69 70 // Details based on the checked in module source 71 static TestModuleData FOO = new TestModuleData("foo", 72 "1.123", 73 "jdk.test.foo.Foo", 74 "Hello World!!!", 75 null, // no hashes 76 Set.of("java.base"), 77 Set.of("jdk.test.foo"), 78 null, // no uses 79 null, // no provides 80 Set.of("jdk.test.foo.internal", 81 "jdk.test.foo.resources")); 82 static TestModuleData BAR = new TestModuleData("bar", 83 "4.5.6.7", 84 "jdk.test.bar.Bar", 85 "Hello from Bar!", 86 null, // no hashes 87 Set.of("java.base", "foo"), 88 null, // no exports 89 null, // no uses 90 null, // no provides 91 Set.of("jdk.test.bar", 92 "jdk.test.bar.internal")); 93 94 static class TestModuleData { 95 final String moduleName; 96 final Set<String> requires; 97 final Set<String> exports; 98 final Set<String> uses; 99 final Set<String> provides; 100 final String mainClass; 101 final String version; 102 final String message; 103 final String hashes; 104 final Set<String> packages; 105 106 TestModuleData(String mn, String v, String mc, String m, String h, 107 Set<String> requires, Set<String> exports, Set<String> uses, 108 Set<String> provides, Set<String> contains) { 109 moduleName = mn; mainClass = mc; version = v; message = m; hashes = h; 110 this.requires = requires != null ? requires : Collections.emptySet(); 111 this.exports = exports != null ? exports : Collections.emptySet(); 112 this.uses = uses != null ? uses : Collections.emptySet();; 113 this.provides = provides != null ? provides : Collections.emptySet(); 114 this.packages = Stream.concat(this.exports.stream(), contains.stream()) 115 .collect(Collectors.toSet()); 116 } 117 static TestModuleData from(String s) { 118 try { 119 BufferedReader reader = new BufferedReader(new StringReader(s)); 120 String line; 121 String message = null; 122 String name = null, version = null, mainClass = null; 123 String hashes = null; 124 Set<String> requires, exports, uses, provides, conceals; 125 requires = exports = uses = provides = conceals = null; 126 while ((line = reader.readLine()) != null) { 127 if (line.startsWith("message:")) { 128 message = line.substring("message:".length()); 129 } else if (line.startsWith("nameAndVersion:")) { 130 line = line.substring("nameAndVersion:".length()); 131 int i = line.indexOf('@'); 132 if (i != -1) { 133 name = line.substring(0, i); 134 version = line.substring(i + 1, line.length()); 135 } else { 136 name = line; 137 } 138 } else if (line.startsWith("mainClass:")) { 139 mainClass = line.substring("mainClass:".length()); 140 } else if (line.startsWith("requires:")) { 141 line = line.substring("requires:".length()); 142 requires = stringToSet(line); 143 } else if (line.startsWith("exports:")) { 144 line = line.substring("exports:".length()); 145 exports = stringToSet(line); 146 } else if (line.startsWith("uses:")) { 147 line = line.substring("uses:".length()); 148 uses = stringToSet(line); 149 } else if (line.startsWith("provides:")) { 150 line = line.substring("provides:".length()); 151 provides = stringToSet(line); 152 } else if (line.startsWith("hashes:")) { 153 hashes = line.substring("hashes:".length()); 154 } else if (line.startsWith("contains:")) { 155 line = line.substring("contains:".length()); 156 conceals = stringToSet(line); 157 } else { 158 throw new AssertionError("Unknown value " + line); 159 } 160 } 161 162 return new TestModuleData(name, version, mainClass, message, 163 hashes, requires, exports, uses, 164 provides, conceals); 165 } catch (IOException x) { 166 throw new UncheckedIOException(x); 167 } 168 } 169 static Set<String> stringToSet(String commaList) { 170 Set<String> s = new HashSet<>(); 171 int i = commaList.indexOf(','); 172 if (i != -1) { 173 String[] p = commaList.split(","); 174 Stream.of(p).forEach(s::add); 175 } else { 176 s.add(commaList); 177 } 178 return s; 179 } 180 } 181 182 static void assertModuleData(Result r, TestModuleData expected) { 183 //out.printf("%s%n", r.output); 184 TestModuleData received = TestModuleData.from(r.output); 185 if (expected.message != null) 186 assertTrue(expected.message.equals(received.message), 187 "Expected message:", expected.message, ", got:", received.message); 188 assertTrue(expected.moduleName.equals(received.moduleName), 189 "Expected moduleName: ", expected.moduleName, ", got:", received.moduleName); 190 assertTrue(expected.version.equals(received.version), 191 "Expected version: ", expected.version, ", got:", received.version); 192 assertTrue(expected.mainClass.equals(received.mainClass), 193 "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass); 194 assertSetsEqual(expected.requires, received.requires); 195 assertSetsEqual(expected.exports, received.exports); 196 assertSetsEqual(expected.uses, received.uses); 197 assertSetsEqual(expected.provides, received.provides); 198 assertSetsEqual(expected.packages, received.packages); 199 } 200 201 static void assertSetsEqual(Set<String> s1, Set<String> s2) { 202 if (!s1.equals(s2)) { 203 org.testng.Assert.assertTrue(false, s1 + " vs " + s2); 204 } 205 } 206 207 @BeforeTest 208 public void compileModules() throws Exception { 209 compileModule(FOO.moduleName); 210 compileModule(BAR.moduleName, MODULE_CLASSES); 211 compileModule("baz"); // for service provider consistency checking 212 213 // copy resources 214 copyResource(TEST_SRC.resolve("src").resolve(FOO.moduleName), 215 MODULE_CLASSES.resolve(FOO.moduleName), 216 "jdk/test/foo/resources/foo.properties"); 217 218 setupMRJARModuleInfo(FOO.moduleName); 219 setupMRJARModuleInfo(BAR.moduleName); 220 setupMRJARModuleInfo("baz"); 221 } 222 223 @Test 224 public void createFoo() throws IOException { 225 Path mp = Paths.get("createFoo"); 226 createTestDir(mp); 227 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 228 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 229 230 jar("--create", 231 "--file=" + modularJar.toString(), 232 "--main-class=" + FOO.mainClass, 233 "--module-version=" + FOO.version, 234 "--no-manifest", 235 "-C", modClasses.toString(), ".") 236 .assertSuccess(); 237 238 assertSetsEqual(readPackagesAttribute(modularJar), 239 Set.of("jdk.test.foo", 240 "jdk.test.foo.resources", 241 "jdk.test.foo.internal")); 242 243 java(mp, FOO.moduleName + "/" + FOO.mainClass) 244 .assertSuccess() 245 .resultChecker(r -> assertModuleData(r, FOO)); 246 try (InputStream fis = Files.newInputStream(modularJar); 247 JarInputStream jis = new JarInputStream(fis)) { 248 assertTrue(!jarContains(jis, "./"), 249 "Unexpected ./ found in ", modularJar.toString()); 250 } 251 } 252 253 /** Similar to createFoo, but with a Multi-Release Modular jar. */ 254 @Test 255 public void createMRMJarFoo() throws IOException { 256 Path mp = Paths.get("createMRMJarFoo"); 257 createTestDir(mp); 258 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 259 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 260 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 261 262 // Positive test, create 263 jar("--create", 264 "--file=" + modularJar.toString(), 265 "--main-class=" + FOO.mainClass, 266 "--module-version=" + FOO.version, 267 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 268 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 269 "-C", modClasses.toString(), ".") 270 .assertSuccess(); 271 java(mp, FOO.moduleName + "/" + FOO.mainClass) 272 .assertSuccess() 273 .resultChecker(r -> assertModuleData(r, FOO)); 274 } 275 276 277 @Test 278 public void updateFoo() throws IOException { 279 Path mp = Paths.get("updateFoo"); 280 createTestDir(mp); 281 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 282 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 283 284 jar("--create", 285 "--file=" + modularJar.toString(), 286 "--no-manifest", 287 "-C", modClasses.toString(), "jdk") 288 .assertSuccess(); 289 jar("--update", 290 "--file=" + modularJar.toString(), 291 "--main-class=" + FOO.mainClass, 292 "--module-version=" + FOO.version, 293 "--no-manifest", 294 "-C", modClasses.toString(), "module-info.class") 295 .assertSuccess(); 296 java(mp, FOO.moduleName + "/" + FOO.mainClass) 297 .assertSuccess() 298 .resultChecker(r -> assertModuleData(r, FOO)); 299 } 300 301 @Test 302 public void updateMRMJarFoo() throws IOException { 303 Path mp = Paths.get("updateMRMJarFoo"); 304 createTestDir(mp); 305 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 306 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 307 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 308 309 jar("--create", 310 "--file=" + modularJar.toString(), 311 "--no-manifest", 312 "-C", modClasses.toString(), "jdk") 313 .assertSuccess(); 314 jar("--update", 315 "--file=" + modularJar.toString(), 316 "--main-class=" + FOO.mainClass, 317 "--module-version=" + FOO.version, 318 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 319 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 320 "-C", modClasses.toString(), "module-info.class") 321 .assertSuccess(); 322 java(mp, FOO.moduleName + "/" + FOO.mainClass) 323 .assertSuccess() 324 .resultChecker(r -> assertModuleData(r, FOO)); 325 } 326 327 @Test 328 public void partialUpdateFooMainClass() throws IOException { 329 Path mp = Paths.get("partialUpdateFooMainClass"); 330 createTestDir(mp); 331 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 332 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 333 334 // A "bad" main class in first create ( and no version ) 335 jar("--create", 336 "--file=" + modularJar.toString(), 337 "--main-class=" + "jdk.test.foo.IAmNotTheEntryPoint", 338 "--no-manifest", 339 "-C", modClasses.toString(), ".") // includes module-info.class 340 .assertSuccess(); 341 jar("--update", 342 "--file=" + modularJar.toString(), 343 "--main-class=" + FOO.mainClass, 344 "--module-version=" + FOO.version, 345 "--no-manifest") 346 .assertSuccess(); 347 java(mp, FOO.moduleName + "/" + FOO.mainClass) 348 .assertSuccess() 349 .resultChecker(r -> assertModuleData(r, FOO)); 350 } 351 352 @Test 353 public void partialUpdateFooVersion() throws IOException { 354 Path mp = Paths.get("partialUpdateFooVersion"); 355 createTestDir(mp); 356 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 357 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 358 359 // A "bad" version in first create ( and no main class ) 360 jar("--create", 361 "--file=" + modularJar.toString(), 362 "--module-version=" + "100000000", 363 "--no-manifest", 364 "-C", modClasses.toString(), ".") // includes module-info.class 365 .assertSuccess(); 366 jar("--update", 367 "--file=" + modularJar.toString(), 368 "--main-class=" + FOO.mainClass, 369 "--module-version=" + FOO.version, 370 "--no-manifest") 371 .assertSuccess(); 372 java(mp, FOO.moduleName + "/" + FOO.mainClass) 373 .assertSuccess() 374 .resultChecker(r -> assertModuleData(r, FOO)); 375 } 376 377 @Test 378 public void partialUpdateFooNotAllFiles() throws IOException { 379 Path mp = Paths.get("partialUpdateFooNotAllFiles"); 380 createTestDir(mp); 381 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 382 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 383 384 // Not all files, and none from non-exported packages, 385 // i.e. no concealed list in first create 386 jar("--create", 387 "--file=" + modularJar.toString(), 388 "--no-manifest", 389 "-C", modClasses.toString(), "module-info.class", 390 "-C", modClasses.toString(), "jdk/test/foo/Foo.class", 391 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 392 .assertSuccess(); 393 jar("--update", 394 "--file=" + modularJar.toString(), 395 "--main-class=" + FOO.mainClass, 396 "--module-version=" + FOO.version, 397 "--no-manifest", 398 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 399 .assertSuccess(); 400 java(mp, FOO.moduleName + "/" + FOO.mainClass) 401 .assertSuccess() 402 .resultChecker(r -> assertModuleData(r, FOO)); 403 } 404 405 @Test 406 public void partialUpdateMRMJarFooNotAllFiles() throws IOException { 407 Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles"); 408 createTestDir(mp); 409 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 410 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 411 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 412 413 jar("--create", 414 "--file=" + modularJar.toString(), 415 "--module-version=" + FOO.version, 416 "-C", modClasses.toString(), ".") 417 .assertSuccess(); 418 jar("--update", 419 "--file=" + modularJar.toString(), 420 "--main-class=" + FOO.mainClass, 421 "--module-version=" + FOO.version, 422 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 423 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class") 424 .assertSuccess(); 425 java(mp, FOO.moduleName + "/" + FOO.mainClass) 426 .assertSuccess() 427 .resultChecker(r -> assertModuleData(r, FOO)); 428 } 429 430 @Test 431 public void partialUpdateFooAllFilesAndAttributes() throws IOException { 432 Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes"); 433 createTestDir(mp); 434 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 435 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 436 437 // all attributes and files 438 jar("--create", 439 "--file=" + modularJar.toString(), 440 "--main-class=" + FOO.mainClass, 441 "--module-version=" + FOO.version, 442 "--no-manifest", 443 "-C", modClasses.toString(), ".") 444 .assertSuccess(); 445 jar("--update", 446 "--file=" + modularJar.toString(), 447 "--main-class=" + FOO.mainClass, 448 "--module-version=" + FOO.version, 449 "--no-manifest", 450 "-C", modClasses.toString(), ".") 451 .assertSuccess(); 452 java(mp, FOO.moduleName + "/" + FOO.mainClass) 453 .assertSuccess() 454 .resultChecker(r -> assertModuleData(r, FOO)); 455 } 456 457 @Test 458 public void partialUpdateFooModuleInfo() throws IOException { 459 Path mp = Paths.get("partialUpdateFooModuleInfo"); 460 createTestDir(mp); 461 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 462 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 463 Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName); 464 465 jar("--create", 466 "--file=" + modularJar.toString(), 467 "--main-class=" + FOO.mainClass, 468 "--module-version=" + FOO.version, 469 "--no-manifest", 470 "-C", modClasses.toString(), ".") 471 .assertSuccess(); 472 jar("--update", 473 "--file=" + modularJar.toString(), 474 "--no-manifest", 475 "-C", barModInfo.toString(), "module-info.class") // stuff in bar's info 476 .assertSuccess(); 477 jar("-d", 478 "--file=" + modularJar.toString()) 479 .assertSuccess() 480 .resultChecker(r -> { 481 // Expect similar output: "bar, requires mandated foo, ... 482 // conceals jdk.test.foo, conceals jdk.test.foo.internal" 483 Pattern p = Pattern.compile("module bar \\(module-info.class\\)\\s+requires\\s++foo"); 484 assertTrue(p.matcher(r.output).find(), 485 "Expecting to find \"bar, requires foo,...\"", 486 "in output, but did not: [" + r.output + "]"); 487 p = Pattern.compile( 488 "contains\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal"); 489 assertTrue(p.matcher(r.output).find(), 490 "Expecting to find \"contains jdk.test.foo,...\"", 491 "in output, but did not: [" + r.output + "]"); 492 }); 493 } 494 495 496 @Test 497 public void partialUpdateFooPackagesAttribute() throws IOException { 498 Path mp = Paths.get("partialUpdateFooPackagesAttribute"); 499 createTestDir(mp); 500 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 501 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 502 503 // Not all files, and none from non-exported packages, 504 // i.e. no concealed list in first create 505 jar("--create", 506 "--file=" + modularJar.toString(), 507 "--no-manifest", 508 "-C", modClasses.toString(), "module-info.class", 509 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 510 .assertSuccess(); 511 512 assertSetsEqual(readPackagesAttribute(modularJar), 513 Set.of("jdk.test.foo")); 514 515 jar("--update", 516 "--file=" + modularJar.toString(), 517 "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties") 518 .assertSuccess(); 519 520 assertSetsEqual(readPackagesAttribute(modularJar), 521 Set.of("jdk.test.foo", "jdk.test.foo.resources")); 522 523 jar("--update", 524 "--file=" + modularJar.toString(), 525 "--main-class=" + FOO.mainClass, 526 "--module-version=" + FOO.version, 527 "--no-manifest", 528 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 529 .assertSuccess(); 530 531 assertSetsEqual(readPackagesAttribute(modularJar), 532 Set.of("jdk.test.foo", 533 "jdk.test.foo.resources", 534 "jdk.test.foo.internal")); 535 536 java(mp, FOO.moduleName + "/" + FOO.mainClass) 537 .assertSuccess() 538 .resultChecker(r -> assertModuleData(r, FOO)); 539 } 540 541 private Set<String> readPackagesAttribute(Path jar) { 542 return getModuleDescriptor(jar).packages(); 543 } 544 545 @Test 546 public void hashBarInFooModule() throws IOException { 547 Path mp = Paths.get("dependencesFooBar"); 548 createTestDir(mp); 549 550 Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName); 551 Path modularJar = mp.resolve(BAR.moduleName + ".jar"); 552 jar("--create", 553 "--file=" + modularJar.toString(), 554 "--main-class=" + BAR.mainClass, 555 "--module-version=" + BAR.version, 556 "--no-manifest", 557 "-C", modClasses.toString(), ".") 558 .assertSuccess(); 559 560 modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 561 modularJar = mp.resolve(FOO.moduleName + ".jar"); 562 jar("--create", 563 "--file=" + modularJar.toString(), 564 "--main-class=" + FOO.mainClass, 565 "--module-version=" + FOO.version, 566 "--module-path=" + mp.toString(), 567 "--hash-modules=" + "bar", 568 "--no-manifest", 569 "-C", modClasses.toString(), ".") 570 .assertSuccess(); 571 572 java(mp, BAR.moduleName + "/" + BAR.mainClass, 573 "--add-exports", "java.base/jdk.internal.misc=bar", 574 "--add-exports", "java.base/jdk.internal.module=bar") 575 .assertSuccess() 576 .resultChecker(r -> { 577 assertModuleData(r, BAR); 578 TestModuleData received = TestModuleData.from(r.output); 579 assertTrue(received.hashes != null, "Expected non-null hashes value."); 580 }); 581 } 582 583 @Test 584 public void invalidHashInFooModule() throws IOException { 585 Path mp = Paths.get("badDependencyFooBar"); 586 createTestDir(mp); 587 588 Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName); 589 Path barJar = mp.resolve(BAR.moduleName + ".jar"); 590 jar("--create", 591 "--file=" + barJar.toString(), 592 "--main-class=" + BAR.mainClass, 593 "--module-version=" + BAR.version, 594 "--no-manifest", 595 "-C", barClasses.toString(), ".").assertSuccess(); 596 597 Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName); 598 Path fooJar = mp.resolve(FOO.moduleName + ".jar"); 599 jar("--create", 600 "--file=" + fooJar.toString(), 601 "--main-class=" + FOO.mainClass, 602 "--module-version=" + FOO.version, 603 "-p", mp.toString(), // test short-form 604 "--hash-modules=" + "bar", 605 "--no-manifest", 606 "-C", fooClasses.toString(), ".").assertSuccess(); 607 608 // Rebuild bar.jar with a change that will cause its hash to be different 609 FileUtils.deleteFileWithRetry(barJar); 610 jar("--create", 611 "--file=" + barJar.toString(), 612 "--main-class=" + BAR.mainClass, 613 "--module-version=" + BAR.version + ".1", // a newer version 614 "--no-manifest", 615 "-C", barClasses.toString(), ".").assertSuccess(); 616 617 java(mp, BAR.moduleName + "/" + BAR.mainClass, 618 "--add-exports", "java.base/jdk.internal.misc=bar", 619 "--add-exports", "java.base/jdk.internal.module=bar") 620 .assertFailure() 621 .resultChecker(r -> { 622 // Expect similar output: "java.lang.module.ResolutionException: Hash 623 // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to 624 // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)" 625 Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*"); 626 assertTrue(p.matcher(r.output).find(), 627 "Expecting error message containing \"Hash of bar ... differs to" 628 + " expected hash...\" but got: [", r.output + "]"); 629 }); 630 } 631 632 @Test 633 public void badOptionsFoo() throws IOException { 634 Path mp = Paths.get("badOptionsFoo"); 635 createTestDir(mp); 636 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 637 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 638 639 jar("--create", 640 "--file=" + modularJar.toString(), 641 "--module-version=" + 1.1, // no module-info.class 642 "-C", modClasses.toString(), "jdk") 643 .assertFailure(); // TODO: expected failure message 644 645 jar("--create", 646 "--file=" + modularJar.toString(), 647 "--hash-modules=" + ".*", // no module-info.class 648 "-C", modClasses.toString(), "jdk") 649 .assertFailure(); // TODO: expected failure message 650 } 651 652 @Test 653 public void servicesCreateWithoutFailure() throws IOException { 654 Path mp = Paths.get("servicesCreateWithoutFailure"); 655 createTestDir(mp); 656 Path modClasses = MODULE_CLASSES.resolve("baz"); 657 Path modularJar = mp.resolve("baz" + ".jar"); 658 659 // Positive test, create 660 jar("--create", 661 "--file=" + modularJar.toString(), 662 "-C", modClasses.toString(), "module-info.class", 663 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 664 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 665 .assertSuccess(); 666 } 667 668 @Test 669 public void servicesCreateWithoutServiceImpl() throws IOException { 670 Path mp = Paths.get("servicesWithoutServiceImpl"); 671 createTestDir(mp); 672 Path modClasses = MODULE_CLASSES.resolve("baz"); 673 Path modularJar = mp.resolve("baz" + ".jar"); 674 675 // Omit service impl 676 jar("--create", 677 "--file=" + modularJar.toString(), 678 "-C", modClasses.toString(), "module-info.class", 679 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 680 .assertFailure(); 681 } 682 683 @Test 684 public void servicesUpdateWithoutFailure() throws IOException { 685 Path mp = Paths.get("servicesUpdateWithoutFailure"); 686 createTestDir(mp); 687 Path modClasses = MODULE_CLASSES.resolve("baz"); 688 Path modularJar = mp.resolve("baz" + ".jar"); 689 690 // Positive test, update 691 jar("--create", 692 "--file=" + modularJar.toString(), 693 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 694 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 695 .assertSuccess(); 696 jar("--update", 697 "--file=" + modularJar.toString(), 698 "-C", modClasses.toString(), "module-info.class") 699 .assertSuccess(); 700 } 701 702 @Test 703 public void servicesUpdateWithoutServiceImpl() throws IOException { 704 Path mp = Paths.get("servicesUpdateWithoutServiceImpl"); 705 createTestDir(mp); 706 Path modClasses = MODULE_CLASSES.resolve("baz"); 707 Path modularJar = mp.resolve("baz" + ".jar"); 708 709 // Omit service impl 710 jar("--create", 711 "--file=" + modularJar.toString(), 712 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 713 .assertSuccess(); 714 jar("--update", 715 "--file=" + modularJar.toString(), 716 "-C", modClasses.toString(), "module-info.class") 717 .assertFailure(); 718 } 719 720 @Test 721 public void servicesCreateWithoutFailureMRMJAR() throws IOException { 722 Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR"); 723 createTestDir(mp); 724 Path modClasses = MODULE_CLASSES.resolve("baz"); 725 Path mrjarDir = MRJAR_DIR.resolve("baz"); 726 Path modularJar = mp.resolve("baz" + ".jar"); 727 728 jar("--create", 729 "--file=" + modularJar.toString(), 730 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 731 "-C", modClasses.toString(), "module-info.class", 732 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 733 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 734 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 735 .assertSuccess(); 736 } 737 738 @Test 739 public void servicesCreateWithoutFailureNonRootMRMJAR() throws IOException { 740 // without a root module-info.class 741 Path mp = Paths.get("servicesCreateWithoutFailureNonRootMRMJAR"); 742 createTestDir(mp); 743 Path modClasses = MODULE_CLASSES.resolve("baz"); 744 Path mrjarDir = MRJAR_DIR.resolve("baz"); 745 Path modularJar = mp.resolve("baz.jar"); 746 747 jar("--create", 748 "--file=" + modularJar.toString(), 749 "--main-class=" + "jdk.test.baz.Baz", 750 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 751 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 752 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 753 "-C", modClasses.toString(), "jdk/test/baz/Baz.class", 754 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 755 .assertSuccess(); 756 757 758 for (String option : new String[] {"--describe-module", "-d" }) { 759 760 jar(option, 761 "--file=" + modularJar.toString()) 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) 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 maain 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 { "foo4j.jar", "foo4j", }, 873 { "foo1.2.3.jar", "foo" }, 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("module " + mid), 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 }