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 not supported empty classpath with non-empty classlist"); 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 return executeAndLog(pb, "dump"); 246 } 247 248 // This allows you to run the AppCDS tests with JFR enabled at runtime (though not at 249 // dump time, as that's uncommon for typical AppCDS users). 250 // 251 // To run in this special mode, add the following to your jtreg command-line 252 // -Dtest.cds.run.with.jfr=true 253 // 254 // Some AppCDS tests are not compatible with this mode. See the group 255 // hotspot_appcds_with_jfr in ../../TEST.ROOT for details. 256 private static final boolean RUN_WITH_JFR = Boolean.getBoolean("test.cds.run.with.jfr"); 257 // This method simulates -Xshare:dump with -XX:ArchiveClassesAtExit. This way, we 258 // can re-use many tests (outside of the ./dynamicArchive directory) for testing 259 // general features of JDK-8215311 (JEP 350: Dynamic CDS Archives). 260 // 261 // We insert the cdsutils/DynamicDumpHelper.class into the first Jar file in 262 // the classpath. We use this class to load all the classes specified in the classlist. 263 // 264 // There's no need to change the run-time command-line: in this special mode, two 265 // archives are involved. The command-line specifies only the top archive. However, 266 // the location of the base archive is recorded in the top archive, so it can be 267 // determined by the JVM at runtime start-up. 268 // 269 // To run in this special mode, specify the following in your jtreg command-line 270 // -Dtest.dynamic.cds.archive=true 271 // 272 // Note that some tests are not compatible with this special mode, including 273 // + Tests in ./dynamicArchive: these tests are specifically written for 274 // dynamic archive, and do not use TestCommon.createArchive(), which works 275 // together with patchJarForDynamicDump(). 276 // + Tests related to cached objects and shared strings: dynamic dumping 277 // does not support these. 278 // + Custom loader tests: DynamicDumpHelper doesn't support the required 279 // classlist syntax. (FIXME). 280 // + Extra symbols and extra strings. 281 // See the hotspot_appcds_dynamic in ../../TEST.ROOT for details. 282 // 283 // To run all tests that are compatible with this mode: 284 // cd test/hotspot/jtreg 285 // jtreg -Dtest.dynamic.cds.archive=true :hotspot_appcds_dynamic 286 // 287 private static void patchJarForDynamicDump(String cp) throws Exception { 288 System.out.println("patchJarForDynamicDump: classpath = " + cp); 289 String firstJar = cp; 290 int n = firstJar.indexOf(File.pathSeparator); 291 if (n > 0) { 292 firstJar = firstJar.substring(0, n); 293 } 294 String classDir = System.getProperty("test.classes"); 295 String expected1 = classDir + File.separator; 296 String expected2 = System.getProperty("user.dir") + File.separator; 297 298 if (!firstJar.startsWith(expected1) && !firstJar.startsWith(expected2)) { 299 throw new RuntimeException("FIXME: jar file not at a supported location ('" 300 + expected1 + "', or '" + expected2 + "'): " + firstJar); 301 } 302 303 String replaceJar = firstJar + ".tmp"; 304 String patchClass = "cdsutils/DynamicDumpHelper.class"; 305 ZipFile zipFile = new ZipFile(firstJar); 306 byte[] buf = new byte[1024]; 307 int len; 308 if (zipFile.getEntry(patchClass) == null) { 309 FileOutputStream fout = new FileOutputStream(replaceJar); 310 final ZipOutputStream zos = new ZipOutputStream(fout); 311 312 zos.putNextEntry(new ZipEntry(patchClass)); 313 InputStream is = new FileInputStream(classDir + File.separator + patchClass); 314 while ((len = (is.read(buf))) > 0) { 315 zos.write(buf, 0, len); 316 } 317 zos.closeEntry(); 318 is.close(); 319 320 for (Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { 321 ZipEntry entryIn = (ZipEntry) e.nextElement(); 322 zos.putNextEntry(entryIn); 323 is = zipFile.getInputStream(entryIn); 324 while ((len = is.read(buf)) > 0) { 325 zos.write(buf, 0, len); 326 } 327 zos.closeEntry(); 328 is.close(); 329 } 330 331 zos.close(); 332 fout.close(); 333 zipFile.close(); 334 335 File oldFile = new File(firstJar); 336 File newFile = new File(replaceJar); 337 oldFile.delete(); 338 newFile.renameTo(oldFile); 339 System.out.println("firstJar = " + firstJar + " Modified"); 340 } else { 341 System.out.println("firstJar = " + firstJar); 342 } 343 } 344 345 // Execute JVM using AppCDS archive with specified AppCDSOptions 346 public static OutputAnalyzer runWithArchive(AppCDSOptions opts) 347 throws Exception { 348 349 ArrayList<String> cmd = new ArrayList<String>(); 350 351 for (String p : opts.prefix) cmd.add(p); 352 353 cmd.add("-Xshare:" + opts.xShareMode); 354 cmd.add("-showversion"); 355 cmd.add("-XX:SharedArchiveFile=" + getCurrentArchiveName()); 356 cmd.add("-Dtest.timeout.factor=" + timeoutFactor); 357 358 if (opts.appJar != null) { 359 cmd.add("-cp"); 360 cmd.add(opts.appJar); 361 } 362 363 for (String s : opts.suffix) cmd.add(s); 364 365 if (RUN_WITH_JFR) { 366 boolean usesJFR = false; 367 for (String s : cmd) { 368 if (s.startsWith("-XX:StartFlightRecording=") || s.startsWith("-XX:FlightRecorderOptions")) { 369 System.out.println("JFR option might have been specified. Don't interfere: " + s); 370 usesJFR = true; 371 break; 372 } 373 } 374 if (!usesJFR) { 375 System.out.println("JFR option not specified. Enabling JFR ..."); 376 cmd.add(0, "-XX:StartFlightRecording=dumponexit=true"); 377 System.out.println(cmd); 378 } 379 } 380 381 String[] cmdLine = cmd.toArray(new String[cmd.size()]); 382 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine); 383 if (opts.appJarDir != null) { 384 pb.directory(new File(opts.appJarDir)); 385 } 386 return executeAndLog(pb, "exec"); 387 } 388 389 390 public static OutputAnalyzer execCommon(String... suffix) throws Exception { 391 AppCDSOptions opts = (new AppCDSOptions()); 392 opts.addSuffix(suffix); 393 return runWithArchive(opts); 394 } 395 396 // This is the new API for running a Java process with CDS enabled. 397 // See comments in the CDSTestUtils.Result class for how to use this method. 398 public static Result run(String... suffix) throws Exception { 399 AppCDSOptions opts = (new AppCDSOptions()); 400 opts.addSuffix(suffix); 401 return new Result(opts, runWithArchive(opts)); 402 } 403 404 public static Result runWithRelativePath(String jarDir, String... suffix) throws Exception { 405 AppCDSOptions opts = (new AppCDSOptions()); 406 opts.setAppJarDir(jarDir); 407 opts.addSuffix(suffix); 408 return new Result(opts, runWithArchive(opts)); 409 } 410 411 public static OutputAnalyzer exec(String appJar, String... suffix) throws Exception { 412 AppCDSOptions opts = (new AppCDSOptions()).setAppJar(appJar); 413 opts.addSuffix(suffix); 414 return runWithArchive(opts); 415 } 416 417 public static Result runWithModules(String prefix[], String upgrademodulepath, String modulepath, 418 String mid, String... testClassArgs) throws Exception { 419 AppCDSOptions opts = makeModuleOptions(prefix, upgrademodulepath, modulepath, 420 mid, testClassArgs); 421 return new Result(opts, runWithArchive(opts)); 422 } 423 424 public static OutputAnalyzer execAuto(String... suffix) throws Exception { 425 AppCDSOptions opts = (new AppCDSOptions()); 426 opts.addSuffix(suffix).setXShareMode("auto"); 427 return runWithArchive(opts); 428 } 429 430 public static OutputAnalyzer execOff(String... suffix) throws Exception { 431 AppCDSOptions opts = (new AppCDSOptions()); 432 opts.addSuffix(suffix).setXShareMode("off"); 433 return runWithArchive(opts); 434 } 435 436 437 private static AppCDSOptions makeModuleOptions(String prefix[], String upgrademodulepath, String modulepath, 438 String mid, String testClassArgs[]) { 439 AppCDSOptions opts = (new AppCDSOptions()); 440 441 opts.addPrefix(prefix); 442 if (upgrademodulepath == null) { 443 opts.addSuffix("-p", modulepath, "-m", mid); 444 } else { 445 opts.addSuffix("--upgrade-module-path", upgrademodulepath, 446 "-p", modulepath, "-m", mid); 447 } 448 opts.addSuffix(testClassArgs); 449 return opts; 450 } 451 452 public static OutputAnalyzer execModule(String prefix[], String upgrademodulepath, String modulepath, 453 String mid, String... testClassArgs) 454 throws Exception { 455 AppCDSOptions opts = makeModuleOptions(prefix, upgrademodulepath, modulepath, 456 mid, testClassArgs); 457 return runWithArchive(opts); 458 } 459 460 // A common operation: dump, then check results 461 public static OutputAnalyzer testDump(String appJar, String classList[], 462 String... suffix) throws Exception { 463 OutputAnalyzer output = dump(appJar, classList, suffix); 464 if (DYNAMIC_DUMP) { 465 if (isUnableToMap(output)) { 466 throw new SkippedException(UnableToMapMsg); 467 } 468 output.shouldContain("Written dynamic archive"); 469 } else { 470 output.shouldContain("Loading classes to share"); 471 } 472 output.shouldHaveExitValue(0); 473 return output; 474 } 475 476 public static OutputAnalyzer testDump(String appJarDir, String appJar, String classList[], 477 String... suffix) throws Exception { 478 OutputAnalyzer output = dump(appJarDir, appJar, classList, suffix); 479 if (DYNAMIC_DUMP) { 480 if (isUnableToMap(output)) { 481 throw new SkippedException(UnableToMapMsg); 482 } 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 Throwable { 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 Throwable { 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 (Throwable t) {} 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 }