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