1 /*
   2  * Copyright (c) 2018, 2020, 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 
  24 package compiler.graalunit.common;
  25 
  26 import java.io.File;
  27 import java.io.IOException;
  28 import java.util.*;
  29 import java.nio.file.*;
  30 import java.util.stream.Collectors;
  31 
  32 import jdk.test.lib.process.OutputAnalyzer;
  33 import jdk.test.lib.process.ProcessTools;
  34 
  35 /*
  36  * This is helper class used to run Graal unit tests.
  37  * It accepts two arguments:
  38  *  -prefix TEST_PREFIX_TO_DEFINE_SET_OF_TESTS_TO_RUN (Ex: -prefix org.graalvm.compiler.api.test)
  39  *  -exclude EXCLUDED_TESTS_FILE_NAME
  40  */
  41 public class GraalUnitTestLauncher {
  42 
  43     static final String MXTOOL_JARFILE = "com.oracle.mxtool.junit.jar";
  44     static final String GRAAL_UNITTESTS_JARFILE = "jdk.vm.compiler.tests.jar";
  45 
  46     static final String[] GRAAL_EXTRA_JARS = {"junit-4.12.jar", "asm-5.0.4.jar", "asm-tree-5.0.4.jar",
  47                                               "hamcrest-core-1.3.jar", "java-allocation-instrumenter.jar"};
  48 
  49     static final String GENERATED_TESTCLASSES_FILENAME = "list.testclasses";
  50 
  51     // Library dir used to find Graal specific jar files.
  52     static String libsDir;
  53     static {
  54         libsDir = System.getProperty("graalunit.libs");
  55         if (libsDir == null || libsDir.isEmpty()) {
  56             libsDir = System.getenv("TEST_IMAGE_GRAAL_DIR");
  57         }
  58 
  59         if (libsDir == null || libsDir.isEmpty())
  60             throw new RuntimeException("ERROR: Graal library directory is not specified, use -Dgraalunit.libs or TEST_IMAGE_GRAAL_DIR environment variable.");
  61 
  62         System.out.println("INFO: graal libs dir is '" + libsDir + "'");
  63     }
  64 
  65     /*
  66      * Generates --add-exports <module>/<package>=<target-module> flags and
  67      * returns them as array list.
  68      *
  69      * @param moduleName
  70      *        Name of the module to update export data
  71      *
  72      * @param targetModule
  73      *        Name of the module to whom to export
  74      */
  75     static ArrayList<String> getModuleExports(String moduleName, String targetModule) {
  76         ArrayList<String> exports = new ArrayList<String>();
  77 
  78         Optional<Module> mod = ModuleLayer.boot().findModule(moduleName);
  79         Set<String> packages;
  80         if (mod.isPresent()) {
  81             packages = mod.get().getPackages();
  82 
  83             for (String pName : packages) {
  84                 exports.add("--add-exports");
  85                 exports.add(moduleName + "/" + pName + "=" + targetModule);
  86             }
  87         }
  88 
  89         return exports;
  90     }
  91 
  92     /*
  93      * Return list of tests which match specified prefix
  94      *
  95      * @param testPrefix
  96      *        String prefix to select tests
  97      */
  98     static ArrayList<String> getListOfTestsByPrefix(String testPrefix, Set<String> excludeTests) throws Exception {
  99         ArrayList<String> classes = new ArrayList<String>();
 100 
 101         final String testAnnotationName = "@Test";
 102 
 103         // return empty list in case no selection prefix specified
 104         if (testPrefix == null || testPrefix.isEmpty())
 105             return classes;
 106 
 107         // replace "." by "\." in test pattern
 108         testPrefix = testPrefix.replaceAll("\\.", "\\\\.") + ".*";
 109         System.out.println("INFO: use following pattern to find tests: " + testPrefix);
 110 
 111         String graalUnitTestFilePath = String.join(File.separator, libsDir, GRAAL_UNITTESTS_JARFILE);
 112         String classPath = String.join(File.pathSeparator, System.getProperty("java.class.path"),
 113                 String.join(File.separator, libsDir, MXTOOL_JARFILE));
 114 
 115         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(false,
 116                 "-cp",  classPath,
 117                 "com.oracle.mxtool.junit.FindClassesByAnnotatedMethods", graalUnitTestFilePath, testAnnotationName);
 118 
 119         System.out.println("INFO: run command " + String.join(" ", pb.command()));
 120 
 121         OutputAnalyzer out = new OutputAnalyzer(pb.start());
 122         int exitCode = out.getExitValue();
 123         if (exitCode != 0) {
 124             throw new Exception("Failed to find tests, VM crashed with exit code " + exitCode);
 125         }
 126 
 127         String outStr = out.getOutput().trim();
 128         System.out.println("INFO: command output: [" + outStr + "]");
 129 
 130         String[] lines = outStr.split(" ");
 131         Arrays.sort(lines);
 132 
 133         if (lines.length > 1) { // first line contains jar file name
 134             for (int i = 1; i < lines.length; i++) {
 135                 String className = lines[i];
 136 
 137                 if (testPrefix.equals(".*") || className.matches(testPrefix)) {
 138                     // add the test only in case it is not in exclude list
 139                     if (excludeTests!= null && excludeTests.contains(className)) {
 140                         System.out.println("INFO: excluded test: " + className);
 141                     } else {
 142                         classes.add(className);
 143                     }
 144                 }
 145             }
 146         }
 147 
 148         return classes;
 149     }
 150 
 151     /*
 152      * Return set of excluded tests
 153      *
 154      * @param excludeFileName
 155      *        Name of the file to read excluded test list
 156      */
 157     static Set loadExcludeList(String excludeFileName) {
 158         Set<String> excludeTests;
 159 
 160         Path excludeFilePath = Paths.get(excludeFileName);
 161         try {
 162             excludeTests = Files.readAllLines(excludeFilePath).stream()
 163                     .filter(l -> !l.trim().isEmpty())
 164                     .filter(l -> !l.trim().startsWith("#"))
 165                     .map(l -> l.split(" ")[0])
 166                     .collect(Collectors.toSet());
 167 
 168         } catch (IOException ioe) {
 169             throw new Error("TESTBUG: failed to read " + excludeFilePath);
 170         }
 171 
 172         return excludeTests;
 173     }
 174 
 175     static String getUsageString() {
 176         return "Usage: " + GraalUnitTestLauncher.class.getName() + " " +
 177                 "-prefix (org.graalvm.compiler.api.test) " +
 178                 "-exclude <ExcludedTestsFileName>" + System.lineSeparator();
 179     }
 180 
 181     public static void main(String... args) throws Exception {
 182 
 183         String testPrefix = null;
 184         String excludeFileName = null;
 185 
 186         int i=0;
 187         String arg, val;
 188         while (i+1 < args.length) {
 189             arg = args[i++];
 190             val = args[i++];
 191 
 192             switch (arg) {
 193                 case "-prefix":
 194                     testPrefix = val;
 195                     break;
 196 
 197                 case "-exclude":
 198                     excludeFileName = val;
 199                     break;
 200 
 201                 default:
 202                     System.out.println("WARN: illegal option " + arg);
 203                     break;
 204             }
 205         }
 206 
 207         if (testPrefix == null)
 208             throw new Error("TESTBUG: no tests to run specified." + System.lineSeparator() + getUsageString());
 209 
 210 
 211         Set<String> excludeTests = null;
 212         if (excludeFileName != null) {
 213             excludeTests = loadExcludeList(excludeFileName);
 214         }
 215 
 216         // Find list of tests which match provided predicate and write into GENERATED_TESTCLASSES_FILENAME file
 217         ArrayList<String> tests = getListOfTestsByPrefix(testPrefix, excludeTests);
 218         if (tests.size() > 0) {
 219             Files.write(Paths.get(GENERATED_TESTCLASSES_FILENAME), String.join(System.lineSeparator(), tests).getBytes());
 220         } else {
 221             throw new Error("TESTBUG: no tests found for prefix " + testPrefix);
 222         }
 223 
 224         ArrayList<String> javaFlags = new ArrayList<String>();
 225 
 226         // add modules and exports
 227         javaFlags.add("--add-modules");
 228         javaFlags.add("jdk.internal.vm.compiler,jdk.internal.vm.ci");
 229         javaFlags.add("--add-exports");
 230         javaFlags.add("java.base/jdk.internal.module=ALL-UNNAMED");
 231         javaFlags.addAll(getModuleExports("jdk.internal.vm.compiler", "ALL-UNNAMED"));
 232         javaFlags.addAll(getModuleExports("jdk.internal.vm.ci", "ALL-UNNAMED,jdk.internal.vm.compiler"));
 233 
 234         // add opens, see JDK-8236211
 235         javaFlags.add("--add-opens");
 236         javaFlags.add("jdk.internal.vm.compiler/org.graalvm.graphio=ALL-UNNAMED");
 237 
 238         // add VM flags
 239         javaFlags.add("-XX:+UnlockExperimentalVMOptions");
 240         javaFlags.add("-XX:+EnableJVMCI");
 241         javaFlags.add("-Djava.awt.headless=true");
 242         javaFlags.add("-esa");
 243         javaFlags.add("-ea");
 244 
 245 
 246         // generate class path
 247         ArrayList<String> graalJars = new ArrayList<String>(Arrays.asList(GRAAL_EXTRA_JARS));
 248         graalJars.add(MXTOOL_JARFILE);
 249         graalJars.add(GRAAL_UNITTESTS_JARFILE);
 250 
 251         String graalJarsCP = graalJars.stream()
 252                                       .map(s -> String.join(File.separator, libsDir, s))
 253                                       .collect(Collectors.joining(File.pathSeparator));
 254 
 255         javaFlags.add("-cp");
 256         javaFlags.add(String.join(File.pathSeparator, System.getProperty("java.class.path"), graalJarsCP));
 257 
 258         //
 259         javaFlags.add("com.oracle.mxtool.junit.MxJUnitWrapper");
 260         javaFlags.add("-JUnitVerbose");
 261         javaFlags.add("-JUnitEagerStackTrace");
 262         javaFlags.add("-JUnitEnableTiming");
 263 
 264         javaFlags.add("@"+GENERATED_TESTCLASSES_FILENAME);
 265 
 266         ProcessBuilder javaPB = ProcessTools.createJavaProcessBuilder(true,
 267                 javaFlags.toArray(new String[javaFlags.size()]));
 268         System.out.println("INFO: run command: " + String.join(" ", javaPB.command()));
 269 
 270         OutputAnalyzer outputAnalyzer = new OutputAnalyzer(javaPB.start());
 271         System.out.println("INFO: execution result: " + outputAnalyzer.getOutput());
 272         outputAnalyzer.shouldHaveExitValue(0);
 273     }
 274 }