1 /*
   2  * Copyright (c) 2015, 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 
  68     public static Helper newHelper() throws IOException {
  69         Path jdkHome = Paths.get(System.getProperty("test.jdk"));
  70         // JPRT not yet ready for jmods
  71         if (!Files.exists(jdkHome.resolve("jmods"))) {
  72             System.err.println("Test not run, NO jmods directory");
  73             return null;
  74         }
  75         return new Helper(jdkHome);
  76     }
  77 
  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 
 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 
 138     public String defaultModulePath() {
 139         return defaultModulePath(true);
 140     }
 141 
 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 
 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 
 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 
 177     public Result generateDefaultJModule(String moduleName, String... dependencies) throws IOException {
 178         return generateDefaultJModule(moduleName, getDefaultClasses(moduleName), dependencies);
 179     }
 180 
 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 
 199     public Result generateDefaultJarModule(String moduleName, String... dependencies) throws IOException {
 200         return generateDefaultJarModule(moduleName, getDefaultClasses(moduleName), dependencies);
 201     }
 202 
 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 
 213     public Result generateDefaultExplodedModule(String moduleName, String... dependencies) throws IOException {
 214         return generateDefaultExplodedModule(moduleName, getDefaultClasses(moduleName), dependencies);
 215     }
 216 
 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 
 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 
 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 
 243     public Result generateDefaultImage(String module) {
 244         return generateDefaultImage(new String[0], module);
 245     }
 246 
 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 
 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 
 269     private List<String> getDefaultClasses(String module) {
 270         return Arrays.asList(module + ".Main", module + ".com.foo.bar.X");
 271     }
 272 
 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 
 279     private static String toLocation(String module, String className) {
 280         return "/" + module + "/" + className.replaceAll("\\.", "/") + ".class";
 281     }
 282 
 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 
 289     public void checkImage(Path imageDir, String module, String[] paths, String[] files) throws IOException {
 290         checkImage(imageDir, module, paths, files, null);
 291     }
 292 
 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 
 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 
 342     public static String getDebugSymbolsExtension() {
 343         return ".diz";
 344     }
 345 
 346     public Path createNewImageDir(String moduleName) {
 347         return createNewFile(getImageDir(), moduleName, ".image");
 348     }
 349 
 350     public Path createNewExtractedDir(String name) {
 351         return createNewFile(getExtractedDir(), name, ".extracted");
 352     }
 353 
 354     public Path createNewRecreatedDir(String name) {
 355         return createNewFile(getRecreatedDir(), name, ".jimage");
 356     }
 357 
 358     public Path createNewJmodFile(String moduleName) {
 359         return createNewFile(getJmodDir(), moduleName, ".jmod");
 360     }
 361 
 362     public Path createNewJarFile(String moduleName) {
 363         return createNewFile(getJarDir(), moduleName, ".jar");
 364     }
 365 
 366     public Path getJmodSrcDir() {
 367         return jmodssrc;
 368     }
 369 
 370     public Path getJarSrcDir() {
 371         return jarssrc;
 372     }
 373 
 374     public Path getJmodClassesDir() {
 375         return jmodsclasses;
 376     }
 377 
 378     public Path getJarClassesDir() {
 379         return jarsclasses;
 380     }
 381 
 382     public Path getJmodDir() {
 383         return jmods;
 384     }
 385 
 386     public Path getExplodedModsDir() {
 387         return explodedmods;
 388     }
 389 
 390     public Path getJarDir() {
 391         return jars;
 392     }
 393 
 394     public Path getImageDir() {
 395         return images;
 396     }
 397 
 398     public Path getStdJmodsDir() {
 399         return stdjmods;
 400     }
 401 
 402     public Path getExtractedDir() {
 403         return extracted;
 404     }
 405 
 406     public Path getRecreatedDir() {
 407         return recreated;
 408     }
 409 }