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