1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.test.lib.cds;
  24 
  25 import java.io.IOException;
  26 import java.io.File;
  27 import java.io.FileOutputStream;
  28 import java.io.PrintStream;
  29 import java.util.ArrayList;
  30 import jdk.test.lib.Utils;
  31 import jdk.test.lib.process.OutputAnalyzer;
  32 import jdk.test.lib.process.ProcessTools;
  33 
  34 
  35 // This class contains common test utilities for testing CDS
  36 public class CDSTestUtils {
  37     // Specify this property to copy sdandard output of the child test process to
  38     // the parent/main stdout of the test.
  39     // By default such output is logged into a file, but not copied into the main stdout
  40     // to avoid excessive log pollution. See executeAndLog() for details
  41     public static final boolean CopyChildStdoutToMainStdout =
  42         Boolean.getBoolean("test.cds.copy.child.stdout");
  43 
  44     // This property is passed to child test processes
  45     public static final String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0");
  46 
  47     public static final String UnableToMapMsg =
  48         "Unable to map shared archive: test did not complete; assumed PASS";
  49 
  50     // Create bootstrap CDS archive,
  51     // use extra JVM command line args as a prefix.
  52     // For CDS tests specifying prefix makes more sense than specifying suffix, since
  53     // normally there are no classes or arguments to classes, just "-version"
  54     // To specify suffix explicitly use CDSOptions.addSuffix()
  55     public static OutputAnalyzer createArchive(String... cliPrefix)
  56         throws Exception {
  57         return createArchive((new CDSOptions()).addPrefix(cliPrefix));
  58     }
  59 
  60     // Create bootstrap CDS archive
  61     public static OutputAnalyzer createArchive(CDSOptions opts)
  62         throws Exception {
  63 
  64         ArrayList<String> cmd = new ArrayList<String>();
  65 
  66         for (String p : opts.prefix) cmd.add(p);
  67 
  68         cmd.add("-Xshare:dump");
  69         cmd.add("-XX:+PrintSharedSpaces");
  70         cmd.add("-XX:+UnlockDiagnosticVMOptions");
  71         if (opts.archiveName == null)
  72             opts.archiveName = getDefaultArchiveName();
  73         cmd.add("-XX:SharedArchiveFile=./" + opts.archiveName);
  74 
  75         for (String s : opts.suffix) cmd.add(s);
  76 
  77         String[] cmdLine = cmd.toArray(new String[cmd.size()]);
  78         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine);
  79         return executeAndLog(pb, "dump");
  80     }
  81 
  82 
  83     // check result of 'dump-the-archive' operation, that is "-Xshare:dump"
  84     public static OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches)
  85         throws Exception {
  86 
  87         output.shouldContain("Loading classes to share");
  88         output.shouldHaveExitValue(0);
  89 
  90         for (String match : extraMatches) {
  91             output.shouldContain(match);
  92         }
  93 
  94         return output;
  95     }
  96 
  97 
  98     // A commonly used convenience methods to create an archive and check the results
  99     // Creates an archive and checks for errors
 100     public static OutputAnalyzer createArchiveAndCheck(CDSOptions opts)
 101         throws Exception {
 102         return checkDump(createArchive(opts));
 103     }
 104 
 105 
 106     public static OutputAnalyzer createArchiveAndCheck(String... cliPrefix)
 107         throws Exception {
 108         return checkDump(createArchive(cliPrefix));
 109     }
 110 
 111 
 112     // This method should be used to check the output of child VM for common exceptions.
 113     // Most of CDS tests deal with child VM processes for creating and using the archive.
 114     // However exceptions that occur in the child process do not automatically propagate
 115     // to the parent process. This mechanism aims to improve the propagation
 116     // of exceptions and common errors.
 117     // Exception e argument - an exception to be re-thrown if none of the common
 118     // exceptions match. Pass null if you wish not to re-throw any exception.
 119     public static void checkCommonExecExceptions(OutputAnalyzer output, Exception e)
 120         throws Exception {
 121         if (output.getStdout().contains("http://bugreport.java.com/bugreport/crash.jsp")) {
 122             throw new RuntimeException("Hotspot crashed");
 123         }
 124         if (output.getStdout().contains("TEST FAILED")) {
 125             throw new RuntimeException("Test Failed");
 126         }
 127         if (output.getOutput().contains("shared class paths mismatch")) {
 128             throw new RuntimeException("shared class paths mismatch");
 129         }
 130         if (output.getOutput().contains("Unable to unmap shared space")) {
 131             throw new RuntimeException("Unable to unmap shared space");
 132         }
 133 
 134         // Special case -- sometimes Xshare:on fails because it failed to map
 135         // at given address. This behavior is platform-specific, machine config-specific
 136         // and can be random (see ASLR).
 137         if (isUnableToMap(output)) {
 138             System.out.println(UnableToMapMsg);
 139             return;
 140         }
 141 
 142         if (e != null)
 143             throw e;
 144     }
 145 
 146 
 147     // Check the output for indication that mapping of the archive failed.
 148     // Performance note: this check seems to be rather costly - searching the entire
 149     // output stream of a child process for multiple strings. However, it is necessary
 150     // to detect this condition, a failure to map an archive, since this is not a real
 151     // failure of the test or VM operation, and results in a test being "skipped".
 152     // Suggestions to improve:
 153     // 1. VM can designate a special exit code for such condition.
 154     // 2. VM can print a single distinct string indicating failure to map an archive,
 155     //    instead of utilizing multiple messages.
 156     // These are suggestions to improve testibility of the VM. However, implementing them
 157     // could also improve usability in the field.
 158     public static boolean isUnableToMap(OutputAnalyzer output) {
 159         String outStr = output.getOutput();
 160         if ((output.getExitValue() == 1) && (
 161             outStr.contains("Unable to reserve shared space at required address") ||
 162             outStr.contains("Unable to map ReadOnly shared space at required address") ||
 163             outStr.contains("Unable to map ReadWrite shared space at required address") ||
 164             outStr.contains("Unable to map MiscData shared space at required address") ||
 165             outStr.contains("Unable to map MiscCode shared space at required address") ||
 166             outStr.contains("Unable to map shared string space at required address") ||
 167             outStr.contains("Could not allocate metaspace at a compatible address") ||
 168             outStr.contains("Unable to allocate shared string space: range is not within java heap") ))
 169         {
 170             return true;
 171         }
 172 
 173         return false;
 174     }
 175 
 176 
 177     // Execute JVM with CDS archive, specify command line args suffix
 178     public static OutputAnalyzer runWithArchive(String... cliPrefix)
 179         throws Exception {
 180 
 181         return runWithArchive( (new CDSOptions())
 182                                .setArchiveName(getDefaultArchiveName())
 183                                .addPrefix(cliPrefix) );
 184     }
 185 
 186 
 187     // Execute JVM with CDS archive, specify CDSOptions
 188     public static OutputAnalyzer runWithArchive(CDSOptions opts)
 189         throws Exception {
 190 
 191         ArrayList<String> cmd = new ArrayList<String>();
 192 
 193         for (String p : opts.prefix) cmd.add(p);
 194 
 195         cmd.add("-Xshare:" + opts.xShareMode);
 196         cmd.add("-XX:+UnlockDiagnosticVMOptions");
 197         cmd.add("-Dtest.timeout.factor=" + TestTimeoutFactor);
 198 
 199         if (opts.archiveName == null)
 200             opts.archiveName = getDefaultArchiveName();
 201         cmd.add("-XX:SharedArchiveFile=" + opts.archiveName);
 202 
 203         if (opts.useVersion)
 204             cmd.add("-version");
 205 
 206         for (String s : opts.suffix) cmd.add(s);
 207 
 208         String[] cmdLine = cmd.toArray(new String[cmd.size()]);
 209         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(true, cmdLine);
 210         return executeAndLog(pb, "exec");
 211     }
 212 
 213 
 214     // A commonly used convenience methods to create an archive and check the results
 215     // Creates an archive and checks for errors
 216     public static OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception {
 217         return checkExec(runWithArchive(opts));
 218     }
 219 
 220 
 221     public static OutputAnalyzer runWithArchiveAndCheck(String... cliPrefix) throws Exception {
 222         return checkExec(runWithArchive(cliPrefix));
 223     }
 224 
 225 
 226     public static OutputAnalyzer checkExec(OutputAnalyzer output,
 227                                      String... extraMatches) throws Exception {
 228         CDSOptions opts = new CDSOptions();
 229         return checkExec(output, opts, extraMatches);
 230     }
 231 
 232 
 233     // check result of 'exec' operation, that is when JVM is run using the archive
 234     public static OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts,
 235                                      String... extraMatches) throws Exception {
 236         try {
 237             if ("on".equals(opts.xShareMode)) {
 238                 output.shouldContain("sharing");
 239             }
 240             output.shouldHaveExitValue(0);
 241         } catch (RuntimeException e) {
 242             checkCommonExecExceptions(output, e);
 243             return output;
 244         }
 245 
 246         checkExtraMatches(output, extraMatches);
 247         return output;
 248     }
 249 
 250 
 251     public static OutputAnalyzer checkExecExpectError(OutputAnalyzer output,
 252                                              int expectedExitValue,
 253                                              String... extraMatches) throws Exception {
 254         if (isUnableToMap(output)) {
 255             System.out.println(UnableToMapMsg);
 256             return output;
 257         }
 258 
 259         output.shouldHaveExitValue(expectedExitValue);
 260         checkExtraMatches(output, extraMatches);
 261         return output;
 262     }
 263 
 264     public static OutputAnalyzer checkExtraMatches(OutputAnalyzer output,
 265                                                     String... extraMatches) throws Exception {
 266         for (String match : extraMatches) {
 267             output.shouldContain(match);
 268         }
 269         return output;
 270     }
 271 
 272 
 273     // get the file object for the test artifact
 274     public static File getTestArtifact(String name, boolean checkExistence) {
 275         File dir = new File(System.getProperty("test.classes", "."));
 276         File file = new File(dir, name);
 277 
 278         if (checkExistence && !file.exists()) {
 279             throw new RuntimeException("Cannot find " + file.getPath());
 280         }
 281 
 282         return file;
 283     }
 284 
 285 
 286     // create file containing the specified class list
 287     public static File makeClassList(String classes[])
 288         throws Exception {
 289         return makeClassList(getTestName() + "-", classes);
 290     }
 291 
 292     // create file containing the specified class list
 293     public static File makeClassList(String testCaseName, String classes[])
 294         throws Exception {
 295 
 296         File classList = getTestArtifact(testCaseName + "test.classlist", false);
 297         FileOutputStream fos = new FileOutputStream(classList);
 298         PrintStream ps = new PrintStream(fos);
 299 
 300         addToClassList(ps, classes);
 301 
 302         ps.close();
 303         fos.close();
 304 
 305         return classList;
 306     }
 307 
 308 
 309     public static void addToClassList(PrintStream ps, String classes[])
 310         throws IOException
 311     {
 312         if (classes != null) {
 313             for (String s : classes) {
 314                 ps.println(s);
 315             }
 316         }
 317     }
 318 
 319 
 320     // Optimization for getting a test name.
 321     // Test name does not change during execution of the test,
 322     // but getTestName() uses stack walking hence it is expensive.
 323     // Therefore cache it and reuse it.
 324     private static String testName;
 325     public static String getTestName() {
 326         if (testName == null) {
 327             testName = Utils.getTestName();
 328         }
 329         return testName;
 330     }
 331 
 332 
 333     public static String getDefaultArchiveName() {
 334         return getTestName() + ".jsa";
 335     }
 336 
 337 
 338     // ===================== FILE ACCESS convinience methods
 339     public static File getOutputFile(String name) {
 340         File dir = new File(System.getProperty("test.classes", "."));
 341         return new File(dir, getTestName() + "-" + name);
 342     }
 343 
 344 
 345     public static File getOutputSourceFile(String name) {
 346         File dir = new File(System.getProperty("test.classes", "."));
 347         return new File(dir, name);
 348     }
 349 
 350 
 351     public static File getSourceFile(String name) {
 352         File dir = new File(System.getProperty("test.src", "."));
 353         return new File(dir, name);
 354     }
 355 
 356 
 357     // ============================= Logging
 358     public static OutputAnalyzer executeAndLog(ProcessBuilder pb, String logName) throws Exception {
 359         long started = System.currentTimeMillis();
 360         OutputAnalyzer output = new OutputAnalyzer(pb.start());
 361 
 362         writeFile(getOutputFile(logName + ".stdout"), output.getStdout());
 363         writeFile(getOutputFile(logName + ".stderr"), output.getStderr());
 364         System.out.println("[ELAPSED: " + (System.currentTimeMillis() - started) + " ms]");
 365         System.out.println("[STDERR]\n" + output.getStderr());
 366 
 367         if (CopyChildStdoutToMainStdout)
 368             System.out.println("[STDOUT]\n" + output.getStdout());
 369 
 370         return output;
 371     }
 372 
 373 
 374     private static void writeFile(File file, String content) throws Exception {
 375         FileOutputStream fos = new FileOutputStream(file);
 376         PrintStream ps = new PrintStream(fos);
 377         ps.print(content);
 378         ps.close();
 379         fos.close();
 380     }
 381 }