| /* | |
| * Copyright (c) 2015, 2025, 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 | |
| * @bug 8142968 8166568 8166286 8170618 8168149 8240910 8276764 8276766 8353267 | |
| * @summary Basic test for jmod | |
| * @library /test/lib | |
| * @modules jdk.compiler | |
| * jdk.jlink | |
| * java.base/jdk.internal.module | |
| * @build jdk.test.lib.compiler.CompilerUtils | |
| * jdk.test.lib.util.FileUtils | |
| * jdk.test.lib.Platform | |
| * @run testng/othervm -Djava.io.tmpdir=. JmodTest | |
| */ | |
| 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.spi.ToolProvider; | |
| import java.util.stream.Stream; | |
| import jdk.test.lib.compiler.CompilerUtils; | |
| import jdk.test.lib.util.FileUtils; | |
| import jdk.test.lib.util.ModuleInfoWriter; | |
| 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 ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") | |
| .orElseThrow(() -> | |
| new RuntimeException("jmod tool not found") | |
| ); | |
| static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") | |
| .orElseThrow(() -> | |
| new RuntimeException("jar tool not found") | |
| ); | |
| 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 = "lib/"; | |
| 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"))); | |
| copyResource(SRC_DIR.resolve("foo"), | |
| dir.resolve("classes"), | |
| "jdk/test/foo/resources/foo.properties"); | |
| 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); | |
| } | |
| // JDK-8166286 - jmod fails on symlink to directory | |
| @Test | |
| public void testDirSymlinks() throws IOException { | |
| Path apaDir = EXPLODED_DIR.resolve("apa"); | |
| Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes"); | |
| assertTrue(compileModule("apa", classesDir)); | |
| Path libDir = apaDir.resolve("lib"); | |
| createFiles(libDir, List.of("foo/bar/libfoo.so")); | |
| try { | |
| Path link = Files.createSymbolicLink( | |
| libDir.resolve("baz"), libDir.resolve("foo").toAbsolutePath()); | |
| assertTrue(Files.exists(link)); | |
| } catch (IOException|UnsupportedOperationException uoe) { | |
| // OS does not support symlinks. Nothing to test! | |
| System.out.println("Creating symlink failed. Test passes vacuously."); | |
| uoe.printStackTrace(); | |
| return; | |
| } | |
| Path jmod = MODS_DIR.resolve("apa.jmod"); | |
| jmod("create", | |
| "--libs=" + libDir.toString(), | |
| "--class-path", classesDir.toString(), | |
| jmod.toString()) | |
| .assertSuccess(); | |
| Files.delete(jmod); | |
| } | |
| // JDK-8267583 - jmod fails on symlink to class file | |
| @Test | |
| public void testFileSymlinks() throws IOException { | |
| Path apaDir = EXPLODED_DIR.resolve("apa"); | |
| Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes"); | |
| assertTrue(compileModule("apa", classesDir)); | |
| Files.move(classesDir.resolve("module-info.class"), | |
| classesDir.resolve("module-info.class1")); | |
| Files.move(classesDir.resolve(Paths.get("jdk", "test", "apa", "Apa.class")), | |
| classesDir.resolve("Apa.class1")); | |
| try { | |
| Path link = Files.createSymbolicLink( | |
| classesDir.resolve("module-info.class"), | |
| classesDir.resolve("module-info.class1").toAbsolutePath()); | |
| assertTrue(Files.exists(link)); | |
| link = Files.createSymbolicLink( | |
| classesDir.resolve(Paths.get("jdk", "test", "apa", "Apa.class")), | |
| classesDir.resolve("Apa.class1").toAbsolutePath()); | |
| assertTrue(Files.exists(link)); | |
| } catch (IOException|UnsupportedOperationException uoe) { | |
| // OS does not support symlinks. Nothing to test! | |
| System.out.println("Creating symlinks failed. Test passes vacuously."); | |
| uoe.printStackTrace(); | |
| return; | |
| } | |
| Path jmod = MODS_DIR.resolve("apa.jmod"); | |
| jmod("create", | |
| "--class-path", classesDir.toString(), | |
| jmod.toString()) | |
| .assertSuccess(); | |
| Files.delete(jmod); | |
| } | |
| // JDK-8170618 - jmod should validate if any exported or open package is missing | |
| @Test | |
| public void testMissingPackages() throws IOException { | |
| Path apaDir = EXPLODED_DIR.resolve("apa"); | |
| Path classesDir = EXPLODED_DIR.resolve("apa").resolve("classes"); | |
| if (Files.exists(classesDir)) | |
| FileUtils.deleteFileTreeWithRetry(classesDir); | |
| assertTrue(compileModule("apa", classesDir)); | |
| FileUtils.deleteFileTreeWithRetry(classesDir.resolve("jdk")); | |
| Path jmod = MODS_DIR.resolve("apa.jmod"); | |
| jmod("create", | |
| "--class-path", classesDir.toString(), | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertContains(r.output, "Packages that are exported or open in apa are not present: [jdk.test.apa]"); | |
| }); | |
| if (Files.exists(classesDir)) | |
| FileUtils.deleteFileTreeWithRetry(classesDir); | |
| } | |
| @Test | |
| public void testList() throws IOException { | |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); | |
| Path jmod = MODS_DIR.resolve("foo.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| jmod.toString()) | |
| .assertSuccess(); | |
| jmod("list", | |
| 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"); | |
| assertContains(r.output, CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties"); | |
| // JDK-8276764: Ensure the order is sorted for reproducible jmod content | |
| // module-info, followed by <sorted classes> | |
| int mod_info_i = r.output.indexOf(CLASSES_PREFIX + "module-info.class"); | |
| int foo_cls_i = r.output.indexOf(CLASSES_PREFIX + "jdk/test/foo/Foo.class"); | |
| int msg_i = r.output.indexOf(CLASSES_PREFIX + "jdk/test/foo/internal/Message.class"); | |
| int res_i = r.output.indexOf(CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties"); | |
| System.out.println("jmod classes sort order check:\n"+r.output); | |
| assertTrue(mod_info_i < foo_cls_i); | |
| assertTrue(foo_cls_i < msg_i); | |
| assertTrue(msg_i < res_i); | |
| }); | |
| } | |
| @Test | |
| public void testSourceDateReproducible() throws IOException { | |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); | |
| Path jmod1 = MODS_DIR.resolve("foo1.jmod"); | |
| Path jmod2 = MODS_DIR.resolve("foo2.jmod"); | |
| Path jmod3 = MODS_DIR.resolve("foo3.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod1); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod2); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod3); | |
| // Use source date of 15/03/2022 | |
| String sourceDate = "2022-03-15T00:00:00+00:00"; | |
| jmod("create", | |
| "--class-path", cp, | |
| "--date", sourceDate, | |
| jmod1.toString()) | |
| .assertSuccess(); | |
| try { | |
| // Sleep 5 seconds to ensure zip timestamps might be different if they could be | |
| Thread.sleep(5000); | |
| } catch(InterruptedException ex) {} | |
| jmod("create", | |
| "--class-path", cp, | |
| "--date", sourceDate, | |
| jmod2.toString()) | |
| .assertSuccess(); | |
| // Compare file byte content to see if they are identical | |
| assertSameContent(jmod1, jmod2); | |
| // Use a date before 1980 and assert failure error | |
| sourceDate = "1976-03-15T00:00:00+00:00"; | |
| jmod("create", | |
| "--class-path", cp, | |
| "--date", sourceDate, | |
| jmod3.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertContains(r.output, "is out of the valid range"); | |
| }); | |
| // Use a date after 2099 and assert failure error | |
| sourceDate = "2100-03-15T00:00:00+00:00"; | |
| jmod("create", | |
| "--class-path", cp, | |
| "--date", sourceDate, | |
| jmod3.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertContains(r.output, "is out of the valid range"); | |
| }); | |
| } | |
| @Test | |
| public void testExtractCWD() throws IOException { | |
| Path cp = EXPLODED_DIR.resolve("foo").resolve("classes"); | |
| jmod("create", | |
| "--class-path", cp.toString(), | |
| MODS_DIR.resolve("fooExtractCWD.jmod").toString()) | |
| .assertSuccess(); | |
| jmod("extract", | |
| MODS_DIR.resolve("fooExtractCWD.jmod").toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| // module-info should exist, but jmod will have added its Packages attr. | |
| assertTrue(Files.exists(Paths.get("classes/module-info.class"))); | |
| assertSameContent(cp.resolve("jdk/test/foo/Foo.class"), | |
| Paths.get("classes/jdk/test/foo/Foo.class")); | |
| assertSameContent(cp.resolve("jdk/test/foo/internal/Message.class"), | |
| Paths.get("classes/jdk/test/foo/internal/Message.class")); | |
| assertSameContent(cp.resolve("jdk/test/foo/resources/foo.properties"), | |
| Paths.get("classes/jdk/test/foo/resources/foo.properties")); | |
| }); | |
| } | |
| @Test | |
| public void testExtractDir() throws IOException { | |
| if (Files.exists(Paths.get("extractTestDir"))) | |
| FileUtils.deleteFileTreeWithRetry(Paths.get("extractTestDir")); | |
| 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(), | |
| MODS_DIR.resolve("fooExtractDir.jmod").toString()) | |
| .assertSuccess(); | |
| jmod("extract", | |
| "--dir", "extractTestDir", | |
| MODS_DIR.resolve("fooExtractDir.jmod").toString()) | |
| .assertSuccess(); | |
| jmod("extract", | |
| "--dir", "extractTestDir", | |
| MODS_DIR.resolve("fooExtractDir.jmod").toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| // check a sample of the extracted files | |
| Path p = Paths.get("extractTestDir"); | |
| assertTrue(Files.exists(p.resolve("classes/module-info.class"))); | |
| assertSameContent(cp.resolve("jdk/test/foo/Foo.class"), | |
| p.resolve("classes/jdk/test/foo/Foo.class")); | |
| assertSameContent(bp.resolve("first"), | |
| p.resolve(CMDS_PREFIX).resolve("first")); | |
| assertSameContent(lp.resolve("first.so"), | |
| p.resolve(LIBS_PREFIX).resolve("second.so")); | |
| assertSameContent(cf.resolve("second.cfg"), | |
| p.resolve(CONFIGS_PREFIX).resolve("second.cfg")); | |
| }); | |
| } | |
| @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(CLASSES_PREFIX + "jdk/test/foo/resources/foo.properties"); | |
| 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... exports jdk.test.foo ... | |
| // ... requires java.base mandated... contains jdk.test.foo.internal" | |
| Pattern p = Pattern.compile("foo\\s+exports\\s+jdk.test.foo"); | |
| assertTrue(p.matcher(r.output).find(), | |
| "Expecting to find \"foo... exports jdk.test.foo\"" + | |
| "in output, but did not: [" + r.output + "]"); | |
| p = Pattern.compile( | |
| "requires\\s+java.base\\s+mandated\\s+contains\\s+jdk.test.foo.internal"); | |
| assertTrue(p.matcher(r.output).find(), | |
| "Expecting to find \"requires java.base mandated..., " + | |
| "contains jdk.test.foo.internal ...\"" + | |
| "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 testDuplicateEntriesFromJarFile() throws IOException { | |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); | |
| Path jar = Paths.get("foo.jar"); | |
| Path jmod = MODS_DIR.resolve("testDuplicates.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jar); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| // create JAR file | |
| assertTrue(JAR_TOOL.run(System.out, System.err, "cf", jar.toString(), "-C", cp, ".") == 0); | |
| jmod("create", | |
| "--class-path", jar.toString() + pathSeparator + jar.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 testLastOneWins() throws IOException { | |
| Path workDir = Paths.get("lastOneWins"); | |
| if (Files.exists(workDir)) | |
| FileUtils.deleteFileTreeWithRetry(workDir); | |
| Files.createDirectory(workDir); | |
| Path jmod = MODS_DIR.resolve("lastOneWins.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"); | |
| Path shouldNotBeAdded = workDir.resolve("shouldNotBeAdded"); | |
| Files.createDirectory(shouldNotBeAdded); | |
| Files.write(shouldNotBeAdded.resolve("aFile"), "hello".getBytes(UTF_8)); | |
| // Pairs of options. For options with required arguments the last one | |
| // should win ( first should be effectively ignored, but may still be | |
| // validated ). | |
| jmod("create", | |
| "--conf", shouldNotBeAdded.toString(), | |
| "--conf", cf.toString(), | |
| "--cmds", shouldNotBeAdded.toString(), | |
| "--cmds", bp.toString(), | |
| "--libs", shouldNotBeAdded.toString(), | |
| "--libs", lp.toString(), | |
| "--class-path", shouldNotBeAdded.toString(), | |
| "--class-path", cp.toString(), | |
| "--main-class", "does.NotExist", | |
| "--main-class", "jdk.test.foo.Foo", | |
| "--module-version", "00001", | |
| "--module-version", "5.4.3", | |
| "--do-not-resolve-by-default", | |
| "--do-not-resolve-by-default", | |
| "--warn-if-resolved=incubating", | |
| "--warn-if-resolved=deprecated", | |
| MODS_DIR.resolve("lastOneWins.jmod").toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| ModuleDescriptor md = getModuleDescriptor(jmod); | |
| Optional<String> omc = md.mainClass(); | |
| assertTrue(omc.isPresent()); | |
| assertEquals(omc.get(), "jdk.test.foo.Foo"); | |
| Optional<Version> ov = md.version(); | |
| assertTrue(ov.isPresent()); | |
| assertEquals(ov.get().toString(), "5.4.3"); | |
| 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); | |
| } | |
| }); | |
| jmod("extract", | |
| "--dir", "blah", | |
| "--dir", "lastOneWinsExtractDir", | |
| jmod.toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| assertTrue(Files.exists(Paths.get("lastOneWinsExtractDir"))); | |
| assertTrue(Files.notExists(Paths.get("blah"))); | |
| }); | |
| } | |
| @Test | |
| public void testPackagesAttribute() throws IOException { | |
| Path jmod = MODS_DIR.resolve("foo.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); | |
| Set<String> expectedPackages = Set.of("jdk.test.foo", | |
| "jdk.test.foo.internal", | |
| "jdk.test.foo.resources"); | |
| jmod("create", | |
| "--class-path", cp, | |
| jmod.toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| Set<String> pkgs = getModuleDescriptor(jmod).packages(); | |
| assertEquals(pkgs, expectedPackages); | |
| }); | |
| } | |
| /** | |
| * Test class files is the META-INF directory. | |
| */ | |
| @Test | |
| public void testClassInMetaInf() throws IOException { | |
| Path jmod = MODS_DIR.resolve("baz.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| ModuleDescriptor descriptor = ModuleDescriptor.newModule("baz").build(); | |
| byte[] moduleInfo = ModuleInfoWriter.toBytes(descriptor); | |
| Path dir = Files.createTempDirectory(Path.of("."), "baz"); | |
| Files.write(dir.resolve("module-info.class"), moduleInfo); | |
| Files.createFile(Files.createDirectory(dir.resolve("p")).resolve("C.class")); | |
| // META-INF/extra/q/C.class | |
| Path extraClasses = dir.resolve("META-INF/extra/"); | |
| Files.createFile(Files.createDirectories(extraClasses.resolve("q")).resolve("C.class")); | |
| Set<String> expectedPackages = Set.of("p"); | |
| Set<String> expectedContent = Set.of( | |
| CLASSES_PREFIX + "module-info.class", | |
| CLASSES_PREFIX + "p/C.class", | |
| CLASSES_PREFIX + "META-INF/extra/q/C.class"); | |
| jmod("create", | |
| "--class-path", dir.toString(), | |
| jmod.toString()) | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| Set<String> pkgs = getModuleDescriptor(jmod).packages(); | |
| assertEquals(pkgs, expectedPackages); | |
| assertJmodContent(jmod, expectedContent); | |
| }); | |
| } | |
| @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"); | |
| assertFalse(r.output.contains("--do-not-resolve-by-default")); | |
| assertFalse(r.output.contains("--warn-if-resolved")); | |
| }); | |
| } | |
| @Test | |
| public void testHelpExtra() { | |
| jmod("--help-extra") | |
| .assertSuccess() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.startsWith("Usage: jmod"), "Extra help not printed"); | |
| assertContains(r.output, "--do-not-resolve-by-default"); | |
| assertContains(r.output, "--warn-if-resolved"); | |
| }); | |
| } | |
| @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); | |
| }); | |
| } | |
| @Test | |
| public void testCompressionLevel() throws IOException { | |
| String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString(); | |
| Path jmod = MODS_DIR.resolve("foo.jmod"); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip-0", | |
| jmod.toString()) | |
| .assertSuccess(); | |
| jmod("list", | |
| "--compress", "zip-0", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress is only accepted with create mode"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip-9", | |
| jmod.toString()) | |
| .assertSuccess(); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip--1", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip-1-something", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip-10", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "zip-", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "test", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| FileUtils.deleteFileIfExistsWithRetry(jmod); | |
| jmod("create", | |
| "--class-path", cp, | |
| "--compress", "test-0", | |
| jmod.toString()) | |
| .assertFailure() | |
| .resultChecker(r -> { | |
| assertTrue(r.output.contains("--compress value is invalid"), "Error message printed"); | |
| }); | |
| } | |
| // --- | |
| 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 void assertSameContent(Path p1, Path p2) { | |
| try { | |
| byte[] ba1 = Files.readAllBytes(p1); | |
| byte[] ba2 = Files.readAllBytes(p2); | |
| assertEquals(ba1, ba2); | |
| } catch (IOException x) { | |
| throw new UncheckedIOException(x); | |
| } | |
| } | |
| static JmodResult jmod(String... args) { | |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
| PrintStream ps = new PrintStream(baos); | |
| System.out.println("jmod " + Arrays.asList(args)); | |
| int ec = JMOD_TOOL.run(ps, ps, args); | |
| 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)); | |
| } | |
| } | |
| } | |
| static void copyResource(Path srcDir, Path dir, String resource) throws IOException { | |
| Path dest = dir.resolve(resource); | |
| Files.deleteIfExists(dest); | |
| Files.createDirectories(dest.getParent()); | |
| Files.copy(srcDir.resolve(resource), dest); | |
| } | |
| // 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); | |
| } | |
| } | |
| } | |
| } |