CDSTestUtils.java revision 2740:91ac8096f365
1/* 2 * Copyright (c) 2017, 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 */ 23package jdk.test.lib.cds; 24 25import java.io.IOException; 26import java.io.File; 27import java.io.FileOutputStream; 28import java.io.PrintStream; 29import java.util.ArrayList; 30import jdk.test.lib.Utils; 31import jdk.test.lib.process.OutputAnalyzer; 32import jdk.test.lib.process.ProcessTools; 33 34 35// This class contains common test utilities for testing CDS 36public class CDSTestUtils { 37 // Specify this property to copy sdandard output of the child test process to 38 // the parent/main stdout of the test. 39 // By default such output is logged into a file, and is copied into the main stdout. 40 public static final boolean CopyChildStdoutToMainStdout = 41 Boolean.valueOf(System.getProperty("test.cds.copy.child.stdout", "true")); 42 43 // This property is passed to child test processes 44 public static final String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0"); 45 46 public static final String UnableToMapMsg = 47 "Unable to map shared archive: test did not complete; assumed PASS"; 48 49 // Create bootstrap CDS archive, 50 // use extra JVM command line args as a prefix. 51 // For CDS tests specifying prefix makes more sense than specifying suffix, since 52 // normally there are no classes or arguments to classes, just "-version" 53 // To specify suffix explicitly use CDSOptions.addSuffix() 54 public static OutputAnalyzer createArchive(String... cliPrefix) 55 throws Exception { 56 return createArchive((new CDSOptions()).addPrefix(cliPrefix)); 57 } 58 59 // Create bootstrap CDS archive 60 public static OutputAnalyzer createArchive(CDSOptions opts) 61 throws Exception { 62 63 ArrayList<String> cmd = new ArrayList<String>(); 64 65 for (String p : opts.prefix) cmd.add(p); 66 67 cmd.add("-Xshare:dump"); 68 cmd.add("-XX:+PrintSharedSpaces"); 69 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 70 if (opts.archiveName == null) 71 opts.archiveName = getDefaultArchiveName(); 72 cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName); 73 74 for (String s : opts.suffix) cmd.add(s); 75 76 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 77 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 78 return executeAndLog(pb, "dump"); 79 } 80 81 82 // check result of 'dump-the-archive' operation, that is "-Xshare:dump" 83 public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) 84 throws Exception { 85 86 output.shouldContain("Loading classes to share"); 87 output.shouldHaveExitValue(0); 88 89 for (String match : extraMatches) { 90 output.shouldContain(match); 91 } 92 93 return output; 94 } 95 96 97 // A commonly used convenience methods to create an archive and check the results 98 // Creates an archive and checks for errors 99 public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts) 100 throws Exception { 101 return checkDump(createArchive(opts)); 102 } 103 104 105 public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix) 106 throws Exception { 107 return checkDump(createArchive(cliPrefix)); 108 } 109 110 111 // This method should be used to check the output of child VM for common exceptions. 112 // Most of CDS tests deal with child VM processes for creating and using the archive. 113 // However exceptions that occur in the child process do not automatically propagate 114 // to the parent process. This mechanism aims to improve the propagation 115 // of exceptions and common errors. 116 // Exception e argument - an exception to be re-thrown if none of the common 117 // exceptions match. Pass null if you wish not to re-throw any exception. 118 public static void checkCommonExecExceptions(OutputAnalyzer output, Exception e) 119 throws Exception { 120 if (output.getStdout().contains("http://bugreport.java.com/bugreport/crash.jsp")) { 121 throw new RuntimeException("Hotspot crashed"); 122 } 123 if (output.getStdout().contains("TEST FAILED")) { 124 throw new RuntimeException("Test Failed"); 125 } 126 if (output.getOutput().contains("shared class paths mismatch")) { 127 throw new RuntimeException("shared class paths mismatch"); 128 } 129 if (output.getOutput().contains("Unable to unmap shared space")) { 130 throw new RuntimeException("Unable to unmap shared space"); 131 } 132 133 // Special case -- sometimes Xshare:on fails because it failed to map 134 // at given address. This behavior is platform-specific, machine config-specific 135 // and can be random (see ASLR). 136 if (isUnableToMap(output)) { 137 System.out.println(UnableToMapMsg); 138 return; 139 } 140 141 if (e != null) 142 throw e; 143 } 144 145 146 // Check the output for indication that mapping of the archive failed. 147 // Performance note: this check seems to be rather costly - searching the entire 148 // output stream of a child process for multiple strings. However, it is necessary 149 // to detect this condition, a failure to map an archive, since this is not a real 150 // failure of the test or VM operation, and results in a test being "skipped". 151 // Suggestions to improve: 152 // 1. VM can designate a special exit code for such condition. 153 // 2. VM can print a single distinct string indicating failure to map an archive, 154 // instead of utilizing multiple messages. 155 // These are suggestions to improve testibility of the VM. However, implementing them 156 // could also improve usability in the field. 157 public static boolean isUnableToMap(OutputAnalyzer output) { 158 String outStr = output.getOutput(); 159 if ((output.getExitValue() == 1) && ( 160 outStr.contains("Unable to reserve shared space at required address") || 161 outStr.contains("Unable to map ReadOnly shared space at required address") || 162 outStr.contains("Unable to map ReadWrite shared space at required address") || 163 outStr.contains("Unable to map MiscData shared space at required address") || 164 outStr.contains("Unable to map MiscCode shared space at required address") || 165 outStr.contains("Unable to map shared string space at required address") || 166 outStr.contains("Could not allocate metaspace at a compatible address") || 167 outStr.contains("Unable to allocate shared string space: range is not within java heap") )) 168 { 169 return true; 170 } 171 172 return false; 173 } 174 175 176 // Execute JVM with CDS archive, specify command line args suffix 177 public static OutputAnalyzer runWithArchive(String... cliPrefix) 178 throws Exception { 179 180 return runWithArchive( (new CDSOptions()) 181 .setArchiveName(getDefaultArchiveName()) 182 .addPrefix(cliPrefix) ); 183 } 184 185 186 // Execute JVM with CDS archive, specify CDSOptions 187 public static OutputAnalyzer runWithArchive(CDSOptions opts) 188 throws Exception { 189 190 ArrayList<String> cmd = new ArrayList<String>(); 191 192 for (String p : opts.prefix) cmd.add(p); 193 194 cmd.add("-Xshare:" + opts.xShareMode); 195 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 196 cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor); 197 198 if (opts.archiveName == null) 199 opts.archiveName = getDefaultArchiveName(); 200 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 201 202 if (opts.useVersion) 203 cmd.add("-version"); 204 205 for (String s : opts.suffix) cmd.add(s); 206 207 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 208 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 209 return executeAndLog(pb, "exec"); 210 } 211 212 213 // A commonly used convenience methods to create an archive and check the results 214 // Creates an archive and checks for errors 215 public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception { 216 return checkExec(runWithArchive(opts)); 217 } 218 219 220 public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception { 221 return checkExec(runWithArchive(cliPrefix)); 222 } 223 224 225 public static OutputAnalyzer checkExec(OutputAnalyzer output, 226 String... extraMatches) throws Exception { 227 CDSOptions opts = new CDSOptions(); 228 return checkExec(output, opts, extraMatches); 229 } 230 231 232 // check result of 'exec' operation, that is when JVM is run using the archive 233 public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts, 234 String... extraMatches) throws Exception { 235 try { 236 if ("on".equals(opts.xShareMode)) { 237 output.shouldContain("sharing"); 238 } 239 output.shouldHaveExitValue(0); 240 } catch (RuntimeException e) { 241 checkCommonExecExceptions(output, e); 242 return output; 243 } 244 245 checkExtraMatches(output, extraMatches); 246 return output; 247 } 248 249 250 public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output, 251 int expectedExitValue, 252 String... extraMatches) throws Exception { 253 if (isUnableToMap(output)) { 254 System.out.println(UnableToMapMsg); 255 return output; 256 } 257 258 output.shouldHaveExitValue(expectedExitValue); 259 checkExtraMatches(output, extraMatches); 260 return output; 261 } 262 263 public static OutputAnalyzer checkExtraMatches(OutputAnalyzer output, 264 String... extraMatches) throws Exception { 265 for (String match : extraMatches) { 266 output.shouldContain(match); 267 } 268 return output; 269 } 270 271 272 // get the file object for the test artifact 273 public static File getTestArtifact(String name, boolean checkExistence) { 274 File dir = new File(System.getProperty("test.classes", ".")); 275 File file = new File(dir, name); 276 277 if (checkExistence && !file.exists()) { 278 throw new RuntimeException("Cannot find " + file.getPath()); 279 } 280 281 return file; 282 } 283 284 285 // create file containing the specified class list 286 public static File makeClassList(String classes[]) 287 throws Exception { 288 return makeClassList(getTestName() + "-", classes); 289 } 290 291 // create file containing the specified class list 292 public static File makeClassList(String testCaseName, String classes[]) 293 throws Exception { 294 295 File classList = getTestArtifact(testCaseName + "test.classlist", false); 296 FileOutputStream fos = new FileOutputStream(classList); 297 PrintStream ps = new PrintStream(fos); 298 299 addToClassList(ps, classes); 300 301 ps.close(); 302 fos.close(); 303 304 return classList; 305 } 306 307 308 public static void addToClassList(PrintStream ps, String classes[]) 309 throws IOException 310 { 311 if (classes != null) { 312 for (String s : classes) { 313 ps.println(s); 314 } 315 } 316 } 317 318 319 // Optimization for getting a test name. 320 // Test name does not change during execution of the test, 321 // but getTestName() uses stack walking hence it is expensive. 322 // Therefore cache it and reuse it. 323 private static String testName; 324 public static String getTestName() { 325 if (testName == null) { 326 testName = Utils.getTestName(); 327 } 328 return testName; 329 } 330 331 332 public static String getDefaultArchiveName() { 333 return getTestName() + ".jsa"; 334 } 335 336 337 // ===================== FILE ACCESS convenience methods 338 public static File getOutputFile(String name) { 339 File dir = new File(System.getProperty("test.classes", ".")); 340 return new File(dir, getTestName() + "-" + name); 341 } 342 343 344 public static File getOutputSourceFile(String name) { 345 File dir = new File(System.getProperty("test.classes", ".")); 346 return new File(dir, name); 347 } 348 349 350 public static File getSourceFile(String name) { 351 File dir = new File(System.getProperty("test.src", ".")); 352 return new File(dir, name); 353 } 354 355 356 // ============================= Logging 357 public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception { 358 long started = System.currentTimeMillis(); 359 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 360 361 writeFile(getOutputFile(logName + ".stdout"), output.getStdout()); 362 writeFile(getOutputFile(logName + ".stderr"), output.getStderr()); 363 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 364 System.out.println("[STDERR]\n" + output.getStderr()); 365 366 if (CopyChildStdoutToMainStdout) 367 System.out.println("[STDOUT]\n" + output.getStdout()); 368 369 return output; 370 } 371 372 373 private static void writeFile(File file, String content) throws Exception { 374 FileOutputStream fos = new FileOutputStream(file); 375 PrintStream ps = new PrintStream(fos); 376 ps.print(content); 377 ps.close(); 378 fos.close(); 379 } 380} 381