1 /* 2 * Copyright (c) 2017, 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 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 public interface Checker { 40 public void check(OutputAnalyzer output) throws Exception; 41 } 42 43 /* 44 * INTRODUCTION 45 * 46 * When testing various CDS functionalities, we need to launch JVM processes 47 * using a "launch method" (such as TestCommon.run), and analyze the results of these 48 * processes. 49 * 50 * While typical jtreg tests would use OutputAnalyzer in such cases, due to the 51 * complexity of CDS failure modes, we have added the CDSTestUtils.Result class 52 * to make the analysis more convenient and less error prone. 53 * 54 * A Java process can end in one of the following 4 states: 55 * 56 * 1: Unexpected error - such as JVM crashing. In this case, the "launch method" 57 * will throw a RuntimeException. 58 * 2: Mapping Failure - this happens when the OS (intermittently) fails to map the 59 * CDS archive, normally caused by Address Space Layout Randomization. 60 * We usually treat this as "pass". 61 * 3: Normal Exit - the JVM process has finished without crashing, and the exit code is 0. 62 * 4: Abnormal Exit - the JVM process has finished without crashing, and the exit code is not 0. 63 * 64 * In most test cases, we need to check the JVM process's output in cases 3 and 4. However, we need 65 * to make sure that our test code is not confused by case 2. 66 * 67 * For example, a JVM process is expected to print the string "Hi" and exit with 0. With the old 68 * CDSTestUtils.runWithArchive API, the test may be written as this: 69 * 70 * OutputAnalyzer out = CDSTestUtils.runWithArchive(args); 71 * out.shouldContain("Hi"); 72 * 73 * However, if the JVM process fails with mapping failure, the string "Hi" will not be in the output, 74 * and your test case will fail intermittently. 75 * 76 * Instead, the test case should be written as 77 * 78 * CCDSTestUtils.run(args).assertNormalExit("Hi"); 79 * 80 * EXAMPLES/HOWTO 81 * 82 * 1. For simple substring matching: 83 * 84 * CCDSTestUtils.run(args).assertNormalExit("Hi"); 85 * CCDSTestUtils.run(args).assertNormalExit("a", "b", "x"); 86 * CCDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2"); 87 * 88 * 2. For more complex output matching: using Lambda expressions 89 * 90 * CCDSTestUtils.run(args) 91 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed"); 92 * CCDSTestUtils.run(args) 93 * .assertAbnormalExit(output -> { 94 * output.shouldNotContain("this should not be printed"); 95 * output.shouldHaveExitValue(123); 96 * }); 97 * 98 * 3. Chaining several checks: 99 * 100 * CCDSTestUtils.run(args) 101 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed") 102 * .assertNormalExit("should have this", "should have that"); 103 * 104 * 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally: 105 * 106 * CCDSTestUtils.run(args) 107 * .ifNormalExit("ths string is printed when exiting with 0") 108 * .ifAbNormalExit("ths string is printed when exiting with 1"); 109 * 110 * NOTE: you usually don't want to write your test case like this -- it should always 111 * exit with the same exit code. (But I kept this API because some existing test cases 112 * behave this way -- need to revisit). 113 */ 114 public static class Result { 115 private final OutputAnalyzer output; 116 private final CDSOptions options; 117 private final boolean hasMappingFailure; 118 private final boolean hasAbnormalExit; 119 private final boolean hasNormalExit; 120 private final String CDS_DISABLED = "warning: CDS is disabled when the"; 121 122 public Result(CDSOptions opts, OutputAnalyzer out) throws Exception { 123 options = opts; 124 output = out; 125 hasMappingFailure = CDSTestUtils.checkCommonExecExceptions(output); 126 hasAbnormalExit = (!hasMappingFailure) && (output.getExitValue() != 0); 127 hasNormalExit = (!hasMappingFailure) && (output.getExitValue() == 0); 128 129 if (hasNormalExit) { 130 if ("on".equals(options.xShareMode) && 131 output.getStderr().contains("java version") && 132 !output.getStderr().contains(CDS_DISABLED)) { 133 // "-showversion" is always passed in the command-line by the execXXX methods. 134 // During normal exit, we require that the VM to show that sharing was enabled. 135 output.shouldContain("sharing"); 136 } 137 } 138 } 139 140 public Result assertNormalExit(Checker checker) throws Exception { 141 if (!hasMappingFailure) { 142 checker.check(output); 143 output.shouldHaveExitValue(0); 144 } 145 return this; 146 } 147 148 public Result assertAbnormalExit(Checker checker) throws Exception { 149 if (!hasMappingFailure) { 150 checker.check(output); 151 output.shouldNotHaveExitValue(0); 152 } 153 return this; 154 } 155 156 // When {--limit-modules, --patch-module, and/or --upgrade-module-path} 157 // are specified, CDS is silently disabled for both -Xshare:auto and -Xshare:on. 158 public Result assertSilentlyDisabledCDS(Checker checker) throws Exception { 159 if (hasMappingFailure) { 160 throw new RuntimeException("Unexpected mapping failure"); 161 } 162 // this comes from a JVM warning message. 163 output.shouldContain(CDS_DISABLED); 164 165 checker.check(output); 166 return this; 167 } 168 169 public Result ifNormalExit(Checker checker) throws Exception { 170 if (hasNormalExit) { 171 checker.check(output); 172 } 173 return this; 174 } 175 176 public Result ifAbnormalExit(Checker checker) throws Exception { 177 if (hasAbnormalExit) { 178 checker.check(output); 179 } 180 return this; 181 } 182 183 public Result ifNoMappingFailure(Checker checker) throws Exception { 184 if (!hasMappingFailure) { 185 checker.check(output); 186 } 187 return this; 188 } 189 190 191 public Result assertNormalExit(String... matches) throws Exception { 192 if (!hasMappingFailure) { 193 checkMatches(output, matches); 194 output.shouldHaveExitValue(0); 195 } 196 return this; 197 } 198 199 public Result assertAbnormalExit(String... matches) throws Exception { 200 if (!hasMappingFailure) { 201 checkMatches(output, matches); 202 output.shouldNotHaveExitValue(0); 203 } 204 205 return this; 206 } 207 } 208 209 // Specify this property to copy sdandard output of the child test process to 210 // the parent/main stdout of the test. 211 // By default such output is logged into a file, and is copied into the main stdout. 212 public static final boolean CopyChildStdoutToMainStdout = 213 Boolean.valueOf(System.getProperty("test.cds.copy.child.stdout", "true")); 214 215 // This property is passed to child test processes 216 public static final String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0"); 217 218 public static final String UnableToMapMsg = 219 "Unable to map shared archive: test did not complete; assumed PASS"; 220 221 // Create bootstrap CDS archive, 222 // use extra JVM command line args as a prefix. 223 // For CDS tests specifying prefix makes more sense than specifying suffix, since 224 // normally there are no classes or arguments to classes, just "-version" 225 // To specify suffix explicitly use CDSOptions.addSuffix() 226 public static OutputAnalyzer createArchive(String... cliPrefix) 227 throws Exception { 228 return createArchive((new CDSOptions()).addPrefix(cliPrefix)); 229 } 230 231 // Create bootstrap CDS archive 232 public static OutputAnalyzer createArchive(CDSOptions opts) 233 throws Exception { 234 235 startNewArchiveName(); 236 237 ArrayList<String> cmd = new ArrayList<String>(); 238 239 for (String p : opts.prefix) cmd.add(p); 240 241 cmd.add("-Xshare:dump"); 242 cmd.add("-Xlog:cds,cds+hashtables"); 243 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 244 if (opts.archiveName == null) 245 opts.archiveName = getDefaultArchiveName(); 246 cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName); 247 248 for (String s : opts.suffix) cmd.add(s); 249 250 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 251 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 252 return executeAndLog(pb, "dump"); 253 } 254 255 256 // check result of 'dump-the-archive' operation, that is "-Xshare:dump" 257 public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) 258 throws Exception { 259 260 output.shouldContain("Loading classes to share"); 261 output.shouldHaveExitValue(0); 262 263 for (String match : extraMatches) { 264 output.shouldContain(match); 265 } 266 267 return output; 268 } 269 270 271 // A commonly used convenience methods to create an archive and check the results 272 // Creates an archive and checks for errors 273 public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts) 274 throws Exception { 275 return checkDump(createArchive(opts)); 276 } 277 278 279 public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix) 280 throws Exception { 281 return checkDump(createArchive(cliPrefix)); 282 } 283 284 285 // This method should be used to check the output of child VM for common exceptions. 286 // Most of CDS tests deal with child VM processes for creating and using the archive. 287 // However exceptions that occur in the child process do not automatically propagate 288 // to the parent process. This mechanism aims to improve the propagation 289 // of exceptions and common errors. 290 // Exception e argument - an exception to be re-thrown if none of the common 291 // exceptions match. Pass null if you wish not to re-throw any exception. 292 public static boolean checkCommonExecExceptions(OutputAnalyzer output, Exception e) 293 throws Exception { 294 if (output.getStdout().contains("http://bugreport.java.com/bugreport/crash.jsp")) { 295 throw new RuntimeException("Hotspot crashed"); 296 } 297 if (output.getStdout().contains("TEST FAILED")) { 298 throw new RuntimeException("Test Failed"); 299 } 300 if (output.getOutput().contains("shared class paths mismatch")) { 301 // throw new RuntimeException("shared class paths mismatch"); 302 } 303 if (output.getOutput().contains("Unable to unmap shared space")) { 304 throw new RuntimeException("Unable to unmap shared space"); 305 } 306 307 // Special case -- sometimes Xshare:on fails because it failed to map 308 // at given address. This behavior is platform-specific, machine config-specific 309 // and can be random (see ASLR). 310 if (isUnableToMap(output)) { 311 System.out.println(UnableToMapMsg); 312 return true; 313 } 314 315 if (e != null) { 316 throw e; 317 } 318 return false; 319 } 320 321 public static boolean checkCommonExecExceptions(OutputAnalyzer output) throws Exception { 322 return checkCommonExecExceptions(output, null); 323 } 324 325 326 // Check the output for indication that mapping of the archive failed. 327 // Performance note: this check seems to be rather costly - searching the entire 328 // output stream of a child process for multiple strings. However, it is necessary 329 // to detect this condition, a failure to map an archive, since this is not a real 330 // failure of the test or VM operation, and results in a test being "skipped". 331 // Suggestions to improve: 332 // 1. VM can designate a special exit code for such condition. 333 // 2. VM can print a single distinct string indicating failure to map an archive, 334 // instead of utilizing multiple messages. 335 // These are suggestions to improve testibility of the VM. However, implementing them 336 // could also improve usability in the field. 337 public static boolean isUnableToMap(OutputAnalyzer output) { 338 String outStr = output.getOutput(); 339 if ((output.getExitValue() == 1) && ( 340 outStr.contains("Unable to reserve shared space at required address") || 341 outStr.contains("Unable to map ReadOnly shared space at required address") || 342 outStr.contains("Unable to map ReadWrite shared space at required address") || 343 outStr.contains("Unable to map MiscData shared space at required address") || 344 outStr.contains("Unable to map MiscCode shared space at required address") || 345 outStr.contains("Unable to map OptionalData shared space at required address") || 346 outStr.contains("Could not allocate metaspace at a compatible address") || 347 outStr.contains("UseSharedSpaces: Unable to allocate region, range is not within java heap") )) 348 { 349 return true; 350 } 351 352 return false; 353 } 354 355 public static Result run(String... cliPrefix) throws Exception { 356 CDSOptions opts = new CDSOptions(); 357 opts.setArchiveName(getDefaultArchiveName()); 358 opts.addPrefix(cliPrefix); 359 return new Result(opts, runWithArchive(opts)); 360 } 361 362 // Execute JVM with CDS archive, specify command line args suffix 363 public static OutputAnalyzer runWithArchive(String... cliPrefix) 364 throws Exception { 365 366 return runWithArchive( (new CDSOptions()) 367 .setArchiveName(getDefaultArchiveName()) 368 .addPrefix(cliPrefix) ); 369 } 370 371 372 // Execute JVM with CDS archive, specify CDSOptions 373 public static OutputAnalyzer runWithArchive(CDSOptions opts) 374 throws Exception { 375 376 ArrayList<String> cmd = new ArrayList<String>(); 377 378 for (String p : opts.prefix) cmd.add(p); 379 380 cmd.add("-Xshare:" + opts.xShareMode); 381 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 382 cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor); 383 384 if (opts.archiveName == null) 385 opts.archiveName = getDefaultArchiveName(); 386 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 387 388 if (opts.useVersion) 389 cmd.add("-version"); 390 391 for (String s : opts.suffix) cmd.add(s); 392 393 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 394 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 395 return executeAndLog(pb, "exec"); 396 } 397 398 399 // A commonly used convenience methods to create an archive and check the results 400 // Creates an archive and checks for errors 401 public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception { 402 return checkExec(runWithArchive(opts)); 403 } 404 405 406 public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception { 407 return checkExec(runWithArchive(cliPrefix)); 408 } 409 410 411 public static OutputAnalyzer checkExec(OutputAnalyzer output, 412 String... extraMatches) throws Exception { 413 CDSOptions opts = new CDSOptions(); 414 return checkExec(output, opts, extraMatches); 415 } 416 417 418 // check result of 'exec' operation, that is when JVM is run using the archive 419 public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts, 420 String... extraMatches) throws Exception { 421 try { 422 if ("on".equals(opts.xShareMode)) { 423 output.shouldContain("sharing"); 424 } 425 output.shouldHaveExitValue(0); 426 } catch (RuntimeException e) { 427 checkCommonExecExceptions(output, e); 428 return output; 429 } 430 431 checkMatches(output, extraMatches); 432 return output; 433 } 434 435 436 public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output, 437 int expectedExitValue, 438 String... extraMatches) throws Exception { 439 if (isUnableToMap(output)) { 440 System.out.println(UnableToMapMsg); 441 return output; 442 } 443 444 output.shouldHaveExitValue(expectedExitValue); 445 checkMatches(output, extraMatches); 446 return output; 447 } 448 449 public static OutputAnalyzer checkMatches(OutputAnalyzer output, 450 String... matches) throws Exception { 451 for (String match : matches) { 452 output.shouldContain(match); 453 } 454 return output; 455 } 456 457 458 // get the file object for the test artifact 459 public static File getTestArtifact(String name, boolean checkExistence) { 460 File dir = new File(System.getProperty("test.classes", ".")); 461 File file = new File(dir, name); 462 463 if (checkExistence && !file.exists()) { 464 throw new RuntimeException("Cannot find " + file.getPath()); 465 } 466 467 return file; 468 } 469 470 471 // create file containing the specified class list 472 public static File makeClassList(String classes[]) 473 throws Exception { 474 return makeClassList(getTestName() + "-", classes); 475 } 476 477 // create file containing the specified class list 478 public static File makeClassList(String testCaseName, String classes[]) 479 throws Exception { 480 481 File classList = getTestArtifact(testCaseName + "test.classlist", false); 482 FileOutputStream fos = new FileOutputStream(classList); 483 PrintStream ps = new PrintStream(fos); 484 485 addToClassList(ps, classes); 486 487 ps.close(); 488 fos.close(); 489 490 return classList; 491 } 492 493 494 public static void addToClassList(PrintStream ps, String classes[]) 495 throws IOException 496 { 497 if (classes != null) { 498 for (String s : classes) { 499 ps.println(s); 500 } 501 } 502 } 503 504 505 // Optimization for getting a test name. 506 // Test name does not change during execution of the test, 507 // but getTestName() uses stack walking hence it is expensive. 508 // Therefore cache it and reuse it. 509 private static String testName; 510 public static String getTestName() { 511 if (testName == null) { 512 testName = Utils.getTestName(); 513 } 514 return testName; 515 } 516 517 private static final SimpleDateFormat timeStampFormat = 518 new SimpleDateFormat("HH'h'mm'm'ss's'SSS"); 519 520 private static String defaultArchiveName; 521 522 // Call this method to start new archive with new unique name 523 public static void startNewArchiveName() { 524 defaultArchiveName = getTestName() + 525 timeStampFormat.format(new Date()) + ".jsa"; 526 } 527 528 public static String getDefaultArchiveName() { 529 return defaultArchiveName; 530 } 531 532 533 // ===================== FILE ACCESS convenience methods 534 public static File getOutputFile(String name) { 535 File dir = new File(System.getProperty("test.classes", ".")); 536 return new File(dir, getTestName() + "-" + name); 537 } 538 539 540 public static File getOutputSourceFile(String name) { 541 File dir = new File(System.getProperty("test.classes", ".")); 542 return new File(dir, name); 543 } 544 545 546 public static File getSourceFile(String name) { 547 File dir = new File(System.getProperty("test.src", ".")); 548 return new File(dir, name); 549 } 550 551 552 // ============================= Logging 553 public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception { 554 long started = System.currentTimeMillis(); 555 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 556 557 writeFile(getOutputFile(logName + ".stdout"), output.getStdout()); 558 writeFile(getOutputFile(logName + ".stderr"), output.getStderr()); 559 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 560 System.out.println("[STDERR]\n" + output.getStderr()); 561 562 if (CopyChildStdoutToMainStdout) 563 System.out.println("[STDOUT]\n" + output.getStdout()); 564 565 return output; 566 } 567 568 569 private static void writeFile(File file, String content) throws Exception { 570 FileOutputStream fos = new FileOutputStream(file); 571 PrintStream ps = new PrintStream(fos); 572 ps.print(content); 573 ps.close(); 574 fos.close(); 575 } 576 }