| /** |
| * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @library /lib/testlibrary |
| * @modules jdk.jlink/jdk.tools.jmod |
| * jdk.compiler |
| * @build jdk.testlibrary.FileUtils CompilerUtils |
| * @run testng JmodTest |
| * @summary Basic test for jmod |
| */ |
| |
| import java.io.*; |
| import java.lang.module.ModuleDescriptor; |
| import java.lang.reflect.Method; |
| import java.nio.file.*; |
| import java.util.*; |
| import java.util.function.Consumer; |
| import java.util.regex.Pattern; |
| import java.util.stream.Stream; |
| import jdk.testlibrary.FileUtils; |
| import org.testng.annotations.BeforeTest; |
| import org.testng.annotations.Test; |
| |
| import static java.io.File.pathSeparator; |
| import static java.lang.module.ModuleDescriptor.Version; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| import static java.util.stream.Collectors.toSet; |
| import static org.testng.Assert.*; |
| |
| public class JmodTest { |
| |
| static final String TEST_SRC = System.getProperty("test.src", "."); |
| static final Path SRC_DIR = Paths.get(TEST_SRC, "src"); |
| static final Path EXPLODED_DIR = Paths.get("build"); |
| static final Path MODS_DIR = Paths.get("jmods"); |
| |
| static final String CLASSES_PREFIX = "classes/"; |
| static final String CMDS_PREFIX = "bin/"; |
| static final String LIBS_PREFIX = "native/"; |
| static final String CONFIGS_PREFIX = "conf/"; |
| |
| @BeforeTest |
| public void buildExplodedModules() throws IOException { |
| if (Files.exists(EXPLODED_DIR)) |
| FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR); |
| |
| for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) { |
| Path dir = EXPLODED_DIR.resolve(name); |
| assertTrue(compileModule(name, dir.resolve("classes"))); |
| createCmds(dir.resolve("bin")); |
| createLibs(dir.resolve("lib")); |
| createConfigs(dir.resolve("conf")); |
| } |
| |
| if (Files.exists(MODS_DIR)) |
| FileUtils.deleteFileTreeWithRetry(MODS_DIR); |
| Files.createDirectories(MODS_DIR); |
| } |
| |
| @Test |
| public void testList() throws IOException { |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| jmod("create", |
| "--class-path", cp, |
| MODS_DIR.resolve("foo.jmod").toString()) |
| .assertSuccess(); |
| |
| jmod("list", |
| MODS_DIR.resolve("foo.jmod").toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| // asserts dependent on the exact contents of foo |
| assertContains(r.output, CLASSES_PREFIX + "module-info.class"); |
| assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/Foo.class"); |
| assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); |
| }); |
| } |
| |
| @Test |
| public void testMainClass() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooMainClass.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| |
| jmod("create", |
| "--class-path", cp, |
| "--main-class", "jdk.test.foo.Foo", |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| Optional<String> omc = getModuleDescriptor(jmod).mainClass(); |
| assertTrue(omc.isPresent()); |
| assertEquals(omc.get(), "jdk.test.foo.Foo"); |
| }); |
| } |
| |
| @Test |
| public void testModuleVersion() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooVersion.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| |
| jmod("create", |
| "--class-path", cp, |
| "--module-version", "5.4.3", |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| Optional<Version> ov = getModuleDescriptor(jmod).version(); |
| assertTrue(ov.isPresent()); |
| assertEquals(ov.get().toString(), "5.4.3"); |
| }); |
| } |
| |
| @Test |
| public void testConfig() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooConfig.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); |
| Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); |
| |
| jmod("create", |
| "--class-path", cp.toString(), |
| "--config", cf.toString(), |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| try (Stream<String> s1 = findFiles(cf).map(p -> CONFIGS_PREFIX + p); |
| Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { |
| Set<String> expectedFilenames = Stream.concat(s1, s2) |
| .collect(toSet()); |
| assertJmodContent(jmod, expectedFilenames); |
| } |
| }); |
| } |
| |
| @Test |
| public void testCmds() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooCmds.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); |
| Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); |
| |
| jmod("create", |
| "--cmds", bp.toString(), |
| "--class-path", cp.toString(), |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| try (Stream<String> s1 = findFiles(bp).map(p -> CMDS_PREFIX + p); |
| Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { |
| Set<String> expectedFilenames = Stream.concat(s1,s2) |
| .collect(toSet()); |
| assertJmodContent(jmod, expectedFilenames); |
| } |
| }); |
| } |
| |
| @Test |
| public void testLibs() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooLibs.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); |
| Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); |
| |
| jmod("create", |
| "--libs=", lp.toString(), |
| "--class-path", cp.toString(), |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); |
| Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p)) { |
| Set<String> expectedFilenames = Stream.concat(s1,s2) |
| .collect(toSet()); |
| assertJmodContent(jmod, expectedFilenames); |
| } |
| }); |
| } |
| |
| @Test |
| public void testAll() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooAll.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); |
| Path bp = EXPLODED_DIR.resolve("foo").resolve("bin"); |
| Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); |
| Path cf = EXPLODED_DIR.resolve("foo").resolve("conf"); |
| |
| jmod("create", |
| "--conf", cf.toString(), |
| "--cmds=", bp.toString(), |
| "--libs=", lp.toString(), |
| "--class-path", cp.toString(), |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| try (Stream<String> s1 = findFiles(lp).map(p -> LIBS_PREFIX + p); |
| Stream<String> s2 = findFiles(cp).map(p -> CLASSES_PREFIX + p); |
| Stream<String> s3 = findFiles(bp).map(p -> CMDS_PREFIX + p); |
| Stream<String> s4 = findFiles(cf).map(p -> CONFIGS_PREFIX + p)) { |
| Set<String> expectedFilenames = Stream.concat(Stream.concat(s1,s2), |
| Stream.concat(s3, s4)) |
| .collect(toSet()); |
| assertJmodContent(jmod, expectedFilenames); |
| } |
| }); |
| } |
| |
| @Test |
| public void testExcludes() throws IOException { |
| Path jmod = MODS_DIR.resolve("fooLibs.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); |
| Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); |
| |
| jmod("create", |
| "--libs=", lp.toString(), |
| "--class-path", cp.toString(), |
| "--exclude", "**internal**", |
| "--exclude", "first.so", |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| Set<String> expectedFilenames = new HashSet<>(); |
| expectedFilenames.add(CLASSES_PREFIX + "module-info.class"); |
| expectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/Foo.class"); |
| expectedFilenames.add(LIBS_PREFIX + "second.so"); |
| expectedFilenames.add(LIBS_PREFIX + "third/third.so"); |
| assertJmodContent(jmod, expectedFilenames); |
| |
| Set<String> unexpectedFilenames = new HashSet<>(); |
| unexpectedFilenames.add(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); |
| unexpectedFilenames.add(LIBS_PREFIX + "first.so"); |
| assertJmodDoesNotContain(jmod, unexpectedFilenames); |
| }); |
| } |
| |
| @Test |
| public void describe() throws IOException { |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| jmod("create", |
| "--class-path", cp, |
| MODS_DIR.resolve("describeFoo.jmod").toString()) |
| .assertSuccess(); |
| |
| jmod("describe", |
| MODS_DIR.resolve("describeFoo.jmod").toString()) |
| .assertSuccess() |
| .resultChecker(r -> { |
| // Expect similar output: "foo, requires mandated java.base |
| // exports jdk.test.foo, conceals jdk.test.foo.internal" |
| Pattern p = Pattern.compile("\\s+foo\\s+requires\\s+mandated\\s+java.base"); |
| assertTrue(p.matcher(r.output).find(), |
| "Expecting to find \"foo, requires java.base\"" + |
| "in output, but did not: [" + r.output + "]"); |
| p = Pattern.compile( |
| "exports\\s+jdk.test.foo\\s+conceals\\s+jdk.test.foo.internal"); |
| assertTrue(p.matcher(r.output).find(), |
| "Expecting to find \"exports ..., conceals ...\"" + |
| "in output, but did not: [" + r.output + "]"); |
| }); |
| } |
| |
| @Test |
| public void testDuplicateEntries() throws IOException { |
| Path jmod = MODS_DIR.resolve("testDuplicates.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| Path lp = EXPLODED_DIR.resolve("foo").resolve("lib"); |
| |
| jmod("create", |
| "--class-path", cp + pathSeparator + cp, |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> |
| assertContains(r.output, "Warning: ignoring duplicate entry") |
| ); |
| |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| jmod("create", |
| "--class-path", cp, |
| "--libs", lp.toString() + pathSeparator + lp.toString(), |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> |
| assertContains(r.output, "Warning: ignoring duplicate entry") |
| ); |
| } |
| |
| @Test |
| public void testIgnoreModuleInfoInOtherSections() throws IOException { |
| Path jmod = MODS_DIR.resolve("testIgnoreModuleInfoInOtherSections.jmod"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| |
| jmod("create", |
| "--class-path", cp, |
| "--libs", cp, |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> |
| assertContains(r.output, "Warning: ignoring entry") |
| ); |
| |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| jmod("create", |
| "--class-path", cp, |
| "--cmds", cp, |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> |
| assertContains(r.output, "Warning: ignoring entry") |
| ); |
| } |
| |
| @Test |
| public void testVersion() { |
| jmod("--version") |
| .assertSuccess() |
| .resultChecker(r -> { |
| assertContains(r.output, System.getProperty("java.version")); |
| }); |
| } |
| |
| @Test |
| public void testHelp() { |
| jmod("--help") |
| .assertSuccess() |
| .resultChecker(r -> |
| assertTrue(r.output.startsWith("Usage: jmod"), "Help not printed") |
| ); |
| } |
| |
| @Test |
| public void testTmpFileAlreadyExists() throws IOException { |
| // Implementation detail: jmod tool creates <jmod-file>.tmp |
| // Ensure that there are no problems if existing |
| |
| Path jmod = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod"); |
| Path tmp = MODS_DIR.resolve("testTmpFileAlreadyExists.jmod.tmp"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| FileUtils.deleteFileIfExistsWithRetry(tmp); |
| Files.createFile(tmp); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); |
| |
| jmod("create", |
| "--class-path", cp, |
| jmod.toString()) |
| .assertSuccess() |
| .resultChecker(r -> |
| assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp) |
| ); |
| } |
| |
| @Test |
| public void testTmpFileRemoved() throws IOException { |
| // Implementation detail: jmod tool creates <jmod-file>.tmp |
| // Ensure that it is removed in the event of a failure. |
| // The failure in this case is a class in the unnamed package. |
| |
| Path jmod = MODS_DIR.resolve("testTmpFileRemoved.jmod"); |
| Path tmp = MODS_DIR.resolve("testTmpFileRemoved.jmod.tmp"); |
| FileUtils.deleteFileIfExistsWithRetry(jmod); |
| FileUtils.deleteFileIfExistsWithRetry(tmp); |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes") + File.pathSeparator + |
| EXPLODED_DIR.resolve("foo").resolve("classes") |
| .resolve("jdk").resolve("test").resolve("foo").toString(); |
| |
| jmod("create", |
| "--class-path", cp, |
| jmod.toString()) |
| .assertFailure() |
| .resultChecker(r -> { |
| assertContains(r.output, "unnamed package"); |
| assertTrue(Files.notExists(tmp), "Unexpected tmp file:" + tmp); |
| }); |
| } |
| |
| // --- |
| |
| static boolean compileModule(String name, Path dest) throws IOException { |
| return CompilerUtils.compile(SRC_DIR.resolve(name), dest); |
| } |
| |
| static void assertContains(String output, String subString) { |
| if (output.contains(subString)) |
| assertTrue(true); |
| else |
| assertTrue(false,"Expected to find [" + subString + "], in output [" |
| + output + "]" + "\n"); |
| } |
| |
| static ModuleDescriptor getModuleDescriptor(Path jmod) { |
| ClassLoader cl = ClassLoader.getSystemClassLoader(); |
| try (FileSystem fs = FileSystems.newFileSystem(jmod, cl)) { |
| String p = "/classes/module-info.class"; |
| try (InputStream is = Files.newInputStream(fs.getPath(p))) { |
| return ModuleDescriptor.read(is); |
| } |
| } catch (IOException ioe) { |
| throw new UncheckedIOException(ioe); |
| } |
| } |
| |
| static Stream<String> findFiles(Path dir) { |
| try { |
| return Files.find(dir, Integer.MAX_VALUE, (p, a) -> a.isRegularFile()) |
| .map(dir::relativize) |
| .map(Path::toString) |
| .map(p -> p.replace(File.separator, "/")); |
| } catch (IOException x) { |
| throw new UncheckedIOException(x); |
| } |
| } |
| |
| static Set<String> getJmodContent(Path jmod) { |
| JmodResult r = jmod("list", jmod.toString()).assertSuccess(); |
| return Stream.of(r.output.split("\r?\n")).collect(toSet()); |
| } |
| |
| static void assertJmodContent(Path jmod, Set<String> expected) { |
| Set<String> actual = getJmodContent(jmod); |
| if (!Objects.equals(actual, expected)) { |
| Set<String> unexpected = new HashSet<>(actual); |
| unexpected.removeAll(expected); |
| Set<String> notFound = new HashSet<>(expected); |
| notFound.removeAll(actual); |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Unexpected but found:\n"); |
| unexpected.forEach(s -> sb.append("\t" + s + "\n")); |
| sb.append("Expected but not found:\n"); |
| notFound.forEach(s -> sb.append("\t" + s + "\n")); |
| assertTrue(false, "Jmod content check failed.\n" + sb.toString()); |
| } |
| } |
| |
| static void assertJmodDoesNotContain(Path jmod, Set<String> unexpectedNames) { |
| Set<String> actual = getJmodContent(jmod); |
| Set<String> unexpected = new HashSet<>(); |
| for (String name : unexpectedNames) { |
| if (actual.contains(name)) |
| unexpected.add(name); |
| } |
| if (!unexpected.isEmpty()) { |
| StringBuilder sb = new StringBuilder(); |
| for (String s : unexpected) |
| sb.append("Unexpected but found: " + s + "\n"); |
| sb.append("In :"); |
| for (String s : actual) |
| sb.append("\t" + s + "\n"); |
| assertTrue(false, "Jmod content check failed.\n" + sb.toString()); |
| } |
| } |
| |
| static JmodResult jmod(String... args) { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| PrintStream ps = new PrintStream(baos); |
| System.out.println("jmod " + Arrays.asList(args)); |
| int ec = jdk.tools.jmod.Main.run(args, ps); |
| return new JmodResult(ec, new String(baos.toByteArray(), UTF_8)); |
| } |
| |
| static class JmodResult { |
| final int exitCode; |
| final String output; |
| |
| JmodResult(int exitValue, String output) { |
| this.exitCode = exitValue; |
| this.output = output; |
| } |
| JmodResult assertSuccess() { assertTrue(exitCode == 0, output); return this; } |
| JmodResult assertFailure() { assertTrue(exitCode != 0, output); return this; } |
| JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; } |
| } |
| |
| static void createCmds(Path dir) throws IOException { |
| List<String> files = Arrays.asList( |
| "first", "second", "third" + File.separator + "third"); |
| createFiles(dir, files); |
| } |
| |
| static void createLibs(Path dir) throws IOException { |
| List<String> files = Arrays.asList( |
| "first.so", "second.so", "third" + File.separator + "third.so"); |
| createFiles(dir, files); |
| } |
| |
| static void createConfigs(Path dir) throws IOException { |
| List<String> files = Arrays.asList( |
| "first.cfg", "second.cfg", "third" + File.separator + "third.cfg"); |
| createFiles(dir, files); |
| } |
| |
| static void createFiles(Path dir, List<String> filenames) throws IOException { |
| for (String name : filenames) { |
| Path file = dir.resolve(name); |
| Files.createDirectories(file.getParent()); |
| Files.createFile(file); |
| try (OutputStream os = Files.newOutputStream(file)) { |
| os.write("blahblahblah".getBytes(UTF_8)); |
| } |
| } |
| } |
| |
| // Standalone entry point. |
| public static void main(String[] args) throws Throwable { |
| JmodTest test = new JmodTest(); |
| test.buildExplodedModules(); |
| for (Method m : JmodTest.class.getDeclaredMethods()) { |
| if (m.getAnnotation(Test.class) != null) { |
| System.out.println("Invoking " + m.getName()); |
| m.invoke(test); |
| } |
| } |
| } |
| } |