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