1 /* 2 * Copyright (c) 2017, 2025, 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.nio.file.Files; 30 import java.nio.file.CopyOption; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.nio.file.StandardCopyOption; 34 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; 35 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 36 import java.text.SimpleDateFormat; 37 import java.util.List; 38 import java.util.ArrayList; 39 import java.util.Date; 40 import jdk.test.lib.JDKToolFinder; 41 import jdk.test.lib.Utils; 42 import jdk.test.lib.process.OutputAnalyzer; 43 import jdk.test.lib.process.ProcessTools; 44 import jtreg.SkippedException; 45 46 // This class contains common test utilities for testing CDS 47 public class CDSTestUtils { 48 public static final String MSG_RANGE_NOT_WITHIN_HEAP = 49 "Unable to allocate region, range is not within java heap."; 50 public static final String MSG_RANGE_ALREADT_IN_USE = 51 "Unable to allocate region, java heap range is already in use."; 52 public static final String MSG_DYNAMIC_NOT_SUPPORTED = 53 "-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded"; 54 public static final String MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE = 55 "an object points to a static field that may hold a different value at runtime"; 56 public static final boolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive"); 57 58 public interface Checker { 59 public void check(OutputAnalyzer output) throws Exception; 60 } 61 62 /* 63 * INTRODUCTION 64 * 65 * When testing various CDS functionalities, we need to launch JVM processes 66 * using a "launch method" (such as TestCommon.run), and analyze the results of these 67 * processes. 68 * 69 * While typical jtreg tests would use OutputAnalyzer in such cases, due to the 70 * complexity of CDS failure modes, we have added the CDSTestUtils.Result class 71 * to make the analysis more convenient and less error prone. 72 * 73 * A Java process can end in one of the following 4 states: 74 * 75 * 1: Unexpected error - such as JVM crashing. In this case, the "launch method" 76 * will throw a RuntimeException. 77 * 2: Mapping Failure - this happens when the OS (intermittently) fails to map the 78 * CDS archive, normally caused by Address Space Layout Randomization. 79 * We usually treat this as "pass". 80 * 3: Normal Exit - the JVM process has finished without crashing, and the exit code is 0. 81 * 4: Abnormal Exit - the JVM process has finished without crashing, and the exit code is not 0. 82 * 83 * In most test cases, we need to check the JVM process's output in cases 3 and 4. However, we need 84 * to make sure that our test code is not confused by case 2. 85 * 86 * For example, a JVM process is expected to print the string "Hi" and exit with 0. With the old 87 * CDSTestUtils.runWithArchive API, the test may be written as this: 88 * 89 * OutputAnalyzer out = CDSTestUtils.runWithArchive(args); 90 * out.shouldContain("Hi"); 91 * 92 * However, if the JVM process fails with mapping failure, the string "Hi" will not be in the output, 93 * and your test case will fail intermittently. 94 * 95 * Instead, the test case should be written as 96 * 97 * CDSTestUtils.run(args).assertNormalExit("Hi"); 98 * 99 * EXAMPLES/HOWTO 100 * 101 * 1. For simple substring matching: 102 * 103 * CDSTestUtils.run(args).assertNormalExit("Hi"); 104 * CDSTestUtils.run(args).assertNormalExit("a", "b", "x"); 105 * CDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2"); 106 * 107 * 2. For more complex output matching: using Lambda expressions 108 * 109 * CDSTestUtils.run(args) 110 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed"); 111 * CDSTestUtils.run(args) 112 * .assertAbnormalExit(output -> { 113 * output.shouldNotContain("this should not be printed"); 114 * output.shouldHaveExitValue(123); 115 * }); 116 * 117 * 3. Chaining several checks: 118 * 119 * CDSTestUtils.run(args) 120 * .assertNormalExit(output -> output.shouldNotContain("this should not be printed") 121 * .assertNormalExit("should have this", "should have that"); 122 * 123 * 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally: 124 * 125 * CDSTestUtils.run(args) 126 * .ifNormalExit("ths string is printed when exiting with 0") 127 * .ifAbNormalExit("ths string is printed when exiting with 1"); 128 * 129 * NOTE: you usually don't want to write your test case like this -- it should always 130 * exit with the same exit code. (But I kept this API because some existing test cases 131 * behave this way -- need to revisit). 132 */ 133 public static class Result { 134 private final OutputAnalyzer output; 135 private final CDSOptions options; 136 private final boolean hasNormalExit; 137 private final String CDS_DISABLED = "warning: CDS is disabled when the"; 138 139 public Result(CDSOptions opts, OutputAnalyzer out) throws Exception { 140 checkMappingFailure(out); 141 this.options = opts; 142 this.output = out; 143 hasNormalExit = (output.getExitValue() == 0); 144 145 if (hasNormalExit) { 146 if ("on".equals(options.xShareMode) && 147 output.getStderr().contains("java version") && 148 !output.getStderr().contains(CDS_DISABLED)) { 149 // "-showversion" is always passed in the command-line by the execXXX methods. 150 // During normal exit, we require that the VM to show that sharing was enabled. 151 output.shouldContain("sharing"); 152 } 153 } 154 } 155 156 public Result assertNormalExit(Checker checker) throws Exception { 157 checker.check(output); 158 output.shouldHaveExitValue(0); 159 return this; 160 } 161 162 public Result assertAbnormalExit(Checker checker) throws Exception { 163 checker.check(output); 164 output.shouldNotHaveExitValue(0); 165 return this; 166 } 167 168 // When {--limit-modules, --patch-module, and/or --upgrade-module-path} 169 // are specified, CDS is silently disabled for both -Xshare:auto and -Xshare:on. 170 public Result assertSilentlyDisabledCDS(Checker checker) throws Exception { 171 // this comes from a JVM warning message. 172 output.shouldContain(CDS_DISABLED); 173 checker.check(output); 174 return this; 175 } 176 177 public Result assertSilentlyDisabledCDS(int exitCode, String... matches) throws Exception { 178 return assertSilentlyDisabledCDS((out) -> { 179 out.shouldHaveExitValue(exitCode); 180 checkMatches(out, matches); 181 }); 182 } 183 184 public Result ifNormalExit(Checker checker) throws Exception { 185 if (hasNormalExit) { 186 checker.check(output); 187 } 188 return this; 189 } 190 191 public Result ifAbnormalExit(Checker checker) throws Exception { 192 if (!hasNormalExit) { 193 checker.check(output); 194 } 195 return this; 196 } 197 198 public Result ifNoMappingFailure(Checker checker) throws Exception { 199 checker.check(output); 200 return this; 201 } 202 203 204 public Result assertNormalExit(String... matches) throws Exception { 205 checkMatches(output, matches); 206 output.shouldHaveExitValue(0); 207 return this; 208 } 209 210 public Result assertAbnormalExit(String... matches) throws Exception { 211 checkMatches(output, matches); 212 output.shouldNotHaveExitValue(0); 213 return this; 214 } 215 } 216 217 // A number to be included in the filename of the stdout and the stderr output file. 218 static int logCounter = 0; 219 220 private static int getNextLogCounter() { 221 return logCounter++; 222 } 223 224 // By default, stdout of child processes are logged in files such as 225 // <testname>-0000-exec.stdout. If you want to also include the stdout 226 // inside jtr files, you can override this in the jtreg command line like 227 // "jtreg -Dtest.cds.copy.child.stdout=true ...." 228 public static final boolean copyChildStdoutToMainStdout = 229 Boolean.getBoolean("test.cds.copy.child.stdout"); 230 231 // This property is passed to child test processes 232 public static final String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0"); 233 234 public static final String UnableToMapMsg = 235 "Unable to map shared archive: test did not complete"; 236 237 // Create bootstrap CDS archive, 238 // use extra JVM command line args as a prefix. 239 // For CDS tests specifying prefix makes more sense than specifying suffix, since 240 // normally there are no classes or arguments to classes, just "-version" 241 // To specify suffix explicitly use CDSOptions.addSuffix() 242 public static OutputAnalyzer createArchive(String... cliPrefix) 243 throws Exception { 244 return createArchive((new CDSOptions()).addPrefix(cliPrefix)); 245 } 246 247 // Create bootstrap CDS archive 248 public static OutputAnalyzer createArchive(CDSOptions opts) 249 throws Exception { 250 251 startNewArchiveName(); 252 253 ArrayList<String> cmd = new ArrayList<String>(); 254 255 for (String p : opts.prefix) cmd.add(p); 256 257 cmd.add("-Xshare:dump"); 258 cmd.add("-Xlog:cds,aot+hashtables"); 259 if (opts.archiveName == null) 260 opts.archiveName = getDefaultArchiveName(); 261 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 262 263 if (opts.classList != null) { 264 File classListFile = makeClassList(opts.classList); 265 cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath()); 266 } 267 268 for (String s : opts.suffix) cmd.add(s); 269 270 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 271 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine); 272 return executeAndLog(pb, "dump"); 273 } 274 275 public static boolean isDynamicArchive() { 276 return DYNAMIC_DUMP; 277 } 278 279 // check result of 'dump-the-archive' operation, that is "-Xshare:dump" 280 public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) 281 throws Exception { 282 283 if (!DYNAMIC_DUMP) { 284 output.shouldContain("Loading classes to share"); 285 } else { 286 output.shouldContain("Written dynamic archive 0x"); 287 } 288 output.shouldHaveExitValue(0); 289 //output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); // FIXME -- leyden+JEP483 merge 290 291 for (String match : extraMatches) { 292 output.shouldContain(match); 293 } 294 295 return output; 296 } 297 298 // check result of dumping base archive 299 public static OutputAnalyzer checkBaseDump(OutputAnalyzer output) throws Exception { 300 output.shouldContain("Loading classes to share"); 301 output.shouldHaveExitValue(0); 302 //output.shouldNotContain(MSG_STATIC_FIELD_MAY_HOLD_DIFFERENT_VALUE); // FIXME -- leyden+JEP483 merge 303 return output; 304 } 305 306 // A commonly used convenience methods to create an archive and check the results 307 // Creates an archive and checks for errors 308 public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts) 309 throws Exception { 310 return checkDump(createArchive(opts)); 311 } 312 313 314 public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix) 315 throws Exception { 316 return checkDump(createArchive(cliPrefix)); 317 } 318 319 320 // This method should be used to check the output of child VM for common exceptions. 321 // Most of CDS tests deal with child VM processes for creating and using the archive. 322 // However exceptions that occur in the child process do not automatically propagate 323 // to the parent process. This mechanism aims to improve the propagation 324 // of exceptions and common errors. 325 // Exception e argument - an exception to be re-thrown if none of the common 326 // exceptions match. Pass null if you wish not to re-throw any exception. 327 public static void checkCommonExecExceptions(OutputAnalyzer output, Exception e) 328 throws Exception { 329 if (output.getStdout().contains("https://bugreport.java.com/bugreport/crash.jsp")) { 330 throw new RuntimeException("Hotspot crashed"); 331 } 332 if (output.getStdout().contains("TEST FAILED")) { 333 throw new RuntimeException("Test Failed"); 334 } 335 if (output.getOutput().contains("Unable to unmap shared space")) { 336 throw new RuntimeException("Unable to unmap shared space"); 337 } 338 339 // Special case -- sometimes Xshare:on fails because it failed to map 340 // at given address. This behavior is platform-specific, machine config-specific 341 // and can be random (see ASLR). 342 checkMappingFailure(output); 343 344 if (e != null) { 345 throw e; 346 } 347 } 348 349 public static void checkCommonExecExceptions(OutputAnalyzer output) throws Exception { 350 checkCommonExecExceptions(output, null); 351 } 352 353 354 // Check the output for indication that mapping of the archive failed. 355 // Performance note: this check seems to be rather costly - searching the entire 356 // output stream of a child process for multiple strings. However, it is necessary 357 // to detect this condition, a failure to map an archive, since this is not a real 358 // failure of the test or VM operation, and results in a test being "skipped". 359 // Suggestions to improve: 360 // 1. VM can designate a special exit code for such condition. 361 // 2. VM can print a single distinct string indicating failure to map an archive, 362 // instead of utilizing multiple messages. 363 // These are suggestions to improve testibility of the VM. However, implementing them 364 // could also improve usability in the field. 365 private static String hasUnableToMapMessage(OutputAnalyzer output) { 366 String outStr = output.getOutput(); 367 if ((output.getExitValue() == 1)) { 368 if (outStr.contains(MSG_RANGE_NOT_WITHIN_HEAP)) { 369 return MSG_RANGE_NOT_WITHIN_HEAP; 370 } 371 if (outStr.contains(MSG_DYNAMIC_NOT_SUPPORTED)) { 372 return MSG_DYNAMIC_NOT_SUPPORTED; 373 } 374 } 375 376 return null; 377 } 378 379 public static boolean isUnableToMap(OutputAnalyzer output) { 380 return hasUnableToMapMessage(output) != null; 381 } 382 383 public static void checkMappingFailure(OutputAnalyzer out) throws SkippedException { 384 String match = hasUnableToMapMessage(out); 385 if (match != null) { 386 throw new SkippedException(UnableToMapMsg + ": " + match); 387 } 388 } 389 390 public static Result run(String... cliPrefix) throws Exception { 391 CDSOptions opts = new CDSOptions(); 392 opts.setArchiveName(getDefaultArchiveName()); 393 opts.addPrefix(cliPrefix); 394 return new Result(opts, runWithArchive(opts)); 395 } 396 397 public static Result run(CDSOptions opts) throws Exception { 398 return new Result(opts, runWithArchive(opts)); 399 } 400 401 // Dump a classlist using the -XX:DumpLoadedClassList option. 402 public static Result dumpClassList(String classListName, String... cli) 403 throws Exception { 404 CDSOptions opts = (new CDSOptions()) 405 .setUseVersion(false) 406 .setXShareMode("auto") 407 .addPrefix("-XX:DumpLoadedClassList=" + classListName) 408 .addSuffix(cli); 409 Result res = run(opts).assertNormalExit(); 410 return res; 411 } 412 413 // Execute JVM with CDS archive, specify command line args suffix 414 public static OutputAnalyzer runWithArchive(String... cliPrefix) 415 throws Exception { 416 417 return runWithArchive( (new CDSOptions()) 418 .setArchiveName(getDefaultArchiveName()) 419 .addPrefix(cliPrefix) ); 420 } 421 422 // Enable basic verification (VerifyArchivedFields=1, no side effects) for all CDS 423 // tests to make sure the archived heap objects are mapped/loaded properly. 424 public static void addVerifyArchivedFields(ArrayList<String> cmd) { 425 cmd.add("-XX:+UnlockDiagnosticVMOptions"); 426 cmd.add("-XX:VerifyArchivedFields=1"); 427 } 428 429 // Execute JVM with CDS archive, specify CDSOptions 430 public static OutputAnalyzer runWithArchive(CDSOptions opts) 431 throws Exception { 432 433 ArrayList<String> cmd = new ArrayList<String>(); 434 cmd.addAll(opts.prefix); 435 cmd.add("-Xshare:" + opts.xShareMode); 436 cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor); 437 438 if (!opts.useSystemArchive) { 439 if (opts.archiveName == null) 440 opts.archiveName = getDefaultArchiveName(); 441 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 442 } 443 if (!opts.benchmarkMode) { 444 addVerifyArchivedFields(cmd); 445 } 446 447 if (opts.useVersion) 448 cmd.add("-version"); 449 450 for (String s : opts.suffix) cmd.add(s); 451 452 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 453 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine); 454 return executeAndLog(pb, "exec"); 455 } 456 457 458 // A commonly used convenience methods to create an archive and check the results 459 // Creates an archive and checks for errors 460 public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception { 461 return checkExec(runWithArchive(opts)); 462 } 463 464 465 public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception { 466 return checkExec(runWithArchive(cliPrefix)); 467 } 468 469 470 public static OutputAnalyzer checkExec(OutputAnalyzer output, 471 String... extraMatches) throws Exception { 472 CDSOptions opts = new CDSOptions(); 473 return checkExec(output, opts, extraMatches); 474 } 475 476 477 // check result of 'exec' operation, that is when JVM is run using the archive 478 public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts, 479 String... extraMatches) throws Exception { 480 try { 481 if ("on".equals(opts.xShareMode)) { 482 output.shouldContain("sharing"); 483 } 484 output.shouldHaveExitValue(0); 485 } catch (RuntimeException e) { 486 checkCommonExecExceptions(output, e); 487 return output; 488 } 489 490 checkMatches(output, extraMatches); 491 return output; 492 } 493 494 495 public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output, 496 int expectedExitValue, 497 String... extraMatches) throws Exception { 498 checkMappingFailure(output); 499 output.shouldHaveExitValue(expectedExitValue); 500 checkMatches(output, extraMatches); 501 return output; 502 } 503 504 public static OutputAnalyzer checkMatches(OutputAnalyzer output, 505 String... matches) throws Exception { 506 for (String match : matches) { 507 output.shouldContain(match); 508 } 509 return output; 510 } 511 512 private static final String outputDir; 513 private static final File outputDirAsFile; 514 515 static { 516 outputDir = System.getProperty("user.dir", "."); 517 outputDirAsFile = new File(outputDir); 518 } 519 520 public static String getOutputDir() { 521 return outputDir; 522 } 523 524 public static File getOutputDirAsFile() { 525 return outputDirAsFile; 526 } 527 528 // get the file object for the test artifact 529 public static File getTestArtifact(String name, boolean checkExistence) { 530 File file = new File(outputDirAsFile, name); 531 532 if (checkExistence && !file.exists()) { 533 throw new RuntimeException("Cannot find " + file.getPath()); 534 } 535 536 return file; 537 } 538 539 540 // create file containing the specified class list 541 public static File makeClassList(String classes[]) 542 throws Exception { 543 return makeClassList(testName + "-", classes); 544 } 545 546 // create file containing the specified class list 547 public static File makeClassList(String testCaseName, String classes[]) 548 throws Exception { 549 550 File classList = getTestArtifact(testCaseName + "test.classlist", false); 551 FileOutputStream fos = new FileOutputStream(classList); 552 PrintStream ps = new PrintStream(fos); 553 554 addToClassList(ps, classes); 555 556 ps.close(); 557 fos.close(); 558 559 return classList; 560 } 561 562 563 public static void addToClassList(PrintStream ps, String classes[]) 564 throws IOException 565 { 566 if (classes != null) { 567 for (String s : classes) { 568 ps.println(s); 569 } 570 } 571 } 572 573 private static String testName = Utils.TEST_NAME.replace('/', '.'); 574 575 private static final SimpleDateFormat timeStampFormat = 576 new SimpleDateFormat("HH'h'mm'm'ss's'SSS"); 577 578 private static String defaultArchiveName; 579 580 // Call this method to start new archive with new unique name 581 public static void startNewArchiveName() { 582 defaultArchiveName = testName + 583 timeStampFormat.format(new Date()) + ".jsa"; 584 } 585 586 public static String getDefaultArchiveName() { 587 return defaultArchiveName; 588 } 589 590 591 // ===================== FILE ACCESS convenience methods 592 public static File getOutputFile(String name) { 593 return new File(outputDirAsFile, testName + "-" + name); 594 } 595 596 public static String getOutputFileName(String name) { 597 return getOutputFile(name).getName(); 598 } 599 600 601 public static File getOutputSourceFile(String name) { 602 return new File(outputDirAsFile, name); 603 } 604 605 606 public static File getSourceFile(String name) { 607 File dir = new File(System.getProperty("test.src", ".")); 608 return new File(dir, name); 609 } 610 611 // Check commandline for the last instance of Xshare to see if the process can load 612 // a CDS archive 613 public static boolean isRunningWithArchive(List<String> cmd) { 614 // -Xshare only works for the java executable 615 if (!cmd.get(0).equals(JDKToolFinder.getJDKTool("java")) || cmd.size() < 2) { 616 return false; 617 } 618 619 // -Xshare options are likely at the end of the args list 620 for (int i = cmd.size() - 1; i >= 1; i--) { 621 String s = cmd.get(i); 622 if (s.equals("-Xshare:dump") || s.equals("-Xshare:off")) { 623 return false; 624 } 625 } 626 return true; 627 } 628 629 public static boolean isGCOption(String s) { 630 return s.startsWith("-XX:+Use") && s.endsWith("GC"); 631 } 632 633 public static boolean hasGCOption(List<String> cmd) { 634 for (String s : cmd) { 635 if (isGCOption(s)) { 636 return true; 637 } 638 } 639 return false; 640 } 641 642 // Handle and insert test.cds.runtime.options to commandline 643 // The test.cds.runtime.options property is used to inject extra VM options to 644 // subprocesses launched by the CDS test cases using executeAndLog(). 645 // The injection applies only to subprocesses that: 646 // - are launched by the standard java launcher (bin/java) 647 // - are not dumping the CDS archive with -Xshare:dump 648 // - do not explicitly disable CDS via -Xshare:off 649 // 650 // The main purpose of this property is to test the runtime loading of 651 // the CDS "archive heap region" with non-default garbage collectors. E.g., 652 // 653 // jtreg -vmoptions:-Dtest.cds.runtime.options=-XX:+UnlockExperimentalVMOptions,-XX:+UseEpsilonGC \ 654 // test/hotspot/jtreg/runtime/cds 655 // 656 // Note that the injection is not applied to -Xshare:dump, so that the CDS archives 657 // will be dumped with G1, which is the only collector that supports dumping 658 // the archive heap region. Similarly, if a UseXxxGC option already exists in the command line, 659 // the UseXxxGC option added in test.cds.runtime.options will be ignored. 660 public static void handleCDSRuntimeOptions(ProcessBuilder pb) { 661 List<String> cmd = pb.command(); 662 String jtropts = System.getProperty("test.cds.runtime.options"); 663 if (jtropts != null && isRunningWithArchive(cmd)) { 664 // There cannot be multiple GC options in the command line so some 665 // options may be ignored 666 ArrayList<String> cdsRuntimeOpts = new ArrayList<String>(); 667 boolean hasGCOption = hasGCOption(cmd); 668 for (String s : jtropts.split(",")) { 669 if (!CDSOptions.disabledRuntimePrefixes.contains(s) && 670 !(hasGCOption && isGCOption(s))) { 671 cdsRuntimeOpts.add(s); 672 } 673 } 674 pb.command().addAll(1, cdsRuntimeOpts); 675 } 676 } 677 678 // ============================= Logging 679 public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception { 680 handleCDSRuntimeOptions(pb); 681 return executeAndLog(pb.start(), logName); 682 } 683 684 public static OutputAnalyzer executeAndLog(Process process, String logName) throws Exception { 685 long started = System.currentTimeMillis(); 686 687 OutputAnalyzer output = new OutputAnalyzer(process); 688 String logFileNameStem = 689 String.format("%04d", getNextLogCounter()) + "-" + logName; 690 691 File stdout = getOutputFile(logFileNameStem + ".stdout"); 692 File stderr = getOutputFile(logFileNameStem + ".stderr"); 693 694 writeFile(stdout, output.getStdout()); 695 writeFile(stderr, output.getStderr()); 696 System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]"); 697 System.out.println("[logging stdout to " + stdout + "]"); 698 System.out.println("[logging stderr to " + stderr + "]"); 699 System.out.println("[STDERR]\n" + output.getStderr()); 700 701 if (copyChildStdoutToMainStdout) 702 System.out.println("[STDOUT]\n" + output.getStdout()); 703 704 if (output.getExitValue() != 0 && output.getStdout().contains("A fatal error has been detected")) { 705 throw new RuntimeException("Hotspot crashed"); 706 } 707 return output; 708 } 709 710 711 private static void writeFile(File file, String content) throws Exception { 712 FileOutputStream fos = new FileOutputStream(file); 713 PrintStream ps = new PrintStream(fos); 714 ps.print(content); 715 ps.close(); 716 fos.close(); 717 } 718 719 // Format a line that defines an extra symbol in the file specify by -XX:SharedArchiveConfigFile=<file> 720 public static String formatArchiveConfigSymbol(String symbol) { 721 int refCount = -1; // This is always -1 in the current HotSpot implementation. 722 if (isAsciiPrintable(symbol)) { 723 return symbol.length() + " " + refCount + ": " + symbol; 724 } else { 725 StringBuilder sb = new StringBuilder(); 726 int utf8_length = escapeArchiveConfigString(sb, symbol); 727 return utf8_length + " " + refCount + ": " + sb.toString(); 728 } 729 } 730 731 // This method generates the same format as HashtableTextDump::put_utf8() in HotSpot, 732 // to be used by -XX:SharedArchiveConfigFile=<file>. 733 private static int escapeArchiveConfigString(StringBuilder sb, String s) { 734 byte arr[]; 735 try { 736 arr = s.getBytes("UTF8"); 737 } catch (java.io.UnsupportedEncodingException e) { 738 throw new RuntimeException("Unexpected", e); 739 } 740 for (int i = 0; i < arr.length; i++) { 741 char ch = (char)(arr[i] & 0xff); 742 if (isAsciiPrintable(ch)) { 743 sb.append(ch); 744 } else if (ch == '\t') { 745 sb.append("\\t"); 746 } else if (ch == '\r') { 747 sb.append("\\r"); 748 } else if (ch == '\n') { 749 sb.append("\\n"); 750 } else if (ch == '\\') { 751 sb.append("\\\\"); 752 } else { 753 String hex = Integer.toHexString(ch); 754 if (ch < 16) { 755 sb.append("\\x0"); 756 } else { 757 sb.append("\\x"); 758 } 759 sb.append(hex); 760 } 761 } 762 763 return arr.length; 764 } 765 766 private static boolean isAsciiPrintable(String s) { 767 for (int i = 0; i < s.length(); i++) { 768 if (!isAsciiPrintable(s.charAt(i))) { 769 return false; 770 } 771 } 772 return true; 773 } 774 775 private static boolean isAsciiPrintable(char ch) { 776 return ch >= 32 && ch < 127; 777 } 778 779 // JDK utility 780 781 // Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so" 782 // must be copied, because the java.home property is derived from the canonicalized paths of these 2 files. 783 // The jvm.cfg file must be copied because the cds/NonJVMVariantLocation.java 784 // test is testing a CDS archive can be loaded from a non-JVM variant directory. 785 // Set a list of {jvm, "java", "jvm.cfg"} which will be physically copied. If a file needs copied physically, add it to the list. 786 private static String[] phCopied = {System.mapLibraryName("jvm"), "java", "jvm.cfg"}; 787 public static void clone(File src, File dst) throws Exception { 788 if (dst.exists()) { 789 if (!dst.isDirectory()) { 790 throw new RuntimeException("Not a directory :" + dst); 791 } 792 } else { 793 if (!dst.mkdir()) { 794 throw new RuntimeException("Cannot create directory: " + dst); 795 } 796 } 797 // final String jvmLib = System.mapLibraryName("jvm"); 798 for (String child : src.list()) { 799 if (child.equals(".") || child.equals("..")) { 800 continue; 801 } 802 803 File child_src = new File(src, child); 804 File child_dst = new File(dst, child); 805 if (child_dst.exists()) { 806 throw new RuntimeException("Already exists: " + child_dst); 807 } 808 if (child_src.isFile()) { 809 boolean needPhCopy = false; 810 for (String target : phCopied) { 811 if (child.equals(target)) { 812 needPhCopy = true; 813 break; 814 } 815 } 816 if (needPhCopy) { 817 Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath(), 818 new CopyOption[] { StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES}); 819 } else { 820 Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath()); 821 } 822 } else { 823 clone(child_src, child_dst); 824 } 825 } 826 } 827 828 // modulesDir, like $JDK/lib 829 // oldName, module name under modulesDir 830 // newName, new name for oldName 831 public static void rename(File fromFile, File toFile) throws Exception { 832 if (!fromFile.exists()) { 833 throw new RuntimeException(fromFile.getName() + " does not exist"); 834 } 835 836 if (toFile.exists()) { 837 throw new RuntimeException(toFile.getName() + " already exists"); 838 } 839 840 boolean success = fromFile.renameTo(toFile); 841 if (!success) { 842 throw new RuntimeException("rename file " + fromFile.getName()+ " to " + toFile.getName() + " failed"); 843 } 844 } 845 846 public static ProcessBuilder makeBuilder(String... args) throws Exception { 847 System.out.print("["); 848 for (String s : args) { 849 System.out.print(" " + s); 850 } 851 System.out.println(" ]"); 852 return new ProcessBuilder(args); 853 } 854 855 public static Path copyFile(String srcFile, String destDir) throws Exception { 856 int idx = srcFile.lastIndexOf(File.separator); 857 String jarName = srcFile.substring(idx + 1); 858 Path srcPath = Paths.get(jarName); 859 Path newPath = Paths.get(destDir); 860 Path newDir; 861 if (!Files.exists(newPath)) { 862 newDir = Files.createDirectories(newPath); 863 } else { 864 newDir = newPath; 865 } 866 Path destPath = newDir.resolve(jarName); 867 Files.copy(srcPath, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES); 868 return destPath; 869 } 870 871 // Some tests were initially written without the knowledge of -XX:+AOTClassLinking. These tests need to 872 // be adjusted if -XX:+AOTClassLinking is specified in jtreg -vmoptions or -javaoptions: 873 public static boolean isAOTClassLinkingEnabled() { 874 return isBooleanVMOptionEnabledInCommandLine("AOTClassLinking"); 875 } 876 877 public static boolean isBooleanVMOptionEnabledInCommandLine(String optionName) { 878 String lastMatch = null; 879 String pattern = "^-XX:." + optionName + "$"; 880 for (String s : Utils.getTestJavaOpts()) { 881 if (s.matches(pattern)) { 882 lastMatch = s; 883 } 884 } 885 if (lastMatch != null && lastMatch.equals("-XX:+" + optionName)) { 886 return true; 887 } 888 return false; 889 } 890 }