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