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