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