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