1 /* 2 * Copyright (c) 2015, 2016, 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.reflect.Method; 26 import java.nio.file.FileVisitResult; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.nio.file.SimpleFileVisitor; 31 import java.nio.file.attribute.BasicFileAttributes; 32 import java.util.*; 33 import java.util.function.Consumer; 34 import java.util.jar.JarEntry; 35 import java.util.jar.JarInputStream; 36 import java.util.jar.Manifest; 37 import java.util.regex.Pattern; 38 import java.util.stream.Stream; 39 import javax.tools.JavaCompiler; 40 import javax.tools.JavaFileObject; 41 import javax.tools.StandardJavaFileManager; 42 import javax.tools.StandardLocation; 43 import javax.tools.ToolProvider; 44 45 import jdk.testlibrary.FileUtils; 46 import jdk.testlibrary.JDKToolFinder; 47 import org.testng.annotations.BeforeTest; 48 import org.testng.annotations.Test; 49 50 import static java.lang.String.format; 51 import static java.lang.System.out; 52 53 /* 54 * @test 55 * @library /lib/testlibrary 56 * @modules jdk.compiler 57 * jdk.jartool 58 * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder 59 * @compile Basic.java 60 * @run testng Basic 61 * @summary Tests for plain Modular jars & Multi-Release Modular jars 62 */ 63 64 public class Basic { 65 static final Path TEST_SRC = Paths.get(System.getProperty("test.src", ".")); 66 static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", ".")); 67 static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build"); 68 static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar"); 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 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> conceals; 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> conceals) { 108 moduleName = mn; mainClass = mc; version = v; message = m; hashes = h; 109 this.requires = requires; 110 this.exports = exports; 111 this.uses = uses; 112 this.provides = provides; 113 this.conceals = conceals; 114 } 115 static TestModuleData from(String s) { 116 try { 117 BufferedReader reader = new BufferedReader(new StringReader(s)); 118 String line; 119 String message = null; 120 String name = null, version = null, mainClass = null; 121 String hashes = null; 122 Set<String> requires, exports, uses, provides, conceals; 123 requires = exports = uses = provides = conceals = null; 124 while ((line = reader.readLine()) != null) { 125 if (line.startsWith("message:")) { 126 message = line.substring("message:".length()); 127 } else if (line.startsWith("nameAndVersion:")) { 128 line = line.substring("nameAndVersion:".length()); 129 int i = line.indexOf('@'); 130 if (i != -1) { 131 name = line.substring(0, i); 132 version = line.substring(i + 1, line.length()); 133 } else { 134 name = line; 135 } 136 } else if (line.startsWith("mainClass:")) { 137 mainClass = line.substring("mainClass:".length()); 138 } else if (line.startsWith("requires:")) { 139 line = line.substring("requires:".length()); 140 requires = stringToSet(line); 141 } else if (line.startsWith("exports:")) { 142 line = line.substring("exports:".length()); 143 exports = stringToSet(line); 144 } else if (line.startsWith("uses:")) { 145 line = line.substring("uses:".length()); 146 uses = stringToSet(line); 147 } else if (line.startsWith("provides:")) { 148 line = line.substring("provides:".length()); 149 provides = stringToSet(line); 150 } else if (line.startsWith("hashes:")) { 151 hashes = line.substring("hashes:".length()); 152 } else if (line.startsWith("conceals:")) { 153 line = line.substring("conceals:".length()); 154 conceals = stringToSet(line); 155 } else { 156 throw new AssertionError("Unknown value " + line); 157 } 158 } 159 160 return new TestModuleData(name, version, mainClass, message, 161 hashes, requires, exports, uses, 162 provides, conceals); 163 } catch (IOException x) { 164 throw new UncheckedIOException(x); 165 } 166 } 167 static Set<String> stringToSet(String commaList) { 168 Set<String> s = new HashSet<>(); 169 int i = commaList.indexOf(','); 170 if (i != -1) { 171 String[] p = commaList.split(","); 172 Stream.of(p).forEach(s::add); 173 } else { 174 s.add(commaList); 175 } 176 return s; 177 } 178 } 179 180 static void assertModuleData(Result r, TestModuleData expected) { 181 //out.printf("%s%n", r.output); 182 TestModuleData received = TestModuleData.from(r.output); 183 if (expected.message != null) 184 assertTrue(expected.message.equals(received.message), 185 "Expected message:", expected.message, ", got:", received.message); 186 assertTrue(expected.moduleName.equals(received.moduleName), 187 "Expected moduleName: ", expected.moduleName, ", got:", received.moduleName); 188 assertTrue(expected.version.equals(received.version), 189 "Expected version: ", expected.version, ", got:", received.version); 190 assertTrue(expected.mainClass.equals(received.mainClass), 191 "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass); 192 assertSetsEqual(expected.requires, received.requires); 193 assertSetsEqual(expected.exports, received.exports); 194 assertSetsEqual(expected.uses, received.uses); 195 assertSetsEqual(expected.provides, received.provides); 196 assertSetsEqual(expected.conceals, received.conceals); 197 } 198 199 static void assertSetsEqual(Set<String> s1, Set<String> s2) { 200 if (s1 == null && s2 == null) // none expected, or received 201 return; 202 assertTrue(s1.size() == s2.size(), 203 "Unexpected set size difference: ", s1.size(), ", ", s2.size()); 204 s1.forEach(p -> assertTrue(s2.contains(p), "Expected ", p, ", in ", s2)); 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 setupMRJARModuleInfo(FOO.moduleName); 214 setupMRJARModuleInfo(BAR.moduleName); 215 setupMRJARModuleInfo("baz"); 216 } 217 218 @Test 219 public void createFoo() throws IOException { 220 Path mp = Paths.get("createFoo"); 221 createTestDir(mp); 222 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 223 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 224 225 jar("--create", 226 "--file=" + modularJar.toString(), 227 "--main-class=" + FOO.mainClass, 228 "--module-version=" + FOO.version, 229 "--no-manifest", 230 "-C", modClasses.toString(), ".") 231 .assertSuccess(); 232 java(mp, FOO.moduleName + "/" + FOO.mainClass) 233 .assertSuccess() 234 .resultChecker(r -> assertModuleData(r, FOO)); 235 try (InputStream fis = Files.newInputStream(modularJar); 236 JarInputStream jis = new JarInputStream(fis)) { 237 assertTrue(!jarContains(jis, "./"), 238 "Unexpected ./ found in ", modularJar.toString()); 239 } 240 } 241 242 /** Similar to createFoo, but with a Multi-Release Modular jar. */ 243 @Test 244 public void createMRMJarFoo() throws IOException { 245 Path mp = Paths.get("createMRMJarFoo"); 246 createTestDir(mp); 247 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 248 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 249 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 250 251 // Positive test, create 252 jar("--create", 253 "--file=" + modularJar.toString(), 254 "--main-class=" + FOO.mainClass, 255 "--module-version=" + FOO.version, 256 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 257 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 258 "-C", modClasses.toString(), ".") 259 .assertSuccess(); 260 java(mp, FOO.moduleName + "/" + FOO.mainClass) 261 .assertSuccess() 262 .resultChecker(r -> assertModuleData(r, FOO)); 263 } 264 265 266 @Test 267 public void updateFoo() throws IOException { 268 Path mp = Paths.get("updateFoo"); 269 createTestDir(mp); 270 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 271 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 272 273 jar("--create", 274 "--file=" + modularJar.toString(), 275 "--no-manifest", 276 "-C", modClasses.toString(), "jdk") 277 .assertSuccess(); 278 jar("--update", 279 "--file=" + modularJar.toString(), 280 "--main-class=" + FOO.mainClass, 281 "--module-version=" + FOO.version, 282 "--no-manifest", 283 "-C", modClasses.toString(), "module-info.class") 284 .assertSuccess(); 285 java(mp, FOO.moduleName + "/" + FOO.mainClass) 286 .assertSuccess() 287 .resultChecker(r -> assertModuleData(r, FOO)); 288 } 289 290 @Test 291 public void updateMRMJarFoo() throws IOException { 292 Path mp = Paths.get("updateMRMJarFoo"); 293 createTestDir(mp); 294 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 295 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 296 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 297 298 jar("--create", 299 "--file=" + modularJar.toString(), 300 "--no-manifest", 301 "-C", modClasses.toString(), "jdk") 302 .assertSuccess(); 303 jar("--update", 304 "--file=" + modularJar.toString(), 305 "--main-class=" + FOO.mainClass, 306 "--module-version=" + FOO.version, 307 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 308 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 309 "-C", modClasses.toString(), "module-info.class") 310 .assertSuccess(); 311 java(mp, FOO.moduleName + "/" + FOO.mainClass) 312 .assertSuccess() 313 .resultChecker(r -> assertModuleData(r, FOO)); 314 } 315 316 @Test 317 public void partialUpdateFooMainClass() throws IOException { 318 Path mp = Paths.get("partialUpdateFooMainClass"); 319 createTestDir(mp); 320 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 321 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 322 323 // A "bad" main class in first create ( and no version ) 324 jar("--create", 325 "--file=" + modularJar.toString(), 326 "--main-class=" + "IAmNotTheEntryPoint", 327 "--no-manifest", 328 "-C", modClasses.toString(), ".") // includes module-info.class 329 .assertSuccess(); 330 jar("--update", 331 "--file=" + modularJar.toString(), 332 "--main-class=" + FOO.mainClass, 333 "--module-version=" + FOO.version, 334 "--no-manifest") 335 .assertSuccess(); 336 java(mp, FOO.moduleName + "/" + FOO.mainClass) 337 .assertSuccess() 338 .resultChecker(r -> assertModuleData(r, FOO)); 339 } 340 341 @Test 342 public void partialUpdateFooVersion() throws IOException { 343 Path mp = Paths.get("partialUpdateFooVersion"); 344 createTestDir(mp); 345 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 346 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 347 348 // A "bad" version in first create ( and no main class ) 349 jar("--create", 350 "--file=" + modularJar.toString(), 351 "--module-version=" + "100000000", 352 "--no-manifest", 353 "-C", modClasses.toString(), ".") // includes module-info.class 354 .assertSuccess(); 355 jar("--update", 356 "--file=" + modularJar.toString(), 357 "--main-class=" + FOO.mainClass, 358 "--module-version=" + FOO.version, 359 "--no-manifest") 360 .assertSuccess(); 361 java(mp, FOO.moduleName + "/" + FOO.mainClass) 362 .assertSuccess() 363 .resultChecker(r -> assertModuleData(r, FOO)); 364 } 365 366 @Test 367 public void partialUpdateFooNotAllFiles() throws IOException { 368 Path mp = Paths.get("partialUpdateFooNotAllFiles"); 369 createTestDir(mp); 370 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 371 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 372 373 // Not all files, and none from non-exported packages, 374 // i.e. no concealed list in first create 375 jar("--create", 376 "--file=" + modularJar.toString(), 377 "--no-manifest", 378 "-C", modClasses.toString(), "module-info.class", 379 "-C", modClasses.toString(), "jdk/test/foo/Foo.class") 380 .assertSuccess(); 381 jar("--update", 382 "--file=" + modularJar.toString(), 383 "--main-class=" + FOO.mainClass, 384 "--module-version=" + FOO.version, 385 "--no-manifest", 386 "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class") 387 .assertSuccess(); 388 java(mp, FOO.moduleName + "/" + FOO.mainClass) 389 .assertSuccess() 390 .resultChecker(r -> assertModuleData(r, FOO)); 391 } 392 393 @Test 394 public void partialUpdateMRMJarFooNotAllFiles() throws IOException { 395 Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles"); 396 createTestDir(mp); 397 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 398 Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName); 399 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 400 401 jar("--create", 402 "--file=" + modularJar.toString(), 403 "--module-version=" + FOO.version, 404 "-C", modClasses.toString(), ".") 405 .assertSuccess(); 406 jar("--update", 407 "--file=" + modularJar.toString(), 408 "--main-class=" + FOO.mainClass, 409 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 410 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class") 411 .assertSuccess(); 412 java(mp, FOO.moduleName + "/" + FOO.mainClass) 413 .assertSuccess() 414 .resultChecker(r -> assertModuleData(r, FOO)); 415 } 416 417 @Test 418 public void partialUpdateFooAllFilesAndAttributes() throws IOException { 419 Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes"); 420 createTestDir(mp); 421 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 422 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 423 424 // all attributes and files 425 jar("--create", 426 "--file=" + modularJar.toString(), 427 "--main-class=" + FOO.mainClass, 428 "--module-version=" + FOO.version, 429 "--no-manifest", 430 "-C", modClasses.toString(), ".") 431 .assertSuccess(); 432 jar("--update", 433 "--file=" + modularJar.toString(), 434 "--main-class=" + FOO.mainClass, 435 "--module-version=" + FOO.version, 436 "--no-manifest", 437 "-C", modClasses.toString(), ".") 438 .assertSuccess(); 439 java(mp, FOO.moduleName + "/" + FOO.mainClass) 440 .assertSuccess() 441 .resultChecker(r -> assertModuleData(r, FOO)); 442 } 443 444 @Test 445 public void partialUpdateFooModuleInfo() throws IOException { 446 Path mp = Paths.get("partialUpdateFooModuleInfo"); 447 createTestDir(mp); 448 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 449 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 450 Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName); 451 452 jar("--create", 453 "--file=" + modularJar.toString(), 454 "--main-class=" + FOO.mainClass, 455 "--module-version=" + FOO.version, 456 "--no-manifest", 457 "-C", modClasses.toString(), ".") 458 .assertSuccess(); 459 jar("--update", 460 "--file=" + modularJar.toString(), 461 "--no-manifest", 462 "-C", barModInfo.toString(), "module-info.class") // stuff in bar's info 463 .assertSuccess(); 464 jar("-p", 465 "--file=" + modularJar.toString()) 466 .assertSuccess() 467 .resultChecker(r -> { 468 // Expect similar output: "bar, requires mandated foo, ... 469 // conceals jdk.test.foo, conceals jdk.test.foo.internal" 470 Pattern p = Pattern.compile("\\s+bar\\s+requires\\s++foo"); 471 assertTrue(p.matcher(r.output).find(), 472 "Expecting to find \"bar, requires foo,...\"", 473 "in output, but did not: [" + r.output + "]"); 474 p = Pattern.compile( 475 "conceals\\s+jdk.test.foo\\s+conceals\\s+jdk.test.foo.internal"); 476 assertTrue(p.matcher(r.output).find(), 477 "Expecting to find \"conceals jdk.test.foo,...\"", 478 "in output, but did not: [" + r.output + "]"); 479 }); 480 } 481 482 @Test 483 public void hashBarInFooModule() throws IOException { 484 Path mp = Paths.get("dependencesFooBar"); 485 createTestDir(mp); 486 487 Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName); 488 Path modularJar = mp.resolve(BAR.moduleName + ".jar"); 489 jar("--create", 490 "--file=" + modularJar.toString(), 491 "--main-class=" + BAR.mainClass, 492 "--module-version=" + BAR.version, 493 "--no-manifest", 494 "-C", modClasses.toString(), ".") 495 .assertSuccess(); 496 497 modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 498 modularJar = mp.resolve(FOO.moduleName + ".jar"); 499 jar("--create", 500 "--file=" + modularJar.toString(), 501 "--main-class=" + FOO.mainClass, 502 "--module-version=" + FOO.version, 503 "--modulepath=" + mp.toString(), 504 "--hash-modules=" + "bar", 505 "--no-manifest", 506 "-C", modClasses.toString(), ".") 507 .assertSuccess(); 508 509 java(mp, BAR.moduleName + "/" + BAR.mainClass, 510 "-XaddExports:java.base/jdk.internal.module=bar") 511 .assertSuccess() 512 .resultChecker(r -> { 513 assertModuleData(r, BAR); 514 TestModuleData received = TestModuleData.from(r.output); 515 assertTrue(received.hashes != null, "Expected non-null hashes value."); 516 }); 517 } 518 519 @Test 520 public void invalidHashInFooModule() throws IOException { 521 Path mp = Paths.get("badDependencyFooBar"); 522 createTestDir(mp); 523 524 Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName); 525 Path barJar = mp.resolve(BAR.moduleName + ".jar"); 526 jar("--create", 527 "--file=" + barJar.toString(), 528 "--main-class=" + BAR.mainClass, 529 "--module-version=" + BAR.version, 530 "--no-manifest", 531 "-C", barClasses.toString(), ".").assertSuccess(); 532 533 Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName); 534 Path fooJar = mp.resolve(FOO.moduleName + ".jar"); 535 jar("--create", 536 "--file=" + fooJar.toString(), 537 "--main-class=" + FOO.mainClass, 538 "--module-version=" + FOO.version, 539 "--modulepath=" + mp.toString(), 540 "--hash-modules=" + "bar", 541 "--no-manifest", 542 "-C", fooClasses.toString(), ".").assertSuccess(); 543 544 // Rebuild bar.jar with a change that will cause its hash to be different 545 FileUtils.deleteFileWithRetry(barJar); 546 jar("--create", 547 "--file=" + barJar.toString(), 548 "--main-class=" + BAR.mainClass, 549 "--module-version=" + BAR.version + ".1", // a newer version 550 "--no-manifest", 551 "-C", barClasses.toString(), ".").assertSuccess(); 552 553 java(mp, BAR.moduleName + "/" + BAR.mainClass, 554 "-XaddExports:java.base/jdk.internal.module=bar") 555 .assertFailure() 556 .resultChecker(r -> { 557 // Expect similar output: "java.lang.module.ResolutionException: Hash 558 // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to 559 // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)" 560 Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*"); 561 assertTrue(p.matcher(r.output).find(), 562 "Expecting error message containing \"Hash of bar ... differs to" 563 + " expected hash...\" but got: [", r.output + "]"); 564 }); 565 } 566 567 @Test 568 public void badOptionsFoo() throws IOException { 569 Path mp = Paths.get("badOptionsFoo"); 570 createTestDir(mp); 571 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 572 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 573 574 jar("--create", 575 "--file=" + modularJar.toString(), 576 "--module-version=" + 1.1, // no module-info.class 577 "-C", modClasses.toString(), "jdk") 578 .assertFailure(); // TODO: expected failure message 579 580 jar("--create", 581 "--file=" + modularJar.toString(), 582 "--hash-modules=" + ".*", // no module-info.class 583 "-C", modClasses.toString(), "jdk") 584 .assertFailure(); // TODO: expected failure message 585 } 586 587 @Test 588 public void servicesCreateWithoutFailure() throws IOException { 589 Path mp = Paths.get("servicesCreateWithoutFailure"); 590 createTestDir(mp); 591 Path modClasses = MODULE_CLASSES.resolve("baz"); 592 Path modularJar = mp.resolve("baz" + ".jar"); 593 594 // Positive test, create 595 jar("--create", 596 "--file=" + modularJar.toString(), 597 "-C", modClasses.toString(), "module-info.class", 598 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 599 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 600 .assertSuccess(); 601 } 602 603 @Test 604 public void servicesCreateWithoutServiceImpl() throws IOException { 605 Path mp = Paths.get("servicesWithoutServiceImpl"); 606 createTestDir(mp); 607 Path modClasses = MODULE_CLASSES.resolve("baz"); 608 Path modularJar = mp.resolve("baz" + ".jar"); 609 610 // Omit service impl 611 jar("--create", 612 "--file=" + modularJar.toString(), 613 "-C", modClasses.toString(), "module-info.class", 614 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 615 .assertFailure(); 616 } 617 618 @Test 619 public void servicesUpdateWithoutFailure() throws IOException { 620 Path mp = Paths.get("servicesUpdateWithoutFailure"); 621 createTestDir(mp); 622 Path modClasses = MODULE_CLASSES.resolve("baz"); 623 Path modularJar = mp.resolve("baz" + ".jar"); 624 625 // Positive test, update 626 jar("--create", 627 "--file=" + modularJar.toString(), 628 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 629 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 630 .assertSuccess(); 631 jar("--update", 632 "--file=" + modularJar.toString(), 633 "-C", modClasses.toString(), "module-info.class") 634 .assertSuccess(); 635 } 636 637 @Test 638 public void servicesUpdateWithoutServiceImpl() throws IOException { 639 Path mp = Paths.get("servicesUpdateWithoutServiceImpl"); 640 createTestDir(mp); 641 Path modClasses = MODULE_CLASSES.resolve("baz"); 642 Path modularJar = mp.resolve("baz" + ".jar"); 643 644 // Omit service impl 645 jar("--create", 646 "--file=" + modularJar.toString(), 647 "-C", modClasses.toString(), "jdk/test/baz/BazService.class") 648 .assertSuccess(); 649 jar("--update", 650 "--file=" + modularJar.toString(), 651 "-C", modClasses.toString(), "module-info.class") 652 .assertFailure(); 653 } 654 655 @Test 656 public void servicesCreateWithoutFailureMRMJAR() throws IOException { 657 Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR"); 658 createTestDir(mp); 659 Path modClasses = MODULE_CLASSES.resolve("baz"); 660 Path mrjarDir = MRJAR_DIR.resolve("baz"); 661 Path modularJar = mp.resolve("baz" + ".jar"); 662 663 jar("--create", 664 "--file=" + modularJar.toString(), 665 "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(), 666 "-C", modClasses.toString(), "module-info.class", 667 "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class", 668 "-C", modClasses.toString(), "jdk/test/baz/BazService.class", 669 "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class") 670 .assertSuccess(); 671 } 672 673 @Test 674 public void printModuleDescriptorFoo() throws IOException { 675 Path mp = Paths.get("printModuleDescriptorFoo"); 676 createTestDir(mp); 677 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 678 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 679 680 jar("--create", 681 "--file=" + modularJar.toString(), 682 "--main-class=" + FOO.mainClass, 683 "--module-version=" + FOO.version, 684 "--no-manifest", 685 "-C", modClasses.toString(), ".") 686 .assertSuccess(); 687 688 for (String option : new String[] {"--print-module-descriptor", "-p" }) { 689 jar(option, 690 "--file=" + modularJar.toString()) 691 .assertSuccess() 692 .resultChecker(r -> 693 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 694 "Expected to find ", FOO.moduleName + "@" + FOO.version, 695 " in [", r.output, "]") 696 ); 697 } 698 } 699 700 @Test 701 public void printModuleDescriptorFooFromStdin() throws IOException { 702 Path mp = Paths.get("printModuleDescriptorFooFromStdin"); 703 createTestDir(mp); 704 Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName); 705 Path modularJar = mp.resolve(FOO.moduleName + ".jar"); 706 707 jar("--create", 708 "--file=" + modularJar.toString(), 709 "--main-class=" + FOO.mainClass, 710 "--module-version=" + FOO.version, 711 "--no-manifest", 712 "-C", modClasses.toString(), ".") 713 .assertSuccess(); 714 715 for (String option : new String[] {"--print-module-descriptor", "-p" }) { 716 jarWithStdin(modularJar.toFile(), 717 option) 718 .assertSuccess() 719 .resultChecker(r -> 720 assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version), 721 "Expected to find ", FOO.moduleName + "@" + FOO.version, 722 " in [", r.output, "]") 723 ); 724 } 725 } 726 727 // -- Infrastructure 728 729 static Result jarWithStdin(File stdinSource, String... args) { 730 String jar = getJDKTool("jar"); 731 List<String> commands = new ArrayList<>(); 732 commands.add(jar); 733 Stream.of(args).forEach(x -> commands.add(x)); 734 ProcessBuilder p = new ProcessBuilder(commands); 735 if (stdinSource != null) 736 p.redirectInput(stdinSource); 737 return run(p); 738 } 739 740 static Result jar(String... args) { 741 return jarWithStdin(null, args); 742 } 743 744 static Path compileModule(String mn) throws IOException { 745 return compileModule(mn, null); 746 } 747 748 static Path compileModule(String mn, Path mp) 749 throws IOException 750 { 751 Path fooSourcePath = TEST_SRC.resolve("src").resolve(mn); 752 Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn)); 753 javac(build, mp, fileList(fooSourcePath)); 754 return build; 755 } 756 757 static void setupMRJARModuleInfo(String moduleName) throws IOException { 758 Path modClasses = MODULE_CLASSES.resolve(moduleName); 759 Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF"); 760 Path versionSection = metaInfDir.resolve("versions").resolve("9"); 761 createTestDir(versionSection); 762 763 Path versionModuleInfo = versionSection.resolve("module-info.class"); 764 System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo); 765 Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo); 766 767 Manifest manifest = new Manifest(); 768 manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); 769 manifest.getMainAttributes().putValue("Multi-Release", "true"); 770 try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) { 771 manifest.write(os); 772 } 773 } 774 775 // Re-enable when there is support in javax.tools for module path 776 // static void javac(Path dest, Path... sourceFiles) throws IOException { 777 // out.printf("Compiling %d source files %s%n", sourceFiles.length, 778 // Arrays.asList(sourceFiles)); 779 // JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 780 // try (StandardJavaFileManager fileManager = 781 // compiler.getStandardFileManager(null, null, null)) { 782 // 783 // List<File> files = Stream.of(sourceFiles) 784 // .map(p -> p.toFile()) 785 // .collect(Collectors.toList()); 786 // List<File> dests = Stream.of(dest) 787 // .map(p -> p.toFile()) 788 // .collect(Collectors.toList()); 789 // Iterable<? extends JavaFileObject> compilationUnits = 790 // fileManager.getJavaFileObjectsFromFiles(files); 791 // fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests); 792 // JavaCompiler.CompilationTask task = 793 // compiler.getTask(null, fileManager, null, null, null, compilationUnits); 794 // boolean passed = task.call(); 795 // if (!passed) 796 // throw new RuntimeException("Error compiling " + files); 797 // } 798 // } 799 800 static void javac(Path dest, Path... sourceFiles) throws IOException { 801 javac(dest, null, sourceFiles); 802 } 803 804 static void javac(Path dest, Path modulePath, Path... sourceFiles) 805 throws IOException 806 { 807 String javac = getJDKTool("javac"); 808 809 List<String> commands = new ArrayList<>(); 810 commands.add(javac); 811 commands.add("-d"); 812 commands.add(dest.toString()); 813 if (dest.toString().contains("bar")) 814 commands.add("-XaddExports:java.base/jdk.internal.module=bar"); 815 if (modulePath != null) { 816 commands.add("-mp"); 817 commands.add(modulePath.toString()); 818 } 819 Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x)); 820 821 quickFail(run(new ProcessBuilder(commands))); 822 } 823 824 static Result java(Path modulePath, String entryPoint, String... args) { 825 String java = getJDKTool("java"); 826 827 List<String> commands = new ArrayList<>(); 828 commands.add(java); 829 Stream.of(args).forEach(x -> commands.add(x)); 830 commands.add("-mp"); 831 commands.add(modulePath.toString()); 832 commands.add("-m"); 833 commands.add(entryPoint); 834 835 return run(new ProcessBuilder(commands)); 836 } 837 838 static Path[] fileList(Path directory) throws IOException { 839 final List<Path> filePaths = new ArrayList<>(); 840 Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { 841 @Override 842 public FileVisitResult visitFile(Path file, 843 BasicFileAttributes attrs) { 844 filePaths.add(file); 845 return FileVisitResult.CONTINUE; 846 } 847 }); 848 return filePaths.toArray(new Path[filePaths.size()]); 849 } 850 851 static void createTestDir(Path p) throws IOException{ 852 if (Files.exists(p)) 853 FileUtils.deleteFileTreeWithRetry(p); 854 Files.createDirectories(p); 855 } 856 857 static boolean jarContains(JarInputStream jis, String entryName) 858 throws IOException 859 { 860 JarEntry e; 861 while((e = jis.getNextJarEntry()) != null) { 862 if (e.getName().equals(entryName)) 863 return true; 864 } 865 return false; 866 } 867 868 static void quickFail(Result r) { 869 if (r.ec != 0) 870 throw new RuntimeException(r.output); 871 } 872 873 static Result run(ProcessBuilder pb) { 874 Process p; 875 out.printf("Running: %s%n", pb.command()); 876 try { 877 p = pb.start(); 878 } catch (IOException e) { 879 throw new RuntimeException( 880 format("Couldn't start process '%s'", pb.command()), e); 881 } 882 883 String output; 884 try { 885 output = toString(p.getInputStream(), p.getErrorStream()); 886 } catch (IOException e) { 887 throw new RuntimeException( 888 format("Couldn't read process output '%s'", pb.command()), e); 889 } 890 891 try { 892 p.waitFor(); 893 } catch (InterruptedException e) { 894 throw new RuntimeException( 895 format("Process hasn't finished '%s'", pb.command()), e); 896 } 897 return new Result(p.exitValue(), output); 898 } 899 900 static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home") 901 + File.separator + "bin" + File.separator; 902 903 static String getJDKTool(String name) { 904 try { 905 return JDKToolFinder.getJDKTool(name); 906 } catch (Exception x) { 907 return DEFAULT_IMAGE_BIN + name; 908 } 909 } 910 911 static String toString(InputStream in1, InputStream in2) throws IOException { 912 try (ByteArrayOutputStream dst = new ByteArrayOutputStream(); 913 InputStream concatenated = new SequenceInputStream(in1, in2)) { 914 concatenated.transferTo(dst); 915 return new String(dst.toByteArray(), "UTF-8"); 916 } 917 } 918 919 static class Result { 920 final int ec; 921 final String output; 922 923 private Result(int ec, String output) { 924 this.ec = ec; 925 this.output = output; 926 } 927 Result assertSuccess() { 928 assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]"); 929 return this; 930 } 931 Result assertFailure() { 932 assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]"); 933 return this; 934 } 935 Result resultChecker(Consumer<Result> r) { r.accept(this); return this; } 936 } 937 938 static void assertTrue(boolean cond, Object ... failedArgs) { 939 if (cond) 940 return; 941 StringBuilder sb = new StringBuilder(); 942 for (Object o : failedArgs) 943 sb.append(o); 944 org.testng.Assert.assertTrue(false, sb.toString()); 945 } 946 947 // Standalone entry point. 948 public static void main(String[] args) throws Throwable { 949 Basic test = new Basic(); 950 test.compileModules(); 951 for (Method m : Basic.class.getDeclaredMethods()) { 952 if (m.getAnnotation(Test.class) != null) { 953 System.out.println("Invoking " + m.getName()); 954 m.invoke(test); 955 } 956 } 957 } 958 }