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 assertSilentlyDisabledCDS(int exitCode, String... matches) throws Exception { 170 return assertSilentlyDisabledCDS((out) -> { 171 out.shouldHaveExitValue(exitCode); 172 checkMatches(out, matches); 173 }); 174 } 175 176 public Result ifNormalExit(Checker checker) throws Exception { 177 if (hasNormalExit) { 178 checker.check(output); 179 } 180 return this; 181 } 182 183 public Result ifAbnormalExit(Checker checker) throws Exception { 184 if (hasAbnormalExit) { 185 checker.check(output); 186 } 187 return this; 188 } 189 190 public Result ifNoMappingFailure(Checker checker) throws Exception { 191 if (!hasMappingFailure) { 192 checker.check(output); 193 } 194 return this; 195 } 196 197 198 public Result assertNormalExit(String... matches) throws Exception { 199 if (!hasMappingFailure) { 200 checkMatches(output, matches); 201 output.shouldHaveExitValue(0); 202 } 203 return this; 204 } 205 206 public Result assertAbnormalExit(String... matches) throws Exception { 207 if (!hasMappingFailure) { 208 checkMatches(output, matches); 209 output.shouldNotHaveExitValue(0); 210 } 211 212 return this; 213 } 214 215 public OutputAnalyzer getOutput() { 216 return output; 217 } 218 } 219 220 // Specify this property to copy sdandard output of the child test process to 221 // the parent/main stdout of the test. 222 // By default such output is logged into a file, and is copied into the main stdout. 223 public static final boolean CopyChildStdoutToMainStdout = 224 Boolean.valueOf(System.getProperty("test.cds.copy.child.stdout", "true")); 225 226 // This property is passed to child test processes 227 public static final String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0"); 228 229 public static final String UnableToMapMsg = 230 "Unable to map shared archive: test did not complete; assumed PASS"; 231 232 // Create bootstrap CDS archive, 233 // use extra JVM command line args as a prefix. 234 // For CDS tests specifying prefix makes more sense than specifying suffix, since 235 // normally there are no classes or arguments to classes, just "-version" 236 // To specify suffix explicitly use CDSOptions.addSuffix() 237 public static OutputAnalyzer createArchive(String... cliPrefix) 238 throws Exception { 239 return createArchive((new CDSOptions()).addPrefix(cliPrefix)); 240 } 241 242 // Create bootstrap CDS archive 243 public static OutputAnalyzer createArchive(CDSOptions opts) 244 throws Exception { 245 246 startNewArchiveName(); 247 248 ArrayList<String> cmd = new ArrayList<String>(); 249 250 for (String p : opts.prefix) cmd.add(p); 251 252 cmd.add("-Xshare:dump"); 253 cmd.add("-Xlog:cds,cds+hashtables"); 254 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 255 if (opts.archiveName == null) 256 opts.archiveName = getDefaultArchiveName(); 257 cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName); 258 259 for (String s : opts.suffix) cmd.add(s); 260 261 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 262 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 263 return executeAndLog(pb, "dump"); 264 } 265 266 267 // check result of 'dump-the-archive' operation, that is "-Xshare:dump" 268 public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) 269 throws Exception { 270 271 output.shouldContain("Loading classes to share"); 272 output.shouldHaveExitValue(0); 273 274 for (String match : extraMatches) { 275 output.shouldContain(match); 276 } 277 278 return output; 279 } 280 281 282 // A commonly used convenience methods to create an archive and check the results 283 // Creates an archive and checks for errors 284 public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts) 285 throws Exception { 286 return checkDump(createArchive(opts)); 287 } 288 289 290 public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix) 291 throws Exception { 292 return checkDump(createArchive(cliPrefix)); 293 } 294 295 296 // This method should be used to check the output of child VM for common exceptions. 297 // Most of CDS tests deal with child VM processes for creating and using the archive. 298 // However exceptions that occur in the child process do not automatically propagate 299 // to the parent process. This mechanism aims to improve the propagation 300 // of exceptions and common errors. 301 // Exception e argument - an exception to be re-thrown if none of the common 302 // exceptions match. Pass null if you wish not to re-throw any exception. 303 public static boolean checkCommonExecExceptions(OutputAnalyzer output, Exception e) 304 throws Exception { 305 if (output.getStdout().contains("http://bugreport.java.com/bugreport/crash.jsp")) { 306 throw new RuntimeException("Hotspot crashed"); 307 } 308 if (output.getStdout().contains("TEST FAILED")) { 309 throw new RuntimeException("Test Failed"); 310 } 311 if (output.getOutput().contains("shared class paths mismatch")) { 312 // throw new RuntimeException("shared class paths mismatch"); 313 } 314 if (output.getOutput().contains("Unable to unmap shared space")) { 315 throw new RuntimeException("Unable to unmap shared space"); 316 } 317 318 // Special case -- sometimes Xshare:on fails because it failed to map 319 // at given address. This behavior is platform-specific, machine config-specific 320 // and can be random (see ASLR). 321 if (isUnableToMap(output)) { 322 System.out.println(UnableToMapMsg); 323 return true; 324 } 325 326 if (e != null) { 327 throw e; 328 } 329 return false; 330 } 331 332 public static boolean checkCommonExecExceptions(OutputAnalyzer output) throws Exception { 333 return checkCommonExecExceptions(output, null); 334 } 335 336 337 // Check the output for indication that mapping of the archive failed. 338 // Performance note: this check seems to be rather costly - searching the entire 339 // output stream of a child process for multiple strings. However, it is necessary 340 // to detect this condition, a failure to map an archive, since this is not a real 341 // failure of the test or VM operation, and results in a test being "skipped". 342 // Suggestions to improve: 343 // 1. VM can designate a special exit code for such condition. 344 // 2. VM can print a single distinct string indicating failure to map an archive, 345 // instead of utilizing multiple messages. 346 // These are suggestions to improve testibility of the VM. However, implementing them 347 // could also improve usability in the field. 348 public static boolean isUnableToMap(OutputAnalyzer output) { 349 String outStr = output.getOutput(); 350 if ((output.getExitValue() == 1) && ( 351 outStr.contains("Unable to reserve shared space at required address") || 352 outStr.contains("Unable to map ReadOnly shared space at required address") || 353 outStr.contains("Unable to map ReadWrite shared space at required address") || 354 outStr.contains("Unable to map MiscData shared space at required address") || 355 outStr.contains("Unable to map MiscCode shared space at required address") || 356 outStr.contains("Unable to map OptionalData shared space at required address") || 357 outStr.contains("Could not allocate metaspace at a compatible address") || 358 outStr.contains("UseSharedSpaces: Unable to allocate region, range is not within java heap") )) 359 { 360 return true; 361 } 362 363 return false; 364 } 365 366 public static Result run(String... cliPrefix) throws Exception { 367 CDSOptions opts = new CDSOptions(); 368 opts.setArchiveName(getDefaultArchiveName()); 369 opts.addPrefix(cliPrefix); 370 return new Result(opts, runWithArchive(opts)); 371 } 372 373 public static Result run(CDSOptions opts) throws Exception { 374 return new Result(opts, runWithArchive(opts)); 375 } 376 377 // Execute JVM with CDS archive, specify command line args suffix 378 public static OutputAnalyzer runWithArchive(String... cliPrefix) 379 throws Exception { 380 381 return runWithArchive( (new CDSOptions()) 382 .setArchiveName(getDefaultArchiveName()) 383 .addPrefix(cliPrefix) ); 384 } 385 386 387 // Execute JVM with CDS archive, specify CDSOptions 388 public static OutputAnalyzer runWithArchive(CDSOptions opts) 389 throws Exception { 390 391 ArrayList<String> cmd = new ArrayList<String>(); 392 393 for (String p : opts.prefix) cmd.add(p); 394 395 cmd.add("-Xshare:" + opts.xShareMode); 396 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 397 cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor); 398 399 if (opts.archiveName == null) 400 opts.archiveName = getDefaultArchiveName(); 401 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 402 403 if (opts.useVersion) 404 cmd.add("-version"); 405 406 for (String s : opts.suffix) cmd.add(s); 407 408 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 409 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 410 return executeAndLog(pb, "exec"); 411 } 412 413 414 // A commonly used convenience methods to create an archive and check the results 415 // Creates an archive and checks for errors 416 public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception { 417 return checkExec(runWithArchive(opts)); 418 } 419 420 421 public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception { 422 return checkExec(runWithArchive(cliPrefix)); 423 } 424 425 426 public static OutputAnalyzer checkExec(OutputAnalyzer output, 427 String... extraMatches) throws Exception { 428 CDSOptions opts = new CDSOptions(); 429 return checkExec(output, opts, extraMatches); 430 } 431 432 433 // check result of 'exec' operation, that is when JVM is run using the archive 434 public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts, 435 String... extraMatches) throws Exception { 436 try { 437 if ("on".equals(opts.xShareMode)) { 438 output.shouldContain("sharing"); 439 } 440 output.shouldHaveExitValue(0); 441 } catch (RuntimeException e) { 442 checkCommonExecExceptions(output, e); 443 return output; 444 } 445 446 checkMatches(output, extraMatches); 447 return output; 448 } 449 450 451 public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output, 452 int expectedExitValue, 453 String... extraMatches) throws Exception { 454 if (isUnableToMap(output)) { 455 System.out.println(UnableToMapMsg); 456 return output; 457 } 458 459 output.shouldHaveExitValue(expectedExitValue); 460 checkMatches(output, extraMatches); 461 return output; 462 } 463 464 public static OutputAnalyzer checkMatches(OutputAnalyzer output, 465 String... matches) throws Exception { 466 for (String match : matches) { 467 output.shouldContain(match); 468 } 469 return output; 470 } 471 472 473 // get the file object for the test artifact 474 public static File getTestArtifact(String name, boolean checkExistence) { 475 File dir = new File(System.getProperty("test.classes", ".")); 476 File file = new File(dir, name); 477 478 if (checkExistence && !file.exists()) { 479 throw new RuntimeException("Cannot find " + file.getPath()); 480 } 481 482 return file; 483 } 484 485 486 // create file containing the specified class list 487 public static File makeClassList(String classes[]) 488 throws Exception { 489 return makeClassList(getTestName() + "-", classes); 490 } 491 492 // create file containing the specified class list 493 public static File makeClassList(String testCaseName, String classes[]) 494 throws Exception { 495 496 File classList = getTestArtifact(testCaseName + "test.classlist", false); 497 FileOutputStream fos = new FileOutputStream(classList); 498 PrintStream ps = new PrintStream(fos); 499 500 addToClassList(ps, classes); 501 502 ps.close(); 503 fos.close(); 504 505 return classList; 506 } 507 508 509 public static void addToClassList(PrintStream ps, String classes[]) 510 throws IOException 511 { 512 if (classes != null) { 513 for (String s : classes) { 514 ps.println(s); 515 } 516 } 517 } 518 519 520 // Optimization for getting a test name. 521 // Test name does not change during execution of the test, 522 // but getTestName() uses stack walking hence it is expensive. 523 // Therefore cache it and reuse it. 524 private static String testName; 525 public static String getTestName() { 526 if (testName == null) { 527 testName = Utils.getTestName(); 528 } 529 return testName; 530 } 531 532 private static final SimpleDateFormat timeStampFormat = 533 new SimpleDateFormat("HH'h'mm'm'ss's'SSS"); 534 535 private static String defaultArchiveName; 536 537 // Call this method to start new archive with new unique name 538 public static void startNewArchiveName() { 539 defaultArchiveName = getTestName() + 540 timeStampFormat.format(new Date()) + ".jsa"; 541 } 542 543 public static String getDefaultArchiveName() { 544 return defaultArchiveName; 545 } 546 547 548 // ===================== FILE ACCESS convenience methods 549 public static File getOutputFile(String name) { 550 File dir = new File(System.getProperty("test.classes", ".")); 551 return new File(dir, getTestName() + "-" + name); 552 } 553 554 555 public static File getOutputSourceFile(String name) { 556 File dir = new File(System.getProperty("test.classes", ".")); 557 return new File(dir, name); 558 } 559 560 561 public static File getSourceFile(String name) { 562 File dir = new File(System.getProperty("test.src", ".")); 563 return new File(dir, name); 564 } 565 566 567 // ============================= Logging 568 public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception { 569 long started = System.currentTimeMillis(); 570 OutputAnalyzer output = new OutputAnalyzer(pb.start()); 571 572 writeFile(getOutputFile(logName + ".stdout"), output.getStdout()); 573 writeFile(getOutputFile(logName + ".stderr"), output.getStderr()); 574 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 575 System.out.println("[STDERR]\n" + output.getStderr()); 576 577 if (CopyChildStdoutToMainStdout) 578 System.out.println("[STDOUT]\n" + output.getStdout()); 579 580 return output; 581 } 582 583 584 private static void writeFile(File file, String content) throws Exception { 585 FileOutputStream fos = new FileOutputStream(file); 586 PrintStream ps = new PrintStream(fos); 587 ps.print(content); 588 ps.close(); 589 fos.close(); 590 } 591 }