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 /* 25 * @test 26 * @library /lib/testlibrary 27 * @modules jdk.compiler 28 * jdk.jlink 29 * @build jdk.testlibrary.FileUtils CompilerUtils 30 * @run testng JmodTest 31 * @summary Basic test for jmod 32 */ 33 34 import java.io.*; 35 import java.lang.module.ModuleDescriptor; 36 import java.lang.reflect.Method; 37 import java.nio.file.*; 38 import java.util.*; 39 import java.util.function.Consumer; 40 import java.util.regex.Pattern; 41 import java.util.spi.ToolProvider; 42 import java.util.stream.Stream; 43 import jdk.testlibrary.FileUtils; 44 import org.testng.annotations.BeforeTest; 45 import org.testng.annotations.Test; 46 47 import static java.io.File.pathSeparator; 48 import static java.lang.module.ModuleDescriptor.Version; 49 import static java.nio.charset.StandardCharsets.UTF_8; 50 import static java.util.stream.Collectors.toSet; 51 import static org.testng.Assert.*; 52 53 public class JmodTest { 54 55 static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") 56 .orElseThrow(() -> 57 new RuntimeException("jmod tool not found") 58 ); 59 60 static final String TEST_SRC = System.getProperty("test.src", "."); 61 static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); 62 static final Path EXPLODED_DIR = Paths.get("build"); 63 static final Path MODS_DIR = Paths.get("jmods"); 64 65 static final String CLASSES_PREFIX = "classes/"; 66 static final String CMDS_PREFIX = "bin/"; 67 static final String LIBS_PREFIX = "native/"; 68 static final String CONFIGS_PREFIX = "conf/"; 69 70 @BeforeTest 71 public void buildExplodedModules() throws IOException { 72 if (Files.exists(EXPLODED_DIR)) 73 FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR); 74 75 for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) { 76 Path dir = EXPLODED_DIR.resolve(name); 77 assertTrue(compileModule(name, dir.resolve("classes"))); 78 createCmds(dir.resolve("bin")); 79 createLibs(dir.resolve("lib")); 80 createConfigs(dir.resolve("conf")); 81 } 82 83 if (Files.exists(MODS_DIR)) 84 FileUtils.deleteFileTreeWithRetry(MODS_DIR); 85 Files.createDirectories(MODS_DIR); 86 } 87 88 @Test 89 public void testList() throws IOException { 90 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 91 jmod("create", 92 "--class-path", cp, 93 MODS_DIR.resolve("foo.jmod").toString()) 94 .assertSuccess(); 95 96 jmod("list", 97 MODS_DIR.resolve("foo.jmod").toString()) 98 .assertSuccess() 99 .resultChecker(r -> { 100 // asserts dependent on the exact contents of foo 101 assertContains(r.output, CLASSES_PREFIX + "module-info.class"); 102 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/Foo.class"); 103 assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); 104 }); 105 } 106 107 @Test 108 public void testMainClass() throws IOException { 109 Path jmod = MODS_DIR.resolve("fooMainClass.jmod"); 110 FileUtils.deleteFileIfExistsWithRetry(jmod); 111 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 112 113 jmod("create", 114 "--class-path", cp, 115 "--main-class", "jdk.test.foo.Foo", 116 jmod.toString()) 117 .assertSuccess() 118 .resultChecker(r -> { 119 Optional<String> omc = getModuleDescriptor(jmod).mainClass(); 120 assertTrue(omc.isPresent()); 121 assertEquals(omc.get(), "jdk.test.foo.Foo"); 122 }); 123 } 124 125 @Test 126 public void testModuleVersion() throws IOException { 127 Path jmod = MODS_DIR.resolve("fooVersion.jmod"); 128 FileUtils.deleteFileIfExistsWithRetry(jmod); 129 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 130 131 jmod("create", 132 "--class-path", cp, 133 "--module-version", "5.4.3", 134 jmod.toString()) 135 .assertSuccess() 136 .resultChecker(r -> { 137 Optional<Version> ov = getModuleDescriptor(jmod).version(); 138 assertTrue(ov.isPresent()); 139 assertEquals(ov.get().toString(), "5.4.3"); 140 }); 141 } 142 143 @Test 144 public void testConfig() throws IOException { 145 Path jmod = MODS_DIR.resolve("fooConfig.jmod"); 146 FileUtils.deleteFileIfExistsWithRetry(jmod); 147 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 148 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 149 150 jmod("create", 151 "--class-path", cp.toString(), 152 "--config", cf.toString(), 153 jmod.toString()) 154 .assertSuccess() 155 .resultChecker(r -> { 156 try (Stream<String> s1 = findFiles(cf).map(p -> CONFIGS_PREFIX + p); 157 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 158 Set<String> expectedFilenames = Stream.concat(s1, s2) 159 .collect(toSet()); 160 assertJmodContent(jmod, expectedFilenames); 161 } 162 }); 163 } 164 165 @Test 166 public void testCmds() throws IOException { 167 Path jmod = MODS_DIR.resolve("fooCmds.jmod"); 168 FileUtils.deleteFileIfExistsWithRetry(jmod); 169 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 170 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 171 172 jmod("create", 173 "--cmds", bp.toString(), 174 "--class-path", cp.toString(), 175 jmod.toString()) 176 .assertSuccess() 177 .resultChecker(r -> { 178 try (Stream<String> s1 = findFiles(bp).map(p -> CMDS_PREFIX + p); 179 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 180 Set<String> expectedFilenames = Stream.concat(s1,s2) 181 .collect(toSet()); 182 assertJmodContent(jmod, expectedFilenames); 183 } 184 }); 185 } 186 187 @Test 188 public void testLibs() throws IOException { 189 Path jmod = MODS_DIR.resolve("fooLibs.jmod"); 190 FileUtils.deleteFileIfExistsWithRetry(jmod); 191 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 192 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 193 194 jmod("create", 195 "--libs=", lp.toString(), 196 "--class-path", cp.toString(), 197 jmod.toString()) 198 .assertSuccess() 199 .resultChecker(r -> { 200 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); 201 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { 202 Set<String> expectedFilenames = Stream.concat(s1,s2) 203 .collect(toSet()); 204 assertJmodContent(jmod, expectedFilenames); 205 } 206 }); 207 } 208 209 @Test 210 public void testAll() throws IOException { 211 Path jmod = MODS_DIR.resolve("fooAll.jmod"); 212 FileUtils.deleteFileIfExistsWithRetry(jmod); 213 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 214 Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); 215 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 216 Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); 217 218 jmod("create", 219 "--conf", cf.toString(), 220 "--cmds=", bp.toString(), 221 "--libs=", lp.toString(), 222 "--class-path", cp.toString(), 223 jmod.toString()) 224 .assertSuccess() 225 .resultChecker(r -> { 226 try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); 227 Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p); 228 Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p); 229 Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) { 230 Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2), 231 Stream.concat(s3, s4)) 232 .collect(toSet()); 233 assertJmodContent(jmod, expectedFilenames); 234 } 235 }); 236 } 237 238 @Test 239 public void testExcludes() throws IOException { 240 Path jmod = MODS_DIR.resolve("fooLibs.jmod"); 241 FileUtils.deleteFileIfExistsWithRetry(jmod); 242 Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); 243 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 244 245 jmod("create", 246 "--libs=", lp.toString(), 247 "--class-path", cp.toString(), 248 "--exclude", "**internal**", 249 "--exclude", "first.so", 250 jmod.toString()) 251 .assertSuccess() 252 .resultChecker(r -> { 253 Set<String> expectedFilenames = new HashSet<>(); 254 expectedFilenames.add(CLASSES_PREFIX + "module-info.class"); 255 expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/Foo.class"); 256 expectedFilenames.add(LIBS_PREFIX + "second.so"); 257 expectedFilenames.add(LIBS_PREFIX + "third/third.so"); 258 assertJmodContent(jmod, expectedFilenames); 259 260 Set<String> unexpectedFilenames = new HashSet<>(); 261 unexpectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); 262 unexpectedFilenames.add(LIBS_PREFIX + "first.so"); 263 assertJmodDoesNotContain(jmod, unexpectedFilenames); 264 }); 265 } 266 267 @Test 268 public void describe() throws IOException { 269 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 270 jmod("create", 271 "--class-path", cp, 272 MODS_DIR.resolve("describeFoo.jmod").toString()) 273 .assertSuccess(); 274 275 jmod("describe", 276 MODS_DIR.resolve("describeFoo.jmod").toString()) 277 .assertSuccess() 278 .resultChecker(r -> { 279 // Expect similar output: "foo, requires mandated java.base 280 // exports jdk.test.foo, conceals jdk.test.foo.internal" 281 Pattern p = Pattern.compile("\\s+foo\\s+requires\\s+mandated\\s+java.base"); 282 assertTrue(p.matcher(r.output).find(), 283 "Expecting to find \"foo, requires java.base\"" + 284 "in output, but did not: [" + r.output + "]"); 285 p = Pattern.compile( 286 "exports\\s+jdk.test.foo\\s+conceals\\s+jdk.test.foo.internal"); 287 assertTrue(p.matcher(r.output).find(), 288 "Expecting to find \"exports ..., conceals ...\"" + 289 "in output, but did not: [" + r.output + "]"); 290 }); 291 } 292 293 @Test 294 public void testDuplicateEntries() throws IOException { 295 Path jmod = MODS_DIR.resolve("testDuplicates.jmod"); 296 FileUtils.deleteFileIfExistsWithRetry(jmod); 297 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 298 Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); 299 300 jmod("create", 301 "--class-path", cp + pathSeparator + cp, 302 jmod.toString()) 303 .assertSuccess() 304 .resultChecker(r -> 305 assertContains(r.output, "Warning: ignoring duplicate entry") 306 ); 307 308 FileUtils.deleteFileIfExistsWithRetry(jmod); 309 jmod("create", 310 "--class-path", cp, 311 "--libs", lp.toString() + pathSeparator + lp.toString(), 312 jmod.toString()) 313 .assertSuccess() 314 .resultChecker(r -> 315 assertContains(r.output, "Warning: ignoring duplicate entry") 316 ); 317 } 318 319 @Test 320 public void testIgnoreModuleInfoInOtherSections() throws IOException { 321 Path jmod = MODS_DIR.resolve("testIgnoreModuleInfoInOtherSections.jmod"); 322 FileUtils.deleteFileIfExistsWithRetry(jmod); 323 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 324 325 jmod("create", 326 "--class-path", cp, 327 "--libs", cp, 328 jmod.toString()) 329 .assertSuccess() 330 .resultChecker(r -> 331 assertContains(r.output, "Warning: ignoring entry") 332 ); 333 334 FileUtils.deleteFileIfExistsWithRetry(jmod); 335 jmod("create", 336 "--class-path", cp, 337 "--cmds", cp, 338 jmod.toString()) 339 .assertSuccess() 340 .resultChecker(r -> 341 assertContains(r.output, "Warning: ignoring entry") 342 ); 343 } 344 345 @Test 346 public void testVersion() { 347 jmod("--version") 348 .assertSuccess() 349 .resultChecker(r -> { 350 assertContains(r.output, System.getProperty("java.version")); 351 }); 352 } 353 354 @Test 355 public void testHelp() { 356 jmod("--help") 357 .assertSuccess() 358 .resultChecker(r -> 359 assertTrue(r.output.startsWith("Usage: jmod"), "Help not printed") 360 ); 361 } 362 363 @Test 364 public void testTmpFileAlreadyExists() throws IOException { 365 // Implementation detail: jmod tool creates <jmod-file>.tmp 366 // Ensure that there are no problems if existing 367 368 Path jmod = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod"); 369 Path tmp = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod.tmp"); 370 FileUtils.deleteFileIfExistsWithRetry(jmod); 371 FileUtils.deleteFileIfExistsWithRetry(tmp); 372 Files.createFile(tmp); 373 String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); 374 375 jmod("create", 376 "--class-path", cp, 377 jmod.toString()) 378 .assertSuccess() 379 .resultChecker(r -> 380 assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp) 381 ); 382 } 383 384 @Test 385 public void testTmpFileRemoved() throws IOException { 386 // Implementation detail: jmod tool creates <jmod-file>.tmp 387 // Ensure that it is removed in the event of a failure. 388 // The failure in this case is a class in the unnamed package. 389 390 Path jmod = MODS_DIR.resolve("testTmpFileRemoved.jmod"); 391 Path tmp = MODS_DIR.resolve("testTmpFileRemoved.jmod.tmp"); 392 FileUtils.deleteFileIfExistsWithRetry(jmod); 393 FileUtils.deleteFileIfExistsWithRetry(tmp); 394 String cp = EXPLODED_DIR.resolve("foo").resolve("classes") + File.pathSeparator + 395 EXPLODED_DIR.resolve("foo").resolve("classes") 396 .resolve("jdk").resolve("test").resolve("foo").toString(); 397 398 jmod("create", 399 "--class-path", cp, 400 jmod.toString()) 401 .assertFailure() 402 .resultChecker(r -> { 403 assertContains(r.output, "unnamed package"); 404 assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp); 405 }); 406 } 407 408 // --- 409 410 static boolean compileModule(String name, Path dest) throws IOException { 411 return CompilerUtils.compile(SRC_DIR.resolve(name), dest); 412 } 413 414 static void assertContains(String output, String subString) { 415 if (output.contains(subString)) 416 assertTrue(true); 417 else 418 assertTrue(false,"Expected to find [" + subString + "], in output [" 419 + output + "]" + "\n"); 420 } 421 422 static ModuleDescriptor getModuleDescriptor(Path jmod) { 423 ClassLoader cl = ClassLoader.getSystemClassLoader(); 424 try (FileSystem fs = FileSystems.newFileSystem(jmod, cl)) { 425 String p = "/classes/module-info.class"; 426 try (InputStream is = Files.newInputStream(fs.getPath(p))) { 427 return ModuleDescriptor.read(is); 428 } 429 } catch (IOException ioe) { 430 throw new UncheckedIOException(ioe); 431 } 432 } 433 434 static Stream<String> findFiles(Path dir) { 435 try { 436 return Files.find(dir, Integer.MAX_VALUE, (p, a) -> a.isRegularFile()) 437 .map(dir::relativize) 438 .map(Path::toString) 439 .map(p -> p.replace(File.separator, "/")); 440 } catch (IOException x) { 441 throw new UncheckedIOException(x); 442 } 443 } 444 445 static Set<String> getJmodContent(Path jmod) { 446 JmodResult r = jmod("list", jmod.toString()).assertSuccess(); 447 return Stream.of(r.output.split("\r?\n")).collect(toSet()); 448 } 449 450 static void assertJmodContent(Path jmod, Set<String> expected) { 451 Set<String> actual = getJmodContent(jmod); 452 if (!Objects.equals(actual, expected)) { 453 Set<String> unexpected = new HashSet<>(actual); 454 unexpected.removeAll(expected); 455 Set<String> notFound = new HashSet<>(expected); 456 notFound.removeAll(actual); 457 StringBuilder sb = new StringBuilder(); 458 sb.append("Unexpected but found:\n"); 459 unexpected.forEach(s -> sb.append("\t" + s + "\n")); 460 sb.append("Expected but not found:\n"); 461 notFound.forEach(s -> sb.append("\t" + s + "\n")); 462 assertTrue(false, "Jmod content check failed.\n" + sb.toString()); 463 } 464 } 465 466 static void assertJmodDoesNotContain(Path jmod, Set<String> unexpectedNames) { 467 Set<String> actual = getJmodContent(jmod); 468 Set<String> unexpected = new HashSet<>(); 469 for (String name : unexpectedNames) { 470 if (actual.contains(name)) 471 unexpected.add(name); 472 } 473 if (!unexpected.isEmpty()) { 474 StringBuilder sb = new StringBuilder(); 475 for (String s : unexpected) 476 sb.append("Unexpected but found: " + s + "\n"); 477 sb.append("In :"); 478 for (String s : actual) 479 sb.append("\t" + s + "\n"); 480 assertTrue(false, "Jmod content check failed.\n" + sb.toString()); 481 } 482 } 483 484 static JmodResult jmod(String... args) { 485 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 486 PrintStream ps = new PrintStream(baos); 487 System.out.println("jmod " + Arrays.asList(args)); 488 int ec = JMOD_TOOL.run(ps, ps, args); 489 return new JmodResult(ec, new String(baos.toByteArray(), UTF_8)); 490 } 491 492 static class JmodResult { 493 final int exitCode; 494 final String output; 495 496 JmodResult(int exitValue, String output) { 497 this.exitCode = exitValue; 498 this.output = output; 499 } 500 JmodResult assertSuccess() { assertTrue(exitCode == 0, output); return this; } 501 JmodResult assertFailure() { assertTrue(exitCode != 0, output); return this; } 502 JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; } 503 } 504 505 static void createCmds(Path dir) throws IOException { 506 List<String> files = Arrays.asList( 507 "first", "second", "third" + File.separator + "third"); 508 createFiles(dir, files); 509 } 510 511 static void createLibs(Path dir) throws IOException { 512 List<String> files = Arrays.asList( 513 "first.so", "second.so", "third" + File.separator + "third.so"); 514 createFiles(dir, files); 515 } 516 517 static void createConfigs(Path dir) throws IOException { 518 List<String> files = Arrays.asList( 519 "first.cfg", "second.cfg", "third" + File.separator + "third.cfg"); 520 createFiles(dir, files); 521 } 522 523 static void createFiles(Path dir, List<String> filenames) throws IOException { 524 for (String name : filenames) { 525 Path file = dir.resolve(name); 526 Files.createDirectories(file.getParent()); 527 Files.createFile(file); 528 try (OutputStream os = Files.newOutputStream(file)) { 529 os.write("blahblahblah".getBytes(UTF_8)); 530 } 531 } 532 } 533 534 // Standalone entry point. 535 public static void main(String[] args) throws Throwable { 536 JmodTest test = new JmodTest(); 537 test.buildExplodedModules(); 538 for (Method m : JmodTest.class.getDeclaredMethods()) { 539 if (m.getAnnotation(Test.class) != null) { 540 System.out.println("Invoking " + m.getName()); 541 m.invoke(test); 542 } 543 } 544 } 545 }