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