1 /** 2 * Copyright (c) 2015, 2020, 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 /* 25 * @test 26 * @bug 8142968 8166568 8166286 8170618 8168149 8240910 27 * @summary Basic test for jmod 28 * @library /test/lib 29 * @modules jdk.compiler 30 * jdk.jlink 31 * @build jdk.test.lib.compiler.CompilerUtils 32 * jdk.test.lib.util.FileUtils 33 * jdk.test.lib.Platform 34 * @run testng/othervm -Djava.io.tmpdir=. JmodTest 35 */ 36 37 import java.io.*; 38 import java.lang.module.ModuleDescriptor; 39 import java.lang.reflect.Method; 40 import java.nio.file.*; 41 import java.util.*; 42 import java.util.function.Consumer; 43 import java.util.regex.Pattern; 44 import java.util.spi.ToolProvider; 45 import java.util.stream.Stream; 46 import jdk.test.lib.compiler.CompilerUtils; 47 import jdk.test.lib.util.FileUtils; 48 import org.testng.annotations.BeforeTest; 49 import org.testng.annotations.Test; 50 51 import static java.io.File.pathSeparator; 52 import static java.lang.module.ModuleDescriptor.Version; 53 import static java.nio.charset.StandardCharsets.UTF_8; 54 import static java.util.stream.Collectors.toSet; 55 import static org.testng.Assert.*; 56 57 public class JmodTest { 58 59 static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") 60 .orElseThrow(() -> 61 new RuntimeException("jmod tool not found") 62 ); 63 static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") 64 .orElseThrow(() -> 65 new RuntimeException("jar tool not found") 66 ); 67 68 static final String TEST_SRC = System.getProperty("test.src", "."); 69 static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); 70 static final Path EXPLODED_DIR = Paths.get("build"); 71 static final Path MODS_DIR = Paths.get("jmods"); 72 73 static final String CLASSES_PREFIX = "classes/"; 74 static final String CMDS_PREFIX = "bin/"; 75 static final String LIBS_PREFIX = "lib/"; 76 static final String CONFIGS_PREFIX = "conf/"; 77 78 @BeforeTest 79 public void buildExplodedModules() throws IOException { 80 if (Files.exists(EXPLODED_DIR)) 81 FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR); 82 83 for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) { 84 Path dir = EXPLODED_DIR.resolve(name); 85 assertTrue(compileModule(name, dir.resolve("classes"))); 86 copyResource(SRC_DIR.resolve("foo"), 87 dir.resolve("classes"), 88 "jdk/test/foo/resources/foo.properties"); 89 createCmds(dir.resolve("bin")); 90 createLibs(dir.resolve("lib")); 91 createConfigs(dir.resolve("conf")); 92 } 93 94 if (Files.exists(MODS_DIR)) 95 FileUtils.deleteFileTreeWithRetry(MODS_DIR); 96 Files.createDirectories(MODS_DIR); 97 } 98 99 // JDK-8166286 - jmod fails on symlink to directory 100 @Test 101 public void testSymlinks() throws IOException { 102 Path apaDir = EXPLODED_DIR.resolve("apa"); 103 Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes"); 104 assertTrue(compileModule("apa", classesDir)); 105 Path libDir = apaDir.resolve("lib"); 106 createFiles(libDir, List.of("foo/bar/libfoo.so")); 107 try { 108 Path link = Files.createSymbolicLink( 109 libDir.resolve("baz"), libDir.resolve("foo").toAbsolutePath()); 110 assertTrue(Files.exists(link)); 111 } catch (IOException|UnsupportedOperationException uoe) { 112 // OS does not support symlinks. Nothing to test! 113 return; 114 } 115 116 Path jmod = MODS_DIR.resolve("apa.jmod"); 117 jmod("create", 118 "--libs=" + libDir.toString(), 119 "--class-path", classesDir.toString(), 120 jmod.toString()) 121 .assertSuccess(); 122 } 123 124 // JDK-8170618 - jmod should validate if any exported or open package is missing 125 @Test 126 public void testMissingPackages() throws IOException { 127 Path apaDir = EXPLODED_DIR.resolve("apa"); 128 Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes"); 129 if (Files.exists(classesDir)) 130 FileUtils.deleteFileTreeWithRetry(classesDir); 131 assertTrue(compileModule("apa", classesDir)); 132 FileUtils.deleteFileTreeWithRetry(classesDir.resolve("jdk")); 133 Path jmod = MODS_DIR.resolve("apa.jmod"); 134 jmod("create", 135 "--class-path", classesDir.toString(), 136 jmod.toString()) 137 .assertFailure() 138 .resultChecker(r -> { 139 assertContains(r.output, "Packages that are exported or open in apa are not present: [jdk.test.apa]"); 140 }); 141 if (Files.exists(classesDir)) 142 FileUtils.deleteFileTreeWithRetry(classesDir); 143 } 144 145 @Test 146 public void testList() throws IOException { 147 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 148 jmod("create", 149 "--class-path", cp, 150 MODS_DIR.resolve("foo.jmod").toString()) 151 .assertSuccess(); 152 153 jmod("list", 154 MODS_DIR.resolve("foo.jmod").toString()) 155 .assertSuccess() 156 .resultChecker(r -> { 157 // asserts dependent on the exact contents of foo 158 assertContains(r.output, CLASSES_PREFIX + "module-info.class"); 159 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/Foo.class"); 160 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); 161 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties"); 162 }); 163 } 164 165 @Test 166 public void testExtractCWD() throws IOException { 167 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 168 jmod("create", 169 "--class-path", cp.toString(), 170 MODS_DIR.resolve("fooExtractCWD.jmod").toString()) 171 .assertSuccess(); 172 173 jmod("extract", 174 MODS_DIR.resolve("fooExtractCWD.jmod").toString()) 175 .assertSuccess() 176 .resultChecker(r -> { 177 // module-info should exist, but jmod will have added its Packages attr. 178 assertTrue(Files.exists(Paths.get("classes/module-info.class"))); 179 assertSameContent(cp.resolve("jdk/test/foo/Foo.class"), 180 Paths.get("classes/jdk/test/foo/Foo.class")); 181 assertSameContent(cp.resolve("jdk/test/foo/internal/Message.class"), 182 Paths.get("classes/jdk/test/foo/internal/Message.class")); 183 assertSameContent(cp.resolve("jdk/test/foo/resources/foo.properties"), 184 Paths.get("classes/jdk/test/foo/resources/foo.properties")); 185 }); 186 } 187 188 @Test 189 public void testExtractDir() throws IOException { 190 if (Files.exists(Paths.get("extractTestDir"))) 191 FileUtils.deleteFileTreeWithRetry(Paths.get("extractTestDir")); 192 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 193 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 194 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 195 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 196 197 jmod("create", 198 "--conf", cf.toString(), 199 "--cmds", bp.toString(), 200 "--libs", lp.toString(), 201 "--class-path", cp.toString(), 202 MODS_DIR.resolve("fooExtractDir.jmod").toString()) 203 .assertSuccess(); 204 205 jmod("extract", 206 "--dir", "extractTestDir", 207 MODS_DIR.resolve("fooExtractDir.jmod").toString()) 208 .assertSuccess(); 209 210 jmod("extract", 211 "--dir", "extractTestDir", 212 MODS_DIR.resolve("fooExtractDir.jmod").toString()) 213 .assertSuccess() 214 .resultChecker(r -> { 215 // check a sample of the extracted files 216 Path p = Paths.get("extractTestDir"); 217 assertTrue(Files.exists(p.resolve("classes/module-info.class"))); 218 assertSameContent(cp.resolve("jdk/test/foo/Foo.class"), 219 p.resolve("classes/jdk/test/foo/Foo.class")); 220 assertSameContent(bp.resolve("first"), 221 p.resolve(CMDS_PREFIX).resolve("first")); 222 assertSameContent(lp.resolve("first.so"), 223 p.resolve(LIBS_PREFIX).resolve("second.so")); 224 assertSameContent(cf.resolve("second.cfg"), 225 p.resolve(CONFIGS_PREFIX).resolve("second.cfg")); 226 }); 227 } 228 229 @Test 230 public void testMainClass() throws IOException { 231 Path jmod = MODS_DIR.resolve("fooMainClass.jmod"); 232 FileUtils.deleteFileIfExistsWithRetry(jmod); 233 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 234 235 jmod("create", 236 "--class-path", cp, 237 "--main-class", "jdk.test.foo.Foo", 238 jmod.toString()) 239 .assertSuccess() 240 .resultChecker(r -> { 241 Optional<String> omc = getModuleDescriptor(jmod).mainClass(); 242 assertTrue(omc.isPresent()); 243 assertEquals(omc.get(), "jdk.test.foo.Foo"); 244 }); 245 } 246 247 @Test 248 public void testModuleVersion() throws IOException { 249 Path jmod = MODS_DIR.resolve("fooVersion.jmod"); 250 FileUtils.deleteFileIfExistsWithRetry(jmod); 251 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 252 253 jmod("create", 254 "--class-path", cp, 255 "--module-version", "5.4.3", 256 jmod.toString()) 257 .assertSuccess() 258 .resultChecker(r -> { 259 Optional<Version> ov = getModuleDescriptor(jmod).version(); 260 assertTrue(ov.isPresent()); 261 assertEquals(ov.get().toString(), "5.4.3"); 262 }); 263 } 264 265 @Test 266 public void testConfig() throws IOException { 267 Path jmod = MODS_DIR.resolve("fooConfig.jmod"); 268 FileUtils.deleteFileIfExistsWithRetry(jmod); 269 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 270 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 271 272 jmod("create", 273 "--class-path", cp.toString(), 274 "--config", cf.toString(), 275 jmod.toString()) 276 .assertSuccess() 277 .resultChecker(r -> { 278 try (Stream<String> s1 = findFiles(cf).map(p -> CONFIGS_PREFIX + p); 279 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 280 Set<String> expectedFilenames = Stream.concat(s1, s2) 281 .collect(toSet()); 282 assertJmodContent(jmod, expectedFilenames); 283 } 284 }); 285 } 286 287 @Test 288 public void testCmds() throws IOException { 289 Path jmod = MODS_DIR.resolve("fooCmds.jmod"); 290 FileUtils.deleteFileIfExistsWithRetry(jmod); 291 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 292 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 293 294 jmod("create", 295 "--cmds", bp.toString(), 296 "--class-path", cp.toString(), 297 jmod.toString()) 298 .assertSuccess() 299 .resultChecker(r -> { 300 try (Stream<String> s1 = findFiles(bp).map(p -> CMDS_PREFIX + p); 301 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 302 Set<String> expectedFilenames = Stream.concat(s1,s2) 303 .collect(toSet()); 304 assertJmodContent(jmod, expectedFilenames); 305 } 306 }); 307 } 308 309 @Test 310 public void testLibs() throws IOException { 311 Path jmod = MODS_DIR.resolve("fooLibs.jmod"); 312 FileUtils.deleteFileIfExistsWithRetry(jmod); 313 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 314 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 315 316 jmod("create", 317 "--libs=" + lp.toString(), 318 "--class-path", cp.toString(), 319 jmod.toString()) 320 .assertSuccess() 321 .resultChecker(r -> { 322 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); 323 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 324 Set<String> expectedFilenames = Stream.concat(s1,s2) 325 .collect(toSet()); 326 assertJmodContent(jmod, expectedFilenames); 327 } 328 }); 329 } 330 331 @Test 332 public void testAll() throws IOException { 333 Path jmod = MODS_DIR.resolve("fooAll.jmod"); 334 FileUtils.deleteFileIfExistsWithRetry(jmod); 335 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 336 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 337 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 338 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 339 340 jmod("create", 341 "--conf", cf.toString(), 342 "--cmds=" + bp.toString(), 343 "--libs=" + lp.toString(), 344 "--class-path", cp.toString(), 345 jmod.toString()) 346 .assertSuccess() 347 .resultChecker(r -> { 348 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); 349 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p); 350 Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p); 351 Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) { 352 Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2), 353 Stream.concat(s3, s4)) 354 .collect(toSet()); 355 assertJmodContent(jmod, expectedFilenames); 356 } 357 }); 358 } 359 360 @Test 361 public void testExcludes() throws IOException { 362 Path jmod = MODS_DIR.resolve("fooLibs.jmod"); 363 FileUtils.deleteFileIfExistsWithRetry(jmod); 364 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 365 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 366 367 jmod("create", 368 "--libs=" + lp.toString(), 369 "--class-path", cp.toString(), 370 "--exclude", "**internal**", 371 "--exclude", "first.so", 372 jmod.toString()) 373 .assertSuccess() 374 .resultChecker(r -> { 375 Set<String> expectedFilenames = new HashSet<>(); 376 expectedFilenames.add(CLASSES_PREFIX + "module-info.class"); 377 expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/Foo.class"); 378 expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties"); 379 expectedFilenames.add(LIBS_PREFIX + "second.so"); 380 expectedFilenames.add(LIBS_PREFIX + "third/third.so"); 381 assertJmodContent(jmod, expectedFilenames); 382 383 Set<String> unexpectedFilenames = new HashSet<>(); 384 unexpectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); 385 unexpectedFilenames.add(LIBS_PREFIX + "first.so"); 386 assertJmodDoesNotContain(jmod, unexpectedFilenames); 387 }); 388 } 389 390 @Test 391 public void describe() throws IOException { 392 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 393 jmod("create", 394 "--class-path", cp, 395 MODS_DIR.resolve("describeFoo.jmod").toString()) 396 .assertSuccess(); 397 398 jmod("describe", 399 MODS_DIR.resolve("describeFoo.jmod").toString()) 400 .assertSuccess() 401 .resultChecker(r -> { 402 // Expect similar output: "foo... exports jdk.test.foo ... 403 // ... requires java.base mandated... contains jdk.test.foo.internal" 404 Pattern p = Pattern.compile("foo\\s+exports\\s+jdk.test.foo"); 405 assertTrue(p.matcher(r.output).find(), 406 "Expecting to find \"foo... exports jdk.test.foo\"" + 407 "in output, but did not: [" + r.output + "]"); 408 p = Pattern.compile( 409 "requires\\s+java.base\\s+mandated\\s+contains\\s+jdk.test.foo.internal"); 410 assertTrue(p.matcher(r.output).find(), 411 "Expecting to find \"requires java.base mandated..., " + 412 "contains jdk.test.foo.internal ...\"" + 413 "in output, but did not: [" + r.output + "]"); 414 }); 415 } 416 417 @Test 418 public void testDuplicateEntries() throws IOException { 419 Path jmod = MODS_DIR.resolve("testDuplicates.jmod"); 420 FileUtils.deleteFileIfExistsWithRetry(jmod); 421 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 422 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 423 424 jmod("create", 425 "--class-path", cp + pathSeparator + cp, 426 jmod.toString()) 427 .assertSuccess() 428 .resultChecker(r -> 429 assertContains(r.output, "Warning: ignoring duplicate entry") 430 ); 431 432 FileUtils.deleteFileIfExistsWithRetry(jmod); 433 jmod("create", 434 "--class-path", cp, 435 "--libs", lp.toString() + pathSeparator + lp.toString(), 436 jmod.toString()) 437 .assertSuccess() 438 .resultChecker(r -> 439 assertContains(r.output, "Warning: ignoring duplicate entry") 440 ); 441 } 442 443 @Test 444 public void testDuplicateEntriesFromJarFile() throws IOException { 445 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 446 Path jar = Paths.get("foo.jar"); 447 Path jmod = MODS_DIR.resolve("testDuplicates.jmod"); 448 FileUtils.deleteFileIfExistsWithRetry(jar); 449 FileUtils.deleteFileIfExistsWithRetry(jmod); 450 // create JAR file 451 assertTrue(JAR_TOOL.run(System.out, System.err, "cf", jar.toString(), "-C", cp, ".") == 0); 452 453 jmod("create", 454 "--class-path", jar.toString() + pathSeparator + jar.toString(), 455 jmod.toString()) 456 .assertSuccess() 457 .resultChecker(r -> 458 assertContains(r.output, "Warning: ignoring duplicate entry") 459 ); 460 } 461 462 @Test 463 public void testIgnoreModuleInfoInOtherSections() throws IOException { 464 Path jmod = MODS_DIR.resolve("testIgnoreModuleInfoInOtherSections.jmod"); 465 FileUtils.deleteFileIfExistsWithRetry(jmod); 466 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 467 468 jmod("create", 469 "--class-path", cp, 470 "--libs", cp, 471 jmod.toString()) 472 .assertSuccess() 473 .resultChecker(r -> 474 assertContains(r.output, "Warning: ignoring entry") 475 ); 476 477 FileUtils.deleteFileIfExistsWithRetry(jmod); 478 jmod("create", 479 "--class-path", cp, 480 "--cmds", cp, 481 jmod.toString()) 482 .assertSuccess() 483 .resultChecker(r -> 484 assertContains(r.output, "Warning: ignoring entry") 485 ); 486 } 487 488 @Test 489 public void testLastOneWins() throws IOException { 490 Path workDir = Paths.get("lastOneWins"); 491 if (Files.exists(workDir)) 492 FileUtils.deleteFileTreeWithRetry(workDir); 493 Files.createDirectory(workDir); 494 Path jmod = MODS_DIR.resolve("lastOneWins.jmod"); 495 FileUtils.deleteFileIfExistsWithRetry(jmod); 496 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 497 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 498 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 499 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 500 501 Path shouldNotBeAdded = workDir.resolve("shouldNotBeAdded"); 502 Files.createDirectory(shouldNotBeAdded); 503 Files.write(shouldNotBeAdded.resolve("aFile"), "hello".getBytes(UTF_8)); 504 505 // Pairs of options. For options with required arguments the last one 506 // should win ( first should be effectively ignored, but may still be 507 // validated ). 508 jmod("create", 509 "--conf", shouldNotBeAdded.toString(), 510 "--conf", cf.toString(), 511 "--cmds", shouldNotBeAdded.toString(), 512 "--cmds", bp.toString(), 513 "--libs", shouldNotBeAdded.toString(), 514 "--libs", lp.toString(), 515 "--class-path", shouldNotBeAdded.toString(), 516 "--class-path", cp.toString(), 517 "--main-class", "does.NotExist", 518 "--main-class", "jdk.test.foo.Foo", 519 "--module-version", "00001", 520 "--module-version", "5.4.3", 521 "--do-not-resolve-by-default", 522 "--do-not-resolve-by-default", 523 "--warn-if-resolved=incubating", 524 "--warn-if-resolved=deprecated", 525 MODS_DIR.resolve("lastOneWins.jmod").toString()) 526 .assertSuccess() 527 .resultChecker(r -> { 528 ModuleDescriptor md = getModuleDescriptor(jmod); 529 Optional<String> omc = md.mainClass(); 530 assertTrue(omc.isPresent()); 531 assertEquals(omc.get(), "jdk.test.foo.Foo"); 532 Optional<Version> ov = md.version(); 533 assertTrue(ov.isPresent()); 534 assertEquals(ov.get().toString(), "5.4.3"); 535 536 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); 537 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p); 538 Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p); 539 Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) { 540 Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2), 541 Stream.concat(s3, s4)) 542 .collect(toSet()); 543 assertJmodContent(jmod, expectedFilenames); 544 } 545 }); 546 547 jmod("extract", 548 "--dir", "blah", 549 "--dir", "lastOneWinsExtractDir", 550 jmod.toString()) 551 .assertSuccess() 552 .resultChecker(r -> { 553 assertTrue(Files.exists(Paths.get("lastOneWinsExtractDir"))); 554 assertTrue(Files.notExists(Paths.get("blah"))); 555 }); 556 } 557 558 @Test 559 public void testPackagesAttribute() throws IOException { 560 Path jmod = MODS_DIR.resolve("foo.jmod"); 561 FileUtils.deleteFileIfExistsWithRetry(jmod); 562 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 563 564 Set<String> expectedPackages = Set.of("jdk.test.foo", 565 "jdk.test.foo.internal", 566 "jdk.test.foo.resources"); 567 568 jmod("create", 569 "--class-path", cp, 570 jmod.toString()) 571 .assertSuccess() 572 .resultChecker(r -> { 573 Set<String> pkgs = getModuleDescriptor(jmod).packages(); 574 assertEquals(pkgs, expectedPackages); 575 }); 576 } 577 578 @Test 579 public void testVersion() { 580 jmod("--version") 581 .assertSuccess() 582 .resultChecker(r -> { 583 assertContains(r.output, System.getProperty("java.version")); 584 }); 585 } 586 587 @Test 588 public void testHelp() { 589 jmod("--help") 590 .assertSuccess() 591 .resultChecker(r -> { 592 assertTrue(r.output.startsWith("Usage: jmod"), "Help not printed"); 593 assertFalse(r.output.contains("--do-not-resolve-by-default")); 594 assertFalse(r.output.contains("--warn-if-resolved")); 595 }); 596 } 597 598 @Test 599 public void testHelpExtra() { 600 jmod("--help-extra") 601 .assertSuccess() 602 .resultChecker(r -> { 603 assertTrue(r.output.startsWith("Usage: jmod"), "Extra help not printed"); 604 assertContains(r.output, "--do-not-resolve-by-default"); 605 assertContains(r.output, "--warn-if-resolved"); 606 }); 607 } 608 609 @Test 610 public void testTmpFileRemoved() throws IOException { 611 // Implementation detail: jmod tool creates <jmod-file>.tmp 612 // Ensure that it is removed in the event of a failure. 613 // The failure in this case is a class in the unnamed package. 614 615 Path jmod = MODS_DIR.resolve("testTmpFileRemoved.jmod"); 616 Path tmp = MODS_DIR.resolve(".testTmpFileRemoved.jmod.tmp"); 617 FileUtils.deleteFileIfExistsWithRetry(jmod); 618 FileUtils.deleteFileIfExistsWithRetry(tmp); 619 String cp = EXPLODED_DIR.resolve("foo").resolve("classes") + File.pathSeparator + 620 EXPLODED_DIR.resolve("foo").resolve("classes") 621 .resolve("jdk").resolve("test").resolve("foo").toString(); 622 623 jmod("create", 624 "--class-path", cp, 625 jmod.toString()) 626 .assertFailure() 627 .resultChecker(r -> { 628 assertContains(r.output, "unnamed package"); 629 assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp); 630 }); 631 } 632 633 // --- 634 635 static boolean compileModule(String name, Path dest) throws IOException { 636 return CompilerUtils.compile(SRC_DIR.resolve(name), dest); 637 } 638 639 static void assertContains(String output, String subString) { 640 if (output.contains(subString)) 641 assertTrue(true); 642 else 643 assertTrue(false,"Expected to find [" + subString + "], in output [" 644 + output + "]" + "\n"); 645 } 646 647 static ModuleDescriptor getModuleDescriptor(Path jmod) { 648 ClassLoader cl = ClassLoader.getSystemClassLoader(); 649 try (FileSystem fs = FileSystems.newFileSystem(jmod, cl)) { 650 String p = "/classes/module-info.class"; 651 try (InputStream is = Files.newInputStream(fs.getPath(p))) { 652 return ModuleDescriptor.read(is); 653 } 654 } catch (IOException ioe) { 655 throw new UncheckedIOException(ioe); 656 } 657 } 658 659 static Stream<String> findFiles(Path dir) { 660 try { 661 return Files.find(dir, Integer.MAX_VALUE, (p, a) -> a.isRegularFile()) 662 .map(dir::relativize) 663 .map(Path::toString) 664 .map(p -> p.replace(File.separator, "/")); 665 } catch (IOException x) { 666 throw new UncheckedIOException(x); 667 } 668 } 669 670 static Set<String> getJmodContent(Path jmod) { 671 JmodResult r = jmod("list", jmod.toString()).assertSuccess(); 672 return Stream.of(r.output.split("\r?\n")).collect(toSet()); 673 } 674 675 static void assertJmodContent(Path jmod, Set<String> expected) { 676 Set<String> actual = getJmodContent(jmod); 677 if (!Objects.equals(actual, expected)) { 678 Set<String> unexpected = new HashSet<>(actual); 679 unexpected.removeAll(expected); 680 Set<String> notFound = new HashSet<>(expected); 681 notFound.removeAll(actual); 682 StringBuilder sb = new StringBuilder(); 683 sb.append("Unexpected but found:\n"); 684 unexpected.forEach(s -> sb.append("\t" + s + "\n")); 685 sb.append("Expected but not found:\n"); 686 notFound.forEach(s -> sb.append("\t" + s + "\n")); 687 assertTrue(false, "Jmod content check failed.\n" + sb.toString()); 688 } 689 } 690 691 static void assertJmodDoesNotContain(Path jmod, Set<String> unexpectedNames) { 692 Set<String> actual = getJmodContent(jmod); 693 Set<String> unexpected = new HashSet<>(); 694 for (String name : unexpectedNames) { 695 if (actual.contains(name)) 696 unexpected.add(name); 697 } 698 if (!unexpected.isEmpty()) { 699 StringBuilder sb = new StringBuilder(); 700 for (String s : unexpected) 701 sb.append("Unexpected but found: " + s + "\n"); 702 sb.append("In :"); 703 for (String s : actual) 704 sb.append("\t" + s + "\n"); 705 assertTrue(false, "Jmod content check failed.\n" + sb.toString()); 706 } 707 } 708 709 static void assertSameContent(Path p1, Path p2) { 710 try { 711 byte[] ba1 = Files.readAllBytes(p1); 712 byte[] ba2 = Files.readAllBytes(p2); 713 assertEquals(ba1, ba2); 714 } catch (IOException x) { 715 throw new UncheckedIOException(x); 716 } 717 } 718 719 static JmodResult jmod(String... args) { 720 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 721 PrintStream ps = new PrintStream(baos); 722 System.out.println("jmod " + Arrays.asList(args)); 723 int ec = JMOD_TOOL.run(ps, ps, args); 724 return new JmodResult(ec, new String(baos.toByteArray(), UTF_8)); 725 } 726 727 static class JmodResult { 728 final int exitCode; 729 final String output; 730 731 JmodResult(int exitValue, String output) { 732 this.exitCode = exitValue; 733 this.output = output; 734 } 735 JmodResult assertSuccess() { assertTrue(exitCode == 0, output); return this; } 736 JmodResult assertFailure() { assertTrue(exitCode != 0, output); return this; } 737 JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; } 738 } 739 740 static void createCmds(Path dir) throws IOException { 741 List<String> files = Arrays.asList( 742 "first", "second", "third" + File.separator + "third"); 743 createFiles(dir, files); 744 } 745 746 static void createLibs(Path dir) throws IOException { 747 List<String> files = Arrays.asList( 748 "first.so", "second.so", "third" + File.separator + "third.so"); 749 createFiles(dir, files); 750 } 751 752 static void createConfigs(Path dir) throws IOException { 753 List<String> files = Arrays.asList( 754 "first.cfg", "second.cfg", "third" + File.separator + "third.cfg"); 755 createFiles(dir, files); 756 } 757 758 static void createFiles(Path dir, List<String> filenames) throws IOException { 759 for (String name : filenames) { 760 Path file = dir.resolve(name); 761 Files.createDirectories(file.getParent()); 762 Files.createFile(file); 763 try (OutputStream os = Files.newOutputStream(file)) { 764 os.write("blahblahblah".getBytes(UTF_8)); 765 } 766 } 767 } 768 769 static void copyResource(Path srcDir, Path dir, String resource) throws IOException { 770 Path dest = dir.resolve(resource); 771 Files.deleteIfExists(dest); 772 773 Files.createDirectories(dest.getParent()); 774 Files.copy(srcDir.resolve(resource), dest); 775 } 776 777 // Standalone entry point. 778 public static void main(String[] args) throws Throwable { 779 JmodTest test = new JmodTest(); 780 test.buildExplodedModules(); 781 for (Method m : JmodTest.class.getDeclaredMethods()) { 782 if (m.getAnnotation(Test.class) != null) { 783 System.out.println("Invoking " + m.getName()); 784 m.invoke(test); 785 } 786 } 787 } 788 }