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