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 }