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