| /* | |
| * Copyright (c) 2015, 2024, 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. | |
| */ | |
| package tests; | |
| import java.io.File; | |
| import java.io.IOException; | |
| import java.net.URI; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.FileSystems; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.stream.Collectors; | |
| import tests.JImageGenerator.JLinkTask; | |
| import tests.JImageGenerator.JModTask; | |
| /** | |
| * JLink tests helper. | |
| */ | |
| public class Helper { | |
| private final Path explodedmodssrc; | |
| private final Path jmodssrc; | |
| private final Path jarssrc; | |
| private final Path explodedmodsclasses; | |
| private final Path jmodsclasses; | |
| private final Path jarsclasses; | |
| private final Path jmods; | |
| private final Path jars; | |
| private final Path images; | |
| private final Path explodedmods; | |
| private final Path stdjmods; | |
| private final Path extracted; | |
| private final Path recreated; | |
| private final Map<String, List<String>> moduleClassDependencies = new HashMap<>(); | |
| private final Map<String, List<String>> moduleDependencies = new HashMap<>(); | |
| private final List<String> bootClasses; | |
| private final FileSystem fs; | |
| private final boolean linkableRuntime; | |
| private static final Path JDK_HOME = Paths.get(System.getProperty("test.jdk")); | |
| public static Helper newHelper() throws IOException { | |
| return newHelper(false); | |
| } | |
| public static Helper newHelper(boolean linkableRuntime) throws IOException { | |
| if (!linkableRuntime && !jdkHasPackagedModules()) { | |
| // Skip test if the jmods directory is missing (e.g. exploded image) | |
| System.err.println("Test not run, NO jmods directory"); | |
| return null; | |
| } | |
| return new Helper(JDK_HOME, linkableRuntime); | |
| } | |
| public static boolean jdkHasPackagedModules() { | |
| return Files.exists(JDK_HOME.resolve("jmods")); | |
| } | |
| private Helper(Path jdkHome, boolean linkableRuntime) throws IOException { | |
| this.linkableRuntime = linkableRuntime; | |
| this.stdjmods = jdkHome.resolve("jmods").normalize(); | |
| if (!linkableRuntime && !Files.exists(stdjmods)) { | |
| throw new IOException("Standard jMods do not exist."); | |
| } | |
| this.fs = FileSystems.getFileSystem(URI.create("jrt:/")); | |
| Path javabase = fs.getPath("/modules/java.base"); | |
| this.bootClasses = Files.find(javabase, Integer.MAX_VALUE, | |
| (file, attrs) -> file.toString().endsWith(".class")) | |
| .map(Object::toString) | |
| .map(s -> s.substring("/modules".length())) | |
| .collect(Collectors.toList()); | |
| if (bootClasses.isEmpty()) { | |
| throw new AssertionError("No boot class to check against"); | |
| } | |
| this.jmods = Paths.get("jmods").toAbsolutePath(); | |
| Files.createDirectories(jmods); | |
| this.jars = Paths.get("jars").toAbsolutePath(); | |
| Files.createDirectories(jars); | |
| this.explodedmods = Paths.get("explodedmods").toAbsolutePath(); | |
| Files.createDirectories(explodedmods); | |
| this.explodedmodssrc = explodedmods.resolve("src"); | |
| Files.createDirectories(explodedmodssrc); | |
| this.jarssrc = jars.resolve("src"); | |
| Files.createDirectories(jarssrc); | |
| this.jmodssrc = jmods.resolve("src"); | |
| Files.createDirectories(jmodssrc); | |
| this.explodedmodsclasses = explodedmods.resolve("classes"); | |
| Files.createDirectories(explodedmodsclasses); | |
| this.jmodsclasses = jmods.resolve("classes"); | |
| Files.createDirectories(jmodsclasses); | |
| this.jarsclasses = jars.resolve("classes"); | |
| Files.createDirectories(jarsclasses); | |
| this.images = Paths.get("images").toAbsolutePath(); | |
| Files.createDirectories(images); | |
| this.extracted = Paths.get("extracted").toAbsolutePath(); | |
| Files.createDirectories(extracted); | |
| this.recreated = Paths.get("recreated").toAbsolutePath(); | |
| Files.createDirectories(recreated); | |
| } | |
| public void generateDefaultModules() throws IOException { | |
| generateDefaultJModule("leaf1"); | |
| generateDefaultJModule("leaf2"); | |
| generateDefaultJModule("leaf3"); | |
| generateDefaultJarModule("leaf4"); | |
| generateDefaultJarModule("leaf5"); | |
| generateDefaultExplodedModule("leaf6"); | |
| generateDefaultExplodedModule("leaf7"); | |
| generateDefaultJarModule("composite1", "leaf1", "leaf2", "leaf4", "leaf6"); | |
| generateDefaultJModule("composite2", "composite1", "leaf3", "leaf5", "leaf7", | |
| "java.management"); | |
| } | |
| public String defaultModulePath() { | |
| return defaultModulePath(true); | |
| } | |
| public String defaultModulePath(boolean includeStdMods) { | |
| String standardMods = linkableRuntime ? "" : stdjmods.toAbsolutePath().toString() + File.pathSeparator; | |
| return (includeStdMods? standardMods : "") | |
| + jmods.toAbsolutePath().toString() + File.pathSeparator | |
| + jars.toAbsolutePath().toString() + File.pathSeparator | |
| + explodedmodsclasses.toAbsolutePath().toString(); | |
| } | |
| public Path generateModuleCompiledClasses( | |
| Path src, Path classes, String moduleName, String... dependencies) throws IOException { | |
| return generateModuleCompiledClasses(src, classes, moduleName, getDefaultClasses(moduleName), dependencies); | |
| } | |
| public Path generateModuleCompiledClasses( | |
| Path src, Path classes, String moduleName, | |
| List<String> classNames, String... dependencies) throws IOException { | |
| if (classNames == null) { | |
| classNames = getDefaultClasses(moduleName); | |
| } | |
| putAppClasses(moduleName, classNames); | |
| moduleDependencies.put(moduleName, Arrays.asList(dependencies)); | |
| String modulePath = defaultModulePath(); | |
| JImageGenerator.generateSourcesFromTemplate(src, moduleName, classNames.toArray(new String[classNames.size()])); | |
| List<String> packages = classNames.stream() | |
| .map(JImageGenerator::getPackageName) | |
| .distinct() | |
| .collect(Collectors.toList()); | |
| Path srcMod = src.resolve(moduleName); | |
| JImageGenerator.generateModuleInfo(srcMod, packages, dependencies); | |
| Path destination = classes.resolve(moduleName); | |
| if (!JImageGenerator.compile(srcMod, destination, "--module-path", modulePath, "-g")) { | |
| throw new AssertionError("Compilation failure"); | |
| } | |
| return destination; | |
| } | |
| public Result generateDefaultJModule(String moduleName, String... dependencies) throws IOException { | |
| return generateDefaultJModule(moduleName, getDefaultClasses(moduleName), dependencies); | |
| } | |
| public Result generateDefaultJModule(String moduleName, List<String> classNames, | |
| String... dependencies) throws IOException { | |
| generateModuleCompiledClasses(jmodssrc, jmodsclasses, moduleName, classNames, dependencies); | |
| generateGarbage(jmodsclasses.resolve(moduleName)); | |
| Path jmodFile = jmods.resolve(moduleName + ".jmod"); | |
| JModTask task = JImageGenerator.getJModTask(linkableRuntime) | |
| .jmod(jmodFile) | |
| .addJmods(stdjmods) | |
| .addJmods(jmods.toAbsolutePath()) | |
| .addJars(jars.toAbsolutePath()) | |
| .addClassPath(jmodsclasses.resolve(moduleName)); | |
| if (!classNames.isEmpty()) { | |
| task.mainClass(classNames.get(0)); | |
| } | |
| return task.create(); | |
| } | |
| public Result generateDefaultJarModule(String moduleName, String... dependencies) throws IOException { | |
| return generateDefaultJarModule(moduleName, getDefaultClasses(moduleName), dependencies); | |
| } | |
| public Result generateDefaultJarModule(String moduleName, List<String> classNames, | |
| String... dependencies) throws IOException { | |
| generateModuleCompiledClasses(jarssrc, jarsclasses, moduleName, classNames, dependencies); | |
| generateGarbage(jarsclasses.resolve(moduleName)); | |
| Path jarFile = jars.resolve(moduleName + ".jar"); | |
| JImageGenerator.createJarFile(jarFile, jarsclasses.resolve(moduleName)); | |
| return new Result(0, "", jarFile); | |
| } | |
| public Result generateDefaultExplodedModule(String moduleName, String... dependencies) throws IOException { | |
| return generateDefaultExplodedModule(moduleName, getDefaultClasses(moduleName), dependencies); | |
| } | |
| public Result generateDefaultExplodedModule(String moduleName, List<String> classNames, | |
| String... dependencies) throws IOException { | |
| generateModuleCompiledClasses(explodedmodssrc, explodedmodsclasses, | |
| moduleName, classNames, dependencies); | |
| Path dir = explodedmods.resolve("classes").resolve(moduleName); | |
| return new Result(0, "", dir); | |
| } | |
| private void generateGarbage(Path compiled) throws IOException { | |
| Path metaInf = compiled.resolve("META-INF").resolve("services"); | |
| Files.createDirectories(metaInf); | |
| Path provider = metaInf.resolve("MyProvider"); | |
| Files.createFile(provider); | |
| Files.createFile(compiled.resolve("toto.jcov")); | |
| } | |
| public static Path createNewFile(Path root, String pathName, String extension) { | |
| Path out = root.resolve(pathName + extension); | |
| int i = 1; | |
| while (Files.exists(out)) { | |
| out = root.resolve(pathName + "-" + (++i) + extension); | |
| } | |
| return out; | |
| } | |
| public Result generateDefaultImage(String module) { | |
| return generateDefaultImage(new String[0], module); | |
| } | |
| public Result generateDefaultImage(String[] options, String module) { | |
| Path output = createNewFile(images, module, ".image"); | |
| JLinkTask jLinkTask = JImageGenerator.getJLinkTask() | |
| .modulePath(defaultModulePath()) | |
| .output(output) | |
| .addMods(module) | |
| .limitMods(module); | |
| for (String option : options) { | |
| jLinkTask.option(option); | |
| } | |
| return jLinkTask.call(); | |
| } | |
| private List<String> getDefaultClasses(String module) { | |
| return Arrays.asList(module + ".Main", module + ".com.foo.bar.X"); | |
| } | |
| private void putAppClasses(String module, List<String> classes) { | |
| List<String> appClasses = toLocation(module, classes).stream().collect(Collectors.toList()); | |
| appClasses.add(toLocation(module, "module-info")); | |
| moduleClassDependencies.put(module, appClasses); | |
| } | |
| private static String toLocation(String module, String className) { | |
| return "/" + module + "/" + className.replaceAll("\\.", "/") + ".class"; | |
| } | |
| public static List<String> toLocation(String module, List<String> classNames) { | |
| return classNames.stream() | |
| .map(clazz -> toLocation(module, clazz)) | |
| .collect(Collectors.toList()); | |
| } | |
| public void checkImage(Path imageDir, String module, String[] paths, String[] files) throws IOException { | |
| checkImage(imageDir, module, paths, files, null); | |
| } | |
| public void checkImage(Path imageDir, String module, String[] paths, String[] files, String[] expectedFiles) throws IOException { | |
| List<String> unexpectedPaths = new ArrayList<>(); | |
| if (paths != null) { | |
| Collections.addAll(unexpectedPaths, paths); | |
| } | |
| List<String> unexpectedFiles = new ArrayList<>(); | |
| if (files != null) { | |
| Collections.addAll(unexpectedFiles, files); | |
| } | |
| JImageValidator validator = new JImageValidator(module, gatherExpectedLocations(module), | |
| imageDir.toFile(), | |
| unexpectedPaths, | |
| unexpectedFiles, | |
| expectedFiles); | |
| System.out.println("*** Validate Image " + module); | |
| validator.validate(); | |
| long moduleExecutionTime = validator.getModuleLauncherExecutionTime(); | |
| if (moduleExecutionTime != 0) { | |
| System.out.println("Module launcher execution time " + moduleExecutionTime); | |
| } | |
| System.out.println("Java launcher execution time " | |
| + validator.getJavaLauncherExecutionTime()); | |
| System.out.println("***"); | |
| } | |
| private List<String> gatherExpectedLocations(String module) throws IOException { | |
| List<String> expectedLocations = new ArrayList<>(); | |
| expectedLocations.addAll(bootClasses); | |
| List<String> modules = moduleDependencies.get(module); | |
| for (String dep : modules) { | |
| Path path = fs.getPath("/modules/" + dep); | |
| if (Files.exists(path)) { | |
| List<String> locations = Files.find(path, Integer.MAX_VALUE, | |
| (p, attrs) -> Files.isRegularFile(p) && p.toString().endsWith(".class") | |
| && !p.toString().endsWith("module-info.class")) | |
| .map(p -> p.toString().substring("/modules".length())) | |
| .collect(Collectors.toList()); | |
| expectedLocations.addAll(locations); | |
| } | |
| } | |
| List<String> appClasses = moduleClassDependencies.get(module); | |
| if (appClasses != null) { | |
| expectedLocations.addAll(appClasses); | |
| } | |
| return expectedLocations; | |
| } | |
| public static String getDebugSymbolsExtension() { | |
| return ".diz"; | |
| } | |
| public Path createNewImageDir(String moduleName) { | |
| return createNewFile(getImageDir(), moduleName, ".image"); | |
| } | |
| public Path createNewExtractedDir(String name) { | |
| return createNewFile(getExtractedDir(), name, ".extracted"); | |
| } | |
| public Path createNewRecreatedDir(String name) { | |
| return createNewFile(getRecreatedDir(), name, ".jimage"); | |
| } | |
| public Path createNewJmodFile(String moduleName) { | |
| return createNewFile(getJmodDir(), moduleName, ".jmod"); | |
| } | |
| public Path createNewJarFile(String moduleName) { | |
| return createNewFile(getJarDir(), moduleName, ".jar"); | |
| } | |
| public Path getJmodSrcDir() { | |
| return jmodssrc; | |
| } | |
| public Path getJarSrcDir() { | |
| return jarssrc; | |
| } | |
| public Path getJmodClassesDir() { | |
| return jmodsclasses; | |
| } | |
| public Path getJarClassesDir() { | |
| return jarsclasses; | |
| } | |
| public Path getJmodDir() { | |
| return jmods; | |
| } | |
| public Path getExplodedModsDir() { | |
| return explodedmods; | |
| } | |
| public Path getJarDir() { | |
| return jars; | |
| } | |
| public Path getImageDir() { | |
| return images; | |
| } | |
| public Path getStdJmodsDir() { | |
| return stdjmods; | |
| } | |
| public Path getExtractedDir() { | |
| return extracted; | |
| } | |
| public Path getRecreatedDir() { | |
| return recreated; | |
| } | |
| } |