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