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