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 stdjmods.toAbsolutePath().toString() + File.pathSeparator
 140                 + jmods.toAbsolutePath().toString() + File.pathSeparator
 141                 + jars.toAbsolutePath().toString() + File.pathSeparator
 142                 + explodedmodsclasses.toAbsolutePath().toString();
 143     }
 144 
 145     public Path generateModuleCompiledClasses(
 146             Path src, Path classes, String moduleName, String... dependencies) throws IOException {
 147         return generateModuleCompiledClasses(src, classes, moduleName, getDefaultClasses(moduleName), dependencies);
 148     }
 149 
 150     public Path generateModuleCompiledClasses(
 151             Path src, Path classes, String moduleName,
 152             List<String> classNames, String... dependencies) throws IOException {
 153         if (classNames == null) {
 154             classNames = getDefaultClasses(moduleName);
 155         }
 156         putAppClasses(moduleName, classNames);
 157         moduleDependencies.put(moduleName, Arrays.asList(dependencies));
 158         String modulePath = defaultModulePath();
 159         JImageGenerator.generateSourcesFromTemplate(src, moduleName, classNames.toArray(new String[classNames.size()]));
 160         List<String> packages = classNames.stream()
 161                 .map(JImageGenerator::getPackageName)
 162                 .distinct()
 163                 .collect(Collectors.toList());
 164         Path srcMod = src.resolve(moduleName);
 165         JImageGenerator.generateModuleInfo(srcMod, packages, dependencies);
 166         Path destination = classes.resolve(moduleName);
 167         if (!JImageGenerator.compile(srcMod, destination, "--module-path", modulePath, "-g")) {
 168             throw new AssertionError("Compilation failure");
 169         }
 170         return destination;
 171     }
 172 
 173     public Result generateDefaultJModule(String moduleName, String... dependencies) throws IOException {
 174         return generateDefaultJModule(moduleName, getDefaultClasses(moduleName), dependencies);
 175     }
 176 
 177     public Result generateDefaultJModule(String moduleName, List<String> classNames,
 178                                              String... dependencies) throws IOException {
 179         generateModuleCompiledClasses(jmodssrc, jmodsclasses, moduleName, classNames, dependencies);
 180         generateGarbage(jmodsclasses.resolve(moduleName));
 181 
 182         Path jmodFile = jmods.resolve(moduleName + ".jmod");
 183         JModTask task = JImageGenerator.getJModTask()
 184                 .jmod(jmodFile)
 185                 .addJmods(stdjmods)
 186                 .addJmods(jmods.toAbsolutePath())
 187                 .addJars(jars.toAbsolutePath())
 188                 .addClassPath(jmodsclasses.resolve(moduleName));
 189         if (!classNames.isEmpty()) {
 190             task.mainClass(classNames.get(0));
 191         }
 192         return task.create();
 193     }
 194 
 195     public Result generateDefaultJarModule(String moduleName, String... dependencies) throws IOException {
 196         return generateDefaultJarModule(moduleName, getDefaultClasses(moduleName), dependencies);
 197     }
 198 
 199     public Result generateDefaultJarModule(String moduleName, List<String> classNames,
 200                                          String... dependencies) throws IOException {
 201         generateModuleCompiledClasses(jarssrc, jarsclasses, moduleName, classNames, dependencies);
 202         generateGarbage(jarsclasses.resolve(moduleName));
 203 
 204         Path jarFile = jars.resolve(moduleName + ".jar");
 205         JImageGenerator.createJarFile(jarFile, jarsclasses.resolve(moduleName));
 206         return new Result(0, "", jarFile);
 207     }
 208 
 209     public Result generateDefaultExplodedModule(String moduleName, String... dependencies) throws IOException {
 210         return generateDefaultExplodedModule(moduleName, getDefaultClasses(moduleName), dependencies);
 211     }
 212 
 213     public Result generateDefaultExplodedModule(String moduleName, List<String> classNames,
 214             String... dependencies) throws IOException {
 215         generateModuleCompiledClasses(explodedmodssrc, explodedmodsclasses,
 216                 moduleName, classNames, dependencies);
 217 
 218         Path dir = explodedmods.resolve(moduleName);
 219         return new Result(0, "", dir);
 220     }
 221 
 222     private void generateGarbage(Path compiled) throws IOException {
 223         Path metaInf = compiled.resolve("META-INF").resolve("services");
 224         Files.createDirectories(metaInf);
 225         Path provider = metaInf.resolve("MyProvider");
 226         Files.createFile(provider);
 227         Files.createFile(compiled.resolve("toto.jcov"));
 228     }
 229 
 230     public static Path createNewFile(Path root, String pathName, String extension) {
 231         Path out = root.resolve(pathName + extension);
 232         int i = 1;
 233         while (Files.exists(out)) {
 234             out = root.resolve(pathName + "-" + (++i) + extension);
 235         }
 236         return out;
 237     }
 238 
 239     public Result generateDefaultImage(String module) {
 240         return generateDefaultImage(new String[0], module);
 241     }
 242 
 243     public Result generateDefaultImage(String[] options, String module) {
 244         Path output = createNewFile(images, module, ".image");
 245         JLinkTask jLinkTask = JImageGenerator.getJLinkTask()
 246                 .modulePath(defaultModulePath())
 247                 .output(output)
 248                 .addMods(module)
 249                 .limitMods(module);
 250         for (String option : options) {
 251             jLinkTask.option(option);
 252         }
 253         return jLinkTask.call();
 254     }
 255 
 256     public Result postProcessImage(Path root, String[] options) {
 257         JLinkTask jLinkTask = JImageGenerator.getJLinkTask()
 258                 .existing(root);
 259         for (String option : options) {
 260             jLinkTask.option(option);
 261         }
 262         return jLinkTask.callPostProcess();
 263     }
 264 
 265     private List<String> getDefaultClasses(String module) {
 266         return Arrays.asList(module + ".Main", module + ".com.foo.bar.X");
 267     }
 268 
 269     private void putAppClasses(String module, List<String> classes) {
 270         List<String> appClasses = toLocation(module, classes).stream().collect(Collectors.toList());
 271         appClasses.add(toLocation(module, "module-info"));
 272         moduleClassDependencies.put(module, appClasses);
 273     }
 274 
 275     private static String toLocation(String module, String className) {
 276         return "/" + module + "/" + className.replaceAll("\\.", "/") + ".class";
 277     }
 278 
 279     public static List<String> toLocation(String module, List<String> classNames) {
 280         return classNames.stream()
 281                 .map(clazz -> toLocation(module, clazz))
 282                 .collect(Collectors.toList());
 283     }
 284 
 285     public void checkImage(Path imageDir, String module, String[] paths, String[] files) throws IOException {
 286         checkImage(imageDir, module, paths, files, null);
 287     }
 288 
 289     public void checkImage(Path imageDir, String module, String[] paths, String[] files, String[] expectedFiles) throws IOException {
 290         List<String> unexpectedPaths = new ArrayList<>();
 291         if (paths != null) {
 292             Collections.addAll(unexpectedPaths, paths);
 293         }
 294         List<String> unexpectedFiles = new ArrayList<>();
 295         if (files != null) {
 296             Collections.addAll(unexpectedFiles, files);
 297         }
 298 
 299         JImageValidator validator = new JImageValidator(module, gatherExpectedLocations(module),
 300                 imageDir.toFile(),
 301                 unexpectedPaths,
 302                 unexpectedFiles,
 303                 expectedFiles);
 304         System.out.println("*** Validate Image " + module);
 305         validator.validate();
 306         long moduleExecutionTime = validator.getModuleLauncherExecutionTime();
 307         if (moduleExecutionTime != 0) {
 308             System.out.println("Module launcher execution time " + moduleExecutionTime);
 309         }
 310         System.out.println("Java launcher execution time "
 311                 + validator.getJavaLauncherExecutionTime());
 312         System.out.println("***");
 313     }
 314 
 315     private List<String> gatherExpectedLocations(String module) throws IOException {
 316         List<String> expectedLocations = new ArrayList<>();
 317         expectedLocations.addAll(bootClasses);
 318         List<String> modules = moduleDependencies.get(module);
 319         for (String dep : modules) {
 320             Path path = fs.getPath("/modules/" + dep);
 321             if (Files.exists(path)) {
 322                 List<String> locations = Files.find(path, Integer.MAX_VALUE,
 323                         (p, attrs) -> Files.isRegularFile(p) && p.toString().endsWith(".class")
 324                                 && !p.toString().endsWith("module-info.class"))
 325                         .map(p -> p.toString().substring("/modules".length()))
 326                         .collect(Collectors.toList());
 327                 expectedLocations.addAll(locations);
 328             }
 329         }
 330 
 331         List<String> appClasses = moduleClassDependencies.get(module);
 332         if (appClasses != null) {
 333             expectedLocations.addAll(appClasses);
 334         }
 335         return expectedLocations;
 336     }
 337 
 338     public static String getDebugSymbolsExtension() {
 339         return ".diz";
 340     }
 341 
 342     public Path createNewImageDir(String moduleName) {
 343         return createNewFile(getImageDir(), moduleName, ".image");
 344     }
 345 
 346     public Path createNewExtractedDir(String name) {
 347         return createNewFile(getExtractedDir(), name, ".extracted");
 348     }
 349 
 350     public Path createNewRecreatedDir(String name) {
 351         return createNewFile(getRecreatedDir(), name, ".jimage");
 352     }
 353 
 354     public Path createNewJmodFile(String moduleName) {
 355         return createNewFile(getJmodDir(), moduleName, ".jmod");
 356     }
 357 
 358     public Path createNewJarFile(String moduleName) {
 359         return createNewFile(getJarDir(), moduleName, ".jar");
 360     }
 361 
 362     public Path getJmodSrcDir() {
 363         return jmodssrc;
 364     }
 365 
 366     public Path getJarSrcDir() {
 367         return jarssrc;
 368     }
 369 
 370     public Path getJmodClassesDir() {
 371         return jmodsclasses;
 372     }
 373 
 374     public Path getJarClassesDir() {
 375         return jarsclasses;
 376     }
 377 
 378     public Path getJmodDir() {
 379         return jmods;
 380     }
 381 
 382     public Path getExplodedModsDir() {
 383         return explodedmods;
 384     }
 385 
 386     public Path getJarDir() {
 387         return jars;
 388     }
 389 
 390     public Path getImageDir() {
 391         return images;
 392     }
 393 
 394     public Path getStdJmodsDir() {
 395         return stdjmods;
 396     }
 397 
 398     public Path getExtractedDir() {
 399         return extracted;
 400     }
 401 
 402     public Path getRecreatedDir() {
 403         return recreated;
 404     }
 405 }