1 /* 2 * Copyright (c) 2014, 2019, 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 */ 24 25 import jdk.test.lib.Utils; 26 import jdk.test.lib.BuildHelper; 27 import jdk.test.lib.JDKToolFinder; 28 import jdk.test.lib.Platform; 29 import jdk.test.lib.cds.CDSOptions; 30 import jdk.test.lib.cds.CDSTestUtils; 31 import jdk.test.lib.cds.CDSTestUtils.Result; 32 import jdk.test.lib.process.ProcessTools; 33 import jdk.test.lib.process.OutputAnalyzer; 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.FileOutputStream; 37 import java.io.InputStream; 38 import java.net.URI; 39 import java.nio.file.DirectoryStream; 40 import java.nio.file.Files; 41 import java.nio.file.FileSystem; 42 import java.nio.file.FileSystems; 43 import java.nio.file.Path; 44 import java.text.SimpleDateFormat; 45 import java.util.Arrays; 46 import java.util.ArrayList; 47 import java.util.Date; 48 import java.util.Enumeration; 49 import java.util.regex.Matcher; 50 import java.util.regex.Pattern; 51 import java.util.zip.ZipEntry; 52 import java.util.zip.ZipFile; 53 import java.util.zip.ZipOutputStream; 54 import jtreg.SkippedException; 55 import cdsutils.DynamicDumpHelper; 56 57 58 /** 59 * This is a test utility class for common AppCDS test functionality. 60 * 61 * Various methods use (String ...) for passing VM options. Note that the order 62 * of the VM options are important in certain cases. Many methods take arguments like 63 * 64 * (String prefix[], String suffix[], String... opts) 65 * 66 * Note that the order of the VM options is: 67 * 68 * prefix + opts + suffix 69 */ 70 public class TestCommon extends CDSTestUtils { 71 private static final String JSA_FILE_PREFIX = System.getProperty("user.dir") + 72 File.separator; 73 74 private static final SimpleDateFormat timeStampFormat = 75 new SimpleDateFormat("HH'h'mm'm'ss's'SSS"); 76 77 private static final String timeoutFactor = 78 System.getProperty("test.timeout.factor", "1.0"); 79 80 private static String currentArchiveName; 81 82 // Call this method to start new archive with new unique name 83 public static void startNewArchiveName() { 84 deletePriorArchives(); 85 currentArchiveName = getNewArchiveName(); 86 } 87 88 // Call this method to get current archive name 89 public static String getCurrentArchiveName() { 90 return currentArchiveName; 91 } 92 93 public static void setCurrentArchiveName(String archiveName) { 94 currentArchiveName = archiveName; 95 } 96 97 public static String getNewArchiveName() { 98 return getNewArchiveName(null); 99 } 100 101 public static String getNewArchiveName(String stem) { 102 if (stem == null) { 103 stem = "appcds"; 104 } 105 return JSA_FILE_PREFIX + stem + "-" + 106 timeStampFormat.format(new Date()) + ".jsa"; 107 } 108 109 // Attempt to clean old archives to preserve space 110 // Archives are large artifacts (20Mb or more), and much larger than 111 // most other artifacts created in jtreg testing. 112 // Therefore it is a good idea to clean the old archives when they are not needed. 113 // In most cases the deletion attempt will succeed; on rare occasion the 114 // delete operation will fail since the system or VM process still holds a handle 115 // to the file; in such cases the File.delete() operation will silently fail, w/o 116 // throwing an exception, thus allowing testing to continue. 117 public static void deletePriorArchives() { 118 File dir = new File(System.getProperty("user.dir")); 119 String files[] = dir.list(); 120 for (String name : files) { 121 if (name.startsWith("appcds-") && name.endsWith(".jsa")) { 122 if (!(new File(dir, name)).delete()) 123 System.out.println("deletePriorArchives(): delete failed for file " + name); 124 } 125 } 126 } 127 128 // Create AppCDS archive using most common args - convenience method 129 // Legacy name preserved for compatibility 130 public static OutputAnalyzer dump(String appJar, String classList[], 131 String... suffix) throws Exception { 132 return createArchive(appJar, classList, suffix); 133 } 134 135 public static OutputAnalyzer dump(String appJarDir, String appJar, String classList[], 136 String... suffix) throws Exception { 137 return createArchive(appJarDir, appJar, classList, suffix); 138 } 139 140 // Create AppCDS archive using most common args - convenience method 141 public static OutputAnalyzer createArchive(String appJar, String classList[], 142 String... suffix) throws Exception { 143 AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); 144 opts.setClassList(classList); 145 opts.addSuffix(suffix); 146 return createArchive(opts); 147 } 148 149 public static OutputAnalyzer createArchive(String appJarDir, String appJar, String classList[], 150 String... suffix) throws Exception { 151 AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); 152 opts.setAppJarDir(appJarDir); 153 opts.setClassList(classList); 154 opts.addSuffix(suffix); 155 return createArchive(opts); 156 } 157 158 // Simulate -Xshare:dump with -XX:ArchiveClassesAtExit. See comments around patchJarForDynamicDump() 159 private static final Class tmp = DynamicDumpHelper.class; 160 161 // Create AppCDS archive using appcds options 162 public static OutputAnalyzer createArchive(AppCDSOptions opts) 163 throws Exception { 164 ArrayList<String> cmd = new ArrayList<String>(); 165 startNewArchiveName(); 166 167 for (String p : opts.prefix) cmd.add(p); 168 169 if (opts.appJar != null) { 170 cmd.add("-cp"); 171 cmd.add(opts.appJar); 172 File jf = new File(opts.appJar); 173 if (DYNAMIC_DUMP && !jf.isDirectory()) { 174 patchJarForDynamicDump(opts.appJar); 175 } 176 } else { 177 cmd.add("-Djava.class.path="); 178 } 179 180 if (opts.archiveName == null) { 181 opts.archiveName = getCurrentArchiveName(); 182 } 183 184 if (DYNAMIC_DUMP) { 185 cmd.add("-Xshare:on"); 186 cmd.add("-XX:ArchiveClassesAtExit=" + opts.archiveName); 187 188 cmd.add("-Xlog:cds"); 189 cmd.add("-Xlog:cds+dynamic"); 190 boolean mainModuleSpecified = false; 191 boolean patchModuleSpecified = false; 192 for (String s : opts.suffix) { 193 if (s.length() == 0) { 194 continue; 195 } 196 if (s.equals("-m")) { 197 mainModuleSpecified = true; 198 } 199 if (s.startsWith("--patch-module=")) { 200 patchModuleSpecified = true; 201 } 202 cmd.add(s); 203 } 204 205 if (opts.appJar != null) { 206 // classlist is supported only when we have a Jar file to patch (to insert 207 // cdsutils.DynamicDumpHelper) 208 if (opts.classList == null) { 209 throw new RuntimeException("test.dynamic.dump requires classList file"); 210 } 211 212 if (!mainModuleSpecified && !patchModuleSpecified) { 213 cmd.add("cdsutils.DynamicDumpHelper"); 214 File classListFile = makeClassList(opts.classList); 215 cmd.add(classListFile.getPath()); 216 } 217 } else { 218 if (!mainModuleSpecified && !patchModuleSpecified) { 219 // If you have an empty classpath, you cannot specify a classlist! 220 if (opts.classList != null && opts.classList.length > 0) { 221 throw new RuntimeException("test.dynamic.dump is not supported with an empty classpath while the classlist is not empty"); 222 } 223 cmd.add("-version"); 224 } 225 } 226 } else { 227 // static dump 228 cmd.add("-Xshare:dump"); 229 cmd.add("-XX:SharedArchiveFile=" + opts.archiveName); 230 231 if (opts.classList != null) { 232 File classListFile = makeClassList(opts.classList); 233 cmd.add("-XX:ExtraSharedClassListFile=" + classListFile.getPath()); 234 } 235 for (String s : opts.suffix) { 236 cmd.add(s); 237 } 238 } 239 240 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 241 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 242 if (opts.appJarDir != null) { 243 pb.directory(new File(opts.appJarDir)); 244 } 245 246 OutputAnalyzer output = executeAndLog(pb, "dump"); 247 if (DYNAMIC_DUMP && isUnableToMap(output)) { 248 throw new SkippedException(UnableToMapMsg); 249 } 250 return output; 251 } 252 253 // This allows you to run the AppCDS tests with JFR enabled at runtime (though not at 254 // dump time, as that's uncommon for typical AppCDS users). 255 // 256 // To run in this special mode, add the following to your jtreg command-line 257 // -Dtest.cds.run.with.jfr=true 258 // 259 // Some AppCDS tests are not compatible with this mode. See the group 260 // hotspot_appcds_with_jfr in ../../TEST.ROOT for details. 261 private static final boolean RUN_WITH_JFR = Boolean.getBoolean("test.cds.run.with.jfr"); 262 // This method simulates -Xshare:dump with -XX:ArchiveClassesAtExit. This way, we 263 // can re-use many tests (outside of the ./dynamicArchive directory) for testing 264 // general features of JDK-8215311 (JEP 350: Dynamic CDS Archives). 265 // 266 // We insert the cdsutils/DynamicDumpHelper.class into the first Jar file in 267 // the classpath. We use this class to load all the classes specified in the classlist. 268 // 269 // There's no need to change the run-time command-line: in this special mode, two 270 // archives are involved. The command-line specifies only the top archive. However, 271 // the location of the base archive is recorded in the top archive, so it can be 272 // determined by the JVM at runtime start-up. 273 // 274 // To run in this special mode, specify the following in your jtreg command-line 275 // -Dtest.dynamic.cds.archive=true 276 // 277 // Note that some tests are not compatible with this special mode, including 278 // + Tests in ./dynamicArchive: these tests are specifically written for 279 // dynamic archive, and do not use TestCommon.createArchive(), which works 280 // together with patchJarForDynamicDump(). 281 // + Tests related to cached objects and shared strings: dynamic dumping 282 // does not support these. 283 // + Custom loader tests: DynamicDumpHelper doesn't support the required 284 // classlist syntax. (FIXME). 285 // + Extra symbols and extra strings. 286 // See the hotspot_appcds_dynamic in ../../TEST.ROOT for details. 287 // 288 // To run all tests that are compatible with this mode: 289 // cd test/hotspot/jtreg 290 // jtreg -Dtest.dynamic.cds.archive=true :hotspot_appcds_dynamic 291 // 292 private static void patchJarForDynamicDump(String cp) throws Exception { 293 System.out.println("patchJarForDynamicDump: classpath = " + cp); 294 String firstJar = cp; 295 int n = firstJar.indexOf(File.pathSeparator); 296 if (n > 0) { 297 firstJar = firstJar.substring(0, n); 298 } 299 String classDir = System.getProperty("test.classes"); 300 String expected1 = classDir + File.separator; 301 String expected2 = System.getProperty("user.dir") + File.separator; 302 303 if (!firstJar.startsWith(expected1) && !firstJar.startsWith(expected2)) { 304 throw new RuntimeException("FIXME: jar file not at a supported location ('" 305 + expected1 + "', or '" + expected2 + "'): " + firstJar); 306 } 307 308 String replaceJar = firstJar + ".tmp"; 309 String patchClass = "cdsutils/DynamicDumpHelper.class"; 310 ZipFile zipFile = new ZipFile(firstJar); 311 byte[] buf = new byte[1024]; 312 int len; 313 if (zipFile.getEntry(patchClass) == null) { 314 FileOutputStream fout = new FileOutputStream(replaceJar); 315 final ZipOutputStream zos = new ZipOutputStream(fout); 316 317 zos.putNextEntry(new ZipEntry(patchClass)); 318 InputStream is = new FileInputStream(classDir + File.separator + patchClass); 319 while ((len = (is.read(buf))) > 0) { 320 zos.write(buf, 0, len); 321 } 322 zos.closeEntry(); 323 is.close(); 324 325 for (Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { 326 ZipEntry entryIn = (ZipEntry) e.nextElement(); 327 zos.putNextEntry(entryIn); 328 is = zipFile.getInputStream(entryIn); 329 while ((len = is.read(buf)) > 0) { 330 zos.write(buf, 0, len); 331 } 332 zos.closeEntry(); 333 is.close(); 334 } 335 336 zos.close(); 337 fout.close(); 338 zipFile.close(); 339 340 File oldFile = new File(firstJar); 341 File newFile = new File(replaceJar); 342 oldFile.delete(); 343 newFile.renameTo(oldFile); 344 System.out.println("firstJar = " + firstJar + " Modified"); 345 } else { 346 zipFile.close(); 347 System.out.println("firstJar = " + firstJar); 348 } 349 } 350 351 // Execute JVM using AppCDS archive with specified AppCDSOptions 352 public static OutputAnalyzer runWithArchive(AppCDSOptions opts) 353 throws Exception { 354 355 ArrayList<String> cmd = new ArrayList<String>(); 356 357 for (String p : opts.prefix) cmd.add(p); 358 359 cmd.add("-Xshare:" + opts.xShareMode); 360 cmd.add("-showversion"); 361 cmd.add("-XX:SharedArchiveFile=" + getCurrentArchiveName()); 362 cmd.add("-Dtest.timeout.factor=" + timeoutFactor); 363 364 if (opts.appJar != null) { 365 cmd.add("-cp"); 366 cmd.add(opts.appJar); 367 } 368 369 for (String s : opts.suffix) cmd.add(s); 370 371 if (RUN_WITH_JFR) { 372 boolean usesJFR = false; 373 for (String s : cmd) { 374 if (s.startsWith("-XX:StartFlightRecording=") || s.startsWith("-XX:FlightRecorderOptions")) { 375 System.out.println("JFR option might have been specified. Don't interfere: " + s); 376 usesJFR = true; 377 break; 378 } 379 } 380 if (!usesJFR) { 381 System.out.println("JFR option not specified. Enabling JFR ..."); 382 cmd.add(0, "-XX:StartFlightRecording=dumponexit=true"); 383 System.out.println(cmd); 384 } 385 } 386 387 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 388 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 389 if (opts.appJarDir != null) { 390 pb.directory(new File(opts.appJarDir)); 391 } 392 return executeAndLog(pb, "exec"); 393 } 394 395 396 public static OutputAnalyzer execCommon(String... suffix) throws Exception { 397 AppCDSOptions opts = (new AppCDSOptions()); 398 opts.addSuffix(suffix); 399 return runWithArchive(opts); 400 } 401 402 // This is the new API for running a Java process with CDS enabled. 403 // See comments in the CDSTestUtils.Result class for how to use this method. 404 public static Result run(String... suffix) throws Exception { 405 AppCDSOptions opts = (new AppCDSOptions()); 406 opts.addSuffix(suffix); 407 return new Result(opts, runWithArchive(opts)); 408 } 409 410 public static Result runWithRelativePath(String jarDir, String... suffix) throws Exception { 411 AppCDSOptions opts = (new AppCDSOptions()); 412 opts.setAppJarDir(jarDir); 413 opts.addSuffix(suffix); 414 return new Result(opts, runWithArchive(opts)); 415 } 416 417 public static OutputAnalyzer exec(String appJar, String... suffix) throws Exception { 418 AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); 419 opts.addSuffix(suffix); 420 return runWithArchive(opts); 421 } 422 423 public static Result runWithModules(String prefix[], String upgrademodulepath, String modulepath, 424 String mid, String... testClassArgs) throws Exception { 425 AppCDSOptions opts = makeModuleOptions(prefix, upgrademodulepath, modulepath, 426 mid, testClassArgs); 427 return new Result(opts, runWithArchive(opts)); 428 } 429 430 public static OutputAnalyzer execAuto(String... suffix) throws Exception { 431 AppCDSOptions opts = (new AppCDSOptions()); 432 opts.addSuffix(suffix).setXShareMode("auto"); 433 return runWithArchive(opts); 434 } 435 436 public static OutputAnalyzer execOff(String... suffix) throws Exception { 437 AppCDSOptions opts = (new AppCDSOptions()); 438 opts.addSuffix(suffix).setXShareMode("off"); 439 return runWithArchive(opts); 440 } 441 442 443 private static AppCDSOptions makeModuleOptions(String prefix[], String upgrademodulepath, String modulepath, 444 String mid, String testClassArgs[]) { 445 AppCDSOptions opts = (new AppCDSOptions()); 446 447 opts.addPrefix(prefix); 448 if (upgrademodulepath == null) { 449 opts.addSuffix("-p", modulepath, "-m", mid); 450 } else { 451 opts.addSuffix("--upgrade-module-path", upgrademodulepath, 452 "-p", modulepath, "-m", mid); 453 } 454 opts.addSuffix(testClassArgs); 455 return opts; 456 } 457 458 public static OutputAnalyzer execModule(String prefix[], String upgrademodulepath, String modulepath, 459 String mid, String... testClassArgs) 460 throws Exception { 461 AppCDSOptions opts = makeModuleOptions(prefix, upgrademodulepath, modulepath, 462 mid, testClassArgs); 463 return runWithArchive(opts); 464 } 465 466 // A common operation: dump, then check results 467 public static OutputAnalyzer testDump(String appJar, String classList[], 468 String... suffix) throws Exception { 469 OutputAnalyzer output = dump(appJar, classList, suffix); 470 if (DYNAMIC_DUMP) { 471 output.shouldContain("Written dynamic archive"); 472 } else { 473 output.shouldContain("Loading classes to share"); 474 } 475 output.shouldHaveExitValue(0); 476 return output; 477 } 478 479 public static OutputAnalyzer testDump(String appJarDir, String appJar, String classList[], 480 String... suffix) throws Exception { 481 OutputAnalyzer output = dump(appJarDir, appJar, classList, suffix); 482 if (DYNAMIC_DUMP) { 483 output.shouldContain("Written dynamic archive"); 484 } else { 485 output.shouldContain("Loading classes to share"); 486 } 487 output.shouldHaveExitValue(0); 488 return output; 489 } 490 491 /** 492 * Simple test -- dump and execute appJar with the given classList in classlist. 493 */ 494 public static OutputAnalyzer test(String appJar, String classList[], String... args) 495 throws Exception { 496 testDump(appJar, classList); 497 498 OutputAnalyzer output = exec(appJar, args); 499 return checkExec(output); 500 } 501 502 503 public static OutputAnalyzer checkExecReturn(OutputAnalyzer output, int ret, 504 boolean checkContain, String... matches) throws Exception { 505 try { 506 for (String s : matches) { 507 if (checkContain) { 508 output.shouldContain(s); 509 } else { 510 output.shouldNotContain(s); 511 } 512 } 513 output.shouldHaveExitValue(ret); 514 } catch (Exception e) { 515 checkCommonExecExceptions(output, e); 516 } 517 518 return output; 519 } 520 521 // Convenience concatenation utils 522 public static String[] list(String ...args) { 523 return args; 524 } 525 526 527 public static String[] list(String arg, int count) { 528 ArrayList<String> stringList = new ArrayList<String>(); 529 for (int i = 0; i < count; i++) { 530 stringList.add(arg); 531 } 532 533 String outputArray[] = stringList.toArray(new String[stringList.size()]); 534 return outputArray; 535 } 536 537 538 public static String[] concat(String... args) { 539 return list(args); 540 } 541 542 543 public static String[] concat(String prefix[], String... extra) { 544 ArrayList<String> list = new ArrayList<String>(); 545 for (String s : prefix) { 546 list.add(s); 547 } 548 for (String s : extra) { 549 list.add(s); 550 } 551 552 return list.toArray(new String[list.size()]); 553 } 554 555 public static String[] concat(String prefix, String[] extra) { 556 ArrayList<String> list = new ArrayList<String>(); 557 list.add(prefix); 558 for (String s : extra) { 559 list.add(s); 560 } 561 562 return list.toArray(new String[list.size()]); 563 } 564 565 // ===================== Concatenate paths 566 public static String concatPaths(String... paths) { 567 String prefix = ""; 568 String s = ""; 569 for (String p : paths) { 570 s += prefix; 571 s += p; 572 prefix = File.pathSeparator; 573 } 574 return s; 575 } 576 577 578 public static String getTestJar(String jar) { 579 File jarFile = CDSTestUtils.getTestArtifact(jar, true); 580 if (!jarFile.isFile()) { 581 throw new RuntimeException("Not a regular file: " + jarFile.getPath()); 582 } 583 return jarFile.getPath(); 584 } 585 586 587 public static String getTestDir(String d) { 588 File dirFile = CDSTestUtils.getTestArtifact(d, true); 589 if (!dirFile.isDirectory()) { 590 throw new RuntimeException("Not a directory: " + dirFile.getPath()); 591 } 592 return dirFile.getPath(); 593 } 594 595 public static boolean checkOutputStrings(String outputString1, 596 String outputString2, 597 String split_regex) { 598 String[] sa1 = outputString1.split(split_regex); 599 String[] sa2 = outputString2.split(split_regex); 600 Arrays.sort(sa1); 601 Arrays.sort(sa2); 602 603 int i = 0; 604 for (String s : sa1) { 605 if (!s.equals(sa2[i])) { 606 throw new RuntimeException(s + " is different from " + sa2[i]); 607 } 608 i ++; 609 } 610 return true; 611 } 612 613 static Pattern pattern; 614 615 static void findAllClasses(ArrayList<String> list) throws Exception { 616 // Find all the classes in the jrt file system 617 pattern = Pattern.compile("/modules/[a-z.]*[a-z]+/([^-]*)[.]class"); 618 FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); 619 Path base = fs.getPath("/modules/"); 620 findAllClassesAtPath(base, list); 621 } 622 623 private static void findAllClassesAtPath(Path p, ArrayList<String> list) throws Exception { 624 try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) { 625 for (Path entry: stream) { 626 Matcher matcher = pattern.matcher(entry.toString()); 627 if (matcher.find()) { 628 String className = matcher.group(1); 629 list.add(className); 630 } 631 try { 632 findAllClassesAtPath(entry, list); 633 } catch (Exception ex) {} 634 } 635 } 636 } 637 638 public static String composeRelPath(String appJar) { 639 int idx = appJar.lastIndexOf(File.separator); 640 String jarName = appJar.substring(idx + 1); 641 String jarDir = appJar.substring(0, idx); 642 String lastDir = jarDir.substring(jarDir.lastIndexOf(File.separator)); 643 String relPath = jarDir + File.separator + ".." + File.separator + lastDir; 644 String newJar = relPath + File.separator + jarName; 645 return newJar; 646 } 647 648 649 public static File createSymLink(String appJar) throws Exception { 650 int idx = appJar.lastIndexOf(File.separator); 651 String jarName = appJar.substring(idx + 1); 652 String jarDir = appJar.substring(0, idx); 653 File origJar = new File(jarDir, jarName); 654 String linkedJarName = "linked_" + jarName; 655 File linkedJar = null; 656 if (!Platform.isWindows()) { 657 linkedJar = new File(jarDir, linkedJarName); 658 if (linkedJar.exists()) { 659 linkedJar.delete(); 660 } 661 Files.createSymbolicLink(linkedJar.toPath(), origJar.toPath()); 662 } 663 return linkedJar; 664 } 665 666 // Remove all UL log messages from a JVM's STDOUT (such as those printed by -Xlog:cds) 667 static Pattern logPattern = Pattern.compile("^\\[[0-9. ]*s\\].*"); 668 public static String filterOutLogs(String stdout) { 669 StringBuilder sb = new StringBuilder(); 670 String prefix = ""; 671 for (String line : stdout.split("\n")) { 672 if (logPattern.matcher(line).matches()) { 673 continue; 674 } 675 sb.append(prefix); 676 sb.append(line); 677 prefix = "\n"; 678 } 679 if (stdout.endsWith("\n")) { 680 // String.split("A\n") returns {"A"}, not {"A", ""}. 681 sb.append("\n"); 682 } 683 return sb.toString(); 684 } 685 }