1 /*
2  * Copyright (c) 2015, 2018, 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 package tests;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.net.URI;
28 import java.nio.file.FileSystem;
29 import java.nio.file.FileSystems;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.stream.Collectors;
40 
41 import tests.JImageGenerator.JLinkTask;
42 import tests.JImageGenerator.JModTask;
43 
44 /**
45  * JLink tests helper.
46  */
47 public class Helper {
48 
49     private final Path explodedmodssrc;
50     private final Path jmodssrc;
51     private final Path jarssrc;
52     private final Path explodedmodsclasses;
53     private final Path jmodsclasses;
54     private final Path jarsclasses;
55     private final Path jmods;
56     private final Path jars;
57     private final Path images;
58     private final Path explodedmods;
59     private final Path stdjmods;
60     private final Path extracted;
61     private final Path recreated;
62 
63     private final Map<String, List<String>> moduleClassDependencies = new HashMap<>();
64     private final Map<String, List<String>> moduleDependencies = new HashMap<>();
65     private final List<String> bootClasses;
66     private final FileSystem fs;
67 
newHelper()68     public static Helper newHelper() throws IOException {
69         Path jdkHome = Paths.get(System.getProperty("test.jdk"));
70         if (!Files.exists(jdkHome.resolve("jmods"))) {
71             // Skip test if the jmods directory is missing (e.g. exploded image)
72             System.err.println("Test not run, NO jmods directory");
73             return null;
74         }
75         return new Helper(jdkHome);
76     }
77 
Helper(Path jdkHome)78     private Helper(Path jdkHome) throws IOException {
79         this.stdjmods = jdkHome.resolve("jmods").normalize();
80         if (!Files.exists(stdjmods)) {
81             throw new IOException("Standard jMods do not exist.");
82         }
83         this.fs = FileSystems.getFileSystem(URI.create("jrt:/"));
84 
85         Path javabase = fs.getPath("/modules/java.base");
86         this.bootClasses = Files.find(javabase, Integer.MAX_VALUE,
87                 (file, attrs) -> file.toString().endsWith(".class"))
88                 .map(Object::toString)
89                 .map(s -> s.substring("/modules".length()))
90                 .collect(Collectors.toList());
91 
92         if (bootClasses.isEmpty()) {
93             throw new AssertionError("No boot class to check against");
94         }
95 
96         this.jmods = Paths.get("jmods").toAbsolutePath();
97         Files.createDirectories(jmods);
98         this.jars = Paths.get("jars").toAbsolutePath();
99         Files.createDirectories(jars);
100         this.explodedmods = Paths.get("explodedmods").toAbsolutePath();
101         Files.createDirectories(explodedmods);
102         this.explodedmodssrc = explodedmods.resolve("src");
103         Files.createDirectories(explodedmodssrc);
104         this.jarssrc = jars.resolve("src");
105         Files.createDirectories(jarssrc);
106         this.jmodssrc = jmods.resolve("src");
107         Files.createDirectories(jmodssrc);
108         this.explodedmodsclasses = explodedmods.resolve("classes");
109         Files.createDirectories(explodedmodsclasses);
110         this.jmodsclasses = jmods.resolve("classes");
111         Files.createDirectories(jmodsclasses);
112         this.jarsclasses = jars.resolve("classes");
113         Files.createDirectories(jarsclasses);
114         this.images = Paths.get("images").toAbsolutePath();
115         Files.createDirectories(images);
116         this.extracted = Paths.get("extracted").toAbsolutePath();
117         Files.createDirectories(extracted);
118         this.recreated = Paths.get("recreated").toAbsolutePath();
119         Files.createDirectories(recreated);
120     }
121 
generateDefaultModules()122     public void generateDefaultModules() throws IOException {
123         generateDefaultJModule("leaf1");
124         generateDefaultJModule("leaf2");
125         generateDefaultJModule("leaf3");
126 
127         generateDefaultJarModule("leaf4");
128         generateDefaultJarModule("leaf5");
129 
130         generateDefaultExplodedModule("leaf6");
131         generateDefaultExplodedModule("leaf7");
132 
133         generateDefaultJarModule("composite1", "leaf1", "leaf2", "leaf4", "leaf6");
134         generateDefaultJModule("composite2", "composite1", "leaf3", "leaf5", "leaf7",
135                 "java.management");
136     }
137 
defaultModulePath()138     public String defaultModulePath() {
139         return defaultModulePath(true);
140     }
141 
defaultModulePath(boolean includeStdMods)142     public String defaultModulePath(boolean includeStdMods) {
143         return (includeStdMods? stdjmods.toAbsolutePath().toString() : "") + File.pathSeparator
144                 + jmods.toAbsolutePath().toString() + File.pathSeparator
145                 + jars.toAbsolutePath().toString() + File.pathSeparator
146                 + explodedmodsclasses.toAbsolutePath().toString();
147     }
148 
generateModuleCompiledClasses( Path src, Path classes, String moduleName, String... dependencies)149     public Path generateModuleCompiledClasses(
150             Path src, Path classes, String moduleName, String... dependencies) throws IOException {
151         return generateModuleCompiledClasses(src, classes, moduleName, getDefaultClasses(moduleName), dependencies);
152     }
153 
generateModuleCompiledClasses( Path src, Path classes, String moduleName, List<String> classNames, String... dependencies)154     public Path generateModuleCompiledClasses(
155             Path src, Path classes, String moduleName,
156             List<String> classNames, String... dependencies) throws IOException {
157         if (classNames == null) {
158             classNames = getDefaultClasses(moduleName);
159         }
160         putAppClasses(moduleName, classNames);
161         moduleDependencies.put(moduleName, Arrays.asList(dependencies));
162         String modulePath = defaultModulePath();
163         JImageGenerator.generateSourcesFromTemplate(src, moduleName, classNames.toArray(new String[classNames.size()]));
164         List<String> packages = classNames.stream()
165                 .map(JImageGenerator::getPackageName)
166                 .distinct()
167                 .collect(Collectors.toList());
168         Path srcMod = src.resolve(moduleName);
169         JImageGenerator.generateModuleInfo(srcMod, packages, dependencies);
170         Path destination = classes.resolve(moduleName);
171         if (!JImageGenerator.compile(srcMod, destination, "--module-path", modulePath, "-g")) {
172             throw new AssertionError("Compilation failure");
173         }
174         return destination;
175     }
176 
generateDefaultJModule(String moduleName, String... dependencies)177     public Result generateDefaultJModule(String moduleName, String... dependencies) throws IOException {
178         return generateDefaultJModule(moduleName, getDefaultClasses(moduleName), dependencies);
179     }
180 
generateDefaultJModule(String moduleName, List<String> classNames, String... dependencies)181     public Result generateDefaultJModule(String moduleName, List<String> classNames,
182                                              String... dependencies) throws IOException {
183         generateModuleCompiledClasses(jmodssrc, jmodsclasses, moduleName, classNames, dependencies);
184         generateGarbage(jmodsclasses.resolve(moduleName));
185 
186         Path jmodFile = jmods.resolve(moduleName + ".jmod");
187         JModTask task = JImageGenerator.getJModTask()
188                 .jmod(jmodFile)
189                 .addJmods(stdjmods)
190                 .addJmods(jmods.toAbsolutePath())
191                 .addJars(jars.toAbsolutePath())
192                 .addClassPath(jmodsclasses.resolve(moduleName));
193         if (!classNames.isEmpty()) {
194             task.mainClass(classNames.get(0));
195         }
196         return task.create();
197     }
198 
generateDefaultJarModule(String moduleName, String... dependencies)199     public Result generateDefaultJarModule(String moduleName, String... dependencies) throws IOException {
200         return generateDefaultJarModule(moduleName, getDefaultClasses(moduleName), dependencies);
201     }
202 
generateDefaultJarModule(String moduleName, List<String> classNames, String... dependencies)203     public Result generateDefaultJarModule(String moduleName, List<String> classNames,
204                                          String... dependencies) throws IOException {
205         generateModuleCompiledClasses(jarssrc, jarsclasses, moduleName, classNames, dependencies);
206         generateGarbage(jarsclasses.resolve(moduleName));
207 
208         Path jarFile = jars.resolve(moduleName + ".jar");
209         JImageGenerator.createJarFile(jarFile, jarsclasses.resolve(moduleName));
210         return new Result(0, "", jarFile);
211     }
212 
generateDefaultExplodedModule(String moduleName, String... dependencies)213     public Result generateDefaultExplodedModule(String moduleName, String... dependencies) throws IOException {
214         return generateDefaultExplodedModule(moduleName, getDefaultClasses(moduleName), dependencies);
215     }
216 
generateDefaultExplodedModule(String moduleName, List<String> classNames, String... dependencies)217     public Result generateDefaultExplodedModule(String moduleName, List<String> classNames,
218             String... dependencies) throws IOException {
219         generateModuleCompiledClasses(explodedmodssrc, explodedmodsclasses,
220                 moduleName, classNames, dependencies);
221 
222         Path dir = explodedmods.resolve("classes").resolve(moduleName);
223         return new Result(0, "", dir);
224     }
225 
generateGarbage(Path compiled)226     private void generateGarbage(Path compiled) throws IOException {
227         Path metaInf = compiled.resolve("META-INF").resolve("services");
228         Files.createDirectories(metaInf);
229         Path provider = metaInf.resolve("MyProvider");
230         Files.createFile(provider);
231         Files.createFile(compiled.resolve("toto.jcov"));
232     }
233 
createNewFile(Path root, String pathName, String extension)234     public static Path createNewFile(Path root, String pathName, String extension) {
235         Path out = root.resolve(pathName + extension);
236         int i = 1;
237         while (Files.exists(out)) {
238             out = root.resolve(pathName + "-" + (++i) + extension);
239         }
240         return out;
241     }
242 
generateDefaultImage(String module)243     public Result generateDefaultImage(String module) {
244         return generateDefaultImage(new String[0], module);
245     }
246 
generateDefaultImage(String[] options, String module)247     public Result generateDefaultImage(String[] options, String module) {
248         Path output = createNewFile(images, module, ".image");
249         JLinkTask jLinkTask = JImageGenerator.getJLinkTask()
250                 .modulePath(defaultModulePath())
251                 .output(output)
252                 .addMods(module)
253                 .limitMods(module);
254         for (String option : options) {
255             jLinkTask.option(option);
256         }
257         return jLinkTask.call();
258     }
259 
postProcessImage(Path root, String[] options)260     public Result postProcessImage(Path root, String[] options) {
261         JLinkTask jLinkTask = JImageGenerator.getJLinkTask()
262                 .existing(root);
263         for (String option : options) {
264             jLinkTask.option(option);
265         }
266         return jLinkTask.callPostProcess();
267     }
268 
getDefaultClasses(String module)269     private List<String> getDefaultClasses(String module) {
270         return Arrays.asList(module + ".Main", module + ".com.foo.bar.X");
271     }
272 
putAppClasses(String module, List<String> classes)273     private void putAppClasses(String module, List<String> classes) {
274         List<String> appClasses = toLocation(module, classes).stream().collect(Collectors.toList());
275         appClasses.add(toLocation(module, "module-info"));
276         moduleClassDependencies.put(module, appClasses);
277     }
278 
toLocation(String module, String className)279     private static String toLocation(String module, String className) {
280         return "/" + module + "/" + className.replaceAll("\\.", "/") + ".class";
281     }
282 
toLocation(String module, List<String> classNames)283     public static List<String> toLocation(String module, List<String> classNames) {
284         return classNames.stream()
285                 .map(clazz -> toLocation(module, clazz))
286                 .collect(Collectors.toList());
287     }
288 
checkImage(Path imageDir, String module, String[] paths, String[] files)289     public void checkImage(Path imageDir, String module, String[] paths, String[] files) throws IOException {
290         checkImage(imageDir, module, paths, files, null);
291     }
292 
checkImage(Path imageDir, String module, String[] paths, String[] files, String[] expectedFiles)293     public void checkImage(Path imageDir, String module, String[] paths, String[] files, String[] expectedFiles) throws IOException {
294         List<String> unexpectedPaths = new ArrayList<>();
295         if (paths != null) {
296             Collections.addAll(unexpectedPaths, paths);
297         }
298         List<String> unexpectedFiles = new ArrayList<>();
299         if (files != null) {
300             Collections.addAll(unexpectedFiles, files);
301         }
302 
303         JImageValidator validator = new JImageValidator(module, gatherExpectedLocations(module),
304                 imageDir.toFile(),
305                 unexpectedPaths,
306                 unexpectedFiles,
307                 expectedFiles);
308         System.out.println("*** Validate Image " + module);
309         validator.validate();
310         long moduleExecutionTime = validator.getModuleLauncherExecutionTime();
311         if (moduleExecutionTime != 0) {
312             System.out.println("Module launcher execution time " + moduleExecutionTime);
313         }
314         System.out.println("Java launcher execution time "
315                 + validator.getJavaLauncherExecutionTime());
316         System.out.println("***");
317     }
318 
gatherExpectedLocations(String module)319     private List<String> gatherExpectedLocations(String module) throws IOException {
320         List<String> expectedLocations = new ArrayList<>();
321         expectedLocations.addAll(bootClasses);
322         List<String> modules = moduleDependencies.get(module);
323         for (String dep : modules) {
324             Path path = fs.getPath("/modules/" + dep);
325             if (Files.exists(path)) {
326                 List<String> locations = Files.find(path, Integer.MAX_VALUE,
327                         (p, attrs) -> Files.isRegularFile(p) && p.toString().endsWith(".class")
328                                 && !p.toString().endsWith("module-info.class"))
329                         .map(p -> p.toString().substring("/modules".length()))
330                         .collect(Collectors.toList());
331                 expectedLocations.addAll(locations);
332             }
333         }
334 
335         List<String> appClasses = moduleClassDependencies.get(module);
336         if (appClasses != null) {
337             expectedLocations.addAll(appClasses);
338         }
339         return expectedLocations;
340     }
341 
getDebugSymbolsExtension()342     public static String getDebugSymbolsExtension() {
343         return ".diz";
344     }
345 
createNewImageDir(String moduleName)346     public Path createNewImageDir(String moduleName) {
347         return createNewFile(getImageDir(), moduleName, ".image");
348     }
349 
createNewExtractedDir(String name)350     public Path createNewExtractedDir(String name) {
351         return createNewFile(getExtractedDir(), name, ".extracted");
352     }
353 
createNewRecreatedDir(String name)354     public Path createNewRecreatedDir(String name) {
355         return createNewFile(getRecreatedDir(), name, ".jimage");
356     }
357 
createNewJmodFile(String moduleName)358     public Path createNewJmodFile(String moduleName) {
359         return createNewFile(getJmodDir(), moduleName, ".jmod");
360     }
361 
createNewJarFile(String moduleName)362     public Path createNewJarFile(String moduleName) {
363         return createNewFile(getJarDir(), moduleName, ".jar");
364     }
365 
getJmodSrcDir()366     public Path getJmodSrcDir() {
367         return jmodssrc;
368     }
369 
getJarSrcDir()370     public Path getJarSrcDir() {
371         return jarssrc;
372     }
373 
getJmodClassesDir()374     public Path getJmodClassesDir() {
375         return jmodsclasses;
376     }
377 
getJarClassesDir()378     public Path getJarClassesDir() {
379         return jarsclasses;
380     }
381 
getJmodDir()382     public Path getJmodDir() {
383         return jmods;
384     }
385 
getExplodedModsDir()386     public Path getExplodedModsDir() {
387         return explodedmods;
388     }
389 
getJarDir()390     public Path getJarDir() {
391         return jars;
392     }
393 
getImageDir()394     public Path getImageDir() {
395         return images;
396     }
397 
getStdJmodsDir()398     public Path getStdJmodsDir() {
399         return stdjmods;
400     }
401 
getExtractedDir()402     public Path getExtractedDir() {
403         return extracted;
404     }
405 
getRecreatedDir()406     public Path getRecreatedDir() {
407         return recreated;
408     }
409 }
410