/* * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/ package jdk.test.lib.cds;
// This class contains common test utilities for testing CDS publicclass CDSTestUtils { publicstaticfinal String MSG_RANGE_NOT_WITHIN_HEAP = "UseSharedSpaces: Unable to allocate region, range is not within java heap."; publicstaticfinal String MSG_RANGE_ALREADT_IN_USE = "Unable to allocate region, java heap range is already in use."; publicstaticfinal String MSG_DYNAMIC_NOT_SUPPORTED = "-XX:ArchiveClassesAtExit is unsupported when base CDS archive is not loaded"; publicstaticfinalboolean DYNAMIC_DUMP = Boolean.getBoolean("test.dynamic.cds.archive");
/* * INTRODUCTION * * When testing various CDS functionalities, we need to launch JVM processes * using a "launch method" (such as TestCommon.run), and analyze the results of these * processes. * * While typical jtreg tests would use OutputAnalyzer in such cases, due to the * complexity of CDS failure modes, we have added the CDSTestUtils.Result class * to make the analysis more convenient and less error prone. * * A Java process can end in one of the following 4 states: * * 1: Unexpected error - such as JVM crashing. In this case, the "launch method" * will throw a RuntimeException. * 2: Mapping Failure - this happens when the OS (intermittently) fails to map the * CDS archive, normally caused by Address Space Layout Randomization. * We usually treat this as "pass". * 3: Normal Exit - the JVM process has finished without crashing, and the exit code is 0. * 4: Abnormal Exit - the JVM process has finished without crashing, and the exit code is not 0. * * In most test cases, we need to check the JVM process's output in cases 3 and 4. However, we need * to make sure that our test code is not confused by case 2. * * For example, a JVM process is expected to print the string "Hi" and exit with 0. With the old * CDSTestUtils.runWithArchive API, the test may be written as this: * * OutputAnalyzer out = CDSTestUtils.runWithArchive(args); * out.shouldContain("Hi"); * * However, if the JVM process fails with mapping failure, the string "Hi" will not be in the output, * and your test case will fail intermittently. * * Instead, the test case should be written as * * CDSTestUtils.run(args).assertNormalExit("Hi"); * * EXAMPLES/HOWTO * * 1. For simple substring matching: * * CDSTestUtils.run(args).assertNormalExit("Hi"); * CDSTestUtils.run(args).assertNormalExit("a", "b", "x"); * CDSTestUtils.run(args).assertAbnormalExit("failure 1", "failure2"); * * 2. For more complex output matching: using Lambda expressions * * CDSTestUtils.run(args) * .assertNormalExit(output -> output.shouldNotContain("this should not be printed"); * CDSTestUtils.run(args) * .assertAbnormalExit(output -> { * output.shouldNotContain("this should not be printed"); * output.shouldHaveExitValue(123); * }); * * 3. Chaining several checks: * * CDSTestUtils.run(args) * .assertNormalExit(output -> output.shouldNotContain("this should not be printed") * .assertNormalExit("should have this", "should have that"); * * 4. [Rare use case] if a test sometimes exit normally, and sometimes abnormally: * * CDSTestUtils.run(args) * .ifNormalExit("ths string is printed when exiting with 0") * .ifAbNormalExit("ths string is printed when exiting with 1"); * * NOTE: you usually don't want to write your test case like this -- it should always * exit with the same exit code. (But I kept this API because some existing test cases * behave this way -- need to revisit).
*/ publicstaticclass Result { privatefinal OutputAnalyzer output; privatefinal CDSOptions options; privatefinalboolean hasNormalExit; privatefinal String CDS_DISABLED = "warning: CDS is disabled when the";
if (hasNormalExit) { if ("on".equals(options.xShareMode) &&
output.getStderr().contains("java version") &&
!output.getStderr().contains(CDS_DISABLED)) { // "-showversion" is always passed in the command-line by the execXXX methods. // During normal exit, we require that the VM to show that sharing was enabled.
output.shouldContain("sharing");
}
}
}
public Result assertNormalExit(Checker checker) throws Exception {
checker.check(output);
output.shouldHaveExitValue(0); returnthis;
}
public Result assertAbnormalExit(Checker checker) throws Exception {
checker.check(output);
output.shouldNotHaveExitValue(0); returnthis;
}
// When {--limit-modules, --patch-module, and/or --upgrade-module-path} // are specified, CDS is silently disabled for both -Xshare:auto and -Xshare:on. public Result assertSilentlyDisabledCDS(Checker checker) throws Exception { // this comes from a JVM warning message.
output.shouldContain(CDS_DISABLED);
checker.check(output); returnthis;
}
public Result assertSilentlyDisabledCDS(int exitCode, String... matches) throws Exception { return assertSilentlyDisabledCDS((out) -> {
out.shouldHaveExitValue(exitCode);
checkMatches(out, matches);
});
}
public Result ifNormalExit(Checker checker) throws Exception { if (hasNormalExit) {
checker.check(output);
} returnthis;
}
public Result ifAbnormalExit(Checker checker) throws Exception { if (!hasNormalExit) {
checker.check(output);
} returnthis;
}
public Result ifNoMappingFailure(Checker checker) throws Exception {
checker.check(output); returnthis;
}
public Result assertNormalExit(String... matches) throws Exception {
checkMatches(output, matches);
output.shouldHaveExitValue(0); returnthis;
}
public Result assertAbnormalExit(String... matches) throws Exception {
checkMatches(output, matches);
output.shouldNotHaveExitValue(0); returnthis;
}
}
// A number to be included in the filename of the stdout and the stderr output file. staticint logCounter = 0;
// By default, stdout of child processes are logged in files such as // <testname>-0000-exec.stdout. If you want to also include the stdout // inside jtr files, you can override this in the jtreg command line like // "jtreg -Dtest.cds.copy.child.stdout=true ...." publicstaticfinalboolean copyChildStdoutToMainStdout = Boolean.getBoolean("test.cds.copy.child.stdout");
// This property is passed to child test processes publicstaticfinal String TestTimeoutFactor = System.getProperty("test.timeout.factor", "1.0");
publicstaticfinal String UnableToMapMsg = "Unable to map shared archive: test did not complete";
// Create bootstrap CDS archive, // use extra JVM command line args as a prefix. // For CDS tests specifying prefix makes more sense than specifying suffix, since // normally there are no classes or arguments to classes, just "-version" // To specify suffix explicitly use CDSOptions.addSuffix() publicstatic OutputAnalyzer createArchive(String... cliPrefix) throws Exception { return createArchive((new CDSOptions()).addPrefix(cliPrefix));
}
// check result of 'dump-the-archive' operation, that is "-Xshare:dump" publicstatic OutputAnalyzer checkDump(OutputAnalyzer output, String... extraMatches) throws Exception {
if (!DYNAMIC_DUMP) {
output.shouldContain("Loading classes to share");
} else {
output.shouldContain("Written dynamic archive 0x");
}
output.shouldHaveExitValue(0);
for (String match : extraMatches) {
output.shouldContain(match);
}
return output;
}
// check result of dumping base archive publicstatic OutputAnalyzer checkBaseDump(OutputAnalyzer output) throws Exception {
output.shouldContain("Loading classes to share");
output.shouldHaveExitValue(0); return output;
}
// A commonly used convenience methods to create an archive and check the results // Creates an archive and checks for errors publicstatic OutputAnalyzer createArchiveAndCheck(CDSOptions opts) throws Exception { return checkDump(createArchive(opts));
}
// This method should be used to check the output of child VM for common exceptions. // Most of CDS tests deal with child VM processes for creating and using the archive. // However exceptions that occur in the child process do not automatically propagate // to the parent process. This mechanism aims to improve the propagation // of exceptions and common errors. // Exception e argument - an exception to be re-thrown if none of the common // exceptions match. Pass null if you wish not to re-throw any exception. publicstaticvoid checkCommonExecExceptions(OutputAnalyzer output, Exception e) throws Exception { if (output.getStdout().contains("https://bugreport.java.com/bugreport/crash.jsp")) { thrownew RuntimeException("Hotspot crashed");
} if (output.getStdout().contains("TEST FAILED")) { thrownew RuntimeException("Test Failed");
} if (output.getOutput().contains("Unable to unmap shared space")) { thrownew RuntimeException("Unable to unmap shared space");
}
// Special case -- sometimes Xshare:on fails because it failed to map // at given address. This behavior is platform-specific, machine config-specific // and can be random (see ASLR).
checkMappingFailure(output);
// Check the output for indication that mapping of the archive failed. // Performance note: this check seems to be rather costly - searching the entire // output stream of a child process for multiple strings. However, it is necessary // to detect this condition, a failure to map an archive, since this is not a real // failure of the test or VM operation, and results in a test being "skipped". // Suggestions to improve: // 1. VM can designate a special exit code for such condition. // 2. VM can print a single distinct string indicating failure to map an archive, // instead of utilizing multiple messages. // These are suggestions to improve testibility of the VM. However, implementing them // could also improve usability in the field. privatestatic String hasUnableToMapMessage(OutputAnalyzer output) {
String outStr = output.getOutput(); if ((output.getExitValue() == 1)) { if (outStr.contains(MSG_RANGE_NOT_WITHIN_HEAP)) { return MSG_RANGE_NOT_WITHIN_HEAP;
} if (outStr.contains(MSG_DYNAMIC_NOT_SUPPORTED)) { return MSG_DYNAMIC_NOT_SUPPORTED;
}
}
// Dump a classlist using the -XX:DumpLoadedClassList option. publicstatic Result dumpClassList(String classListName, String... cli) throws Exception {
CDSOptions opts = (new CDSOptions())
.setUseVersion(false)
.setXShareMode("auto")
.addPrefix("-XX:DumpLoadedClassList=" + classListName)
.addSuffix(cli);
Result res = run(opts).assertNormalExit(); return res;
}
// Execute JVM with CDS archive, specify command line args suffix publicstatic OutputAnalyzer runWithArchive(String... cliPrefix) throws Exception {
return runWithArchive( (new CDSOptions())
.setArchiveName(getDefaultArchiveName())
.addPrefix(cliPrefix) );
}
// Enable basic verification (VerifyArchivedFields=1, no side effects) for all CDS // tests to make sure the archived heap objects are mapped/loaded properly. publicstaticvoid addVerifyArchivedFields(ArrayList<String> cmd) {
cmd.add("-XX:+UnlockDiagnosticVMOptions");
cmd.add("-XX:VerifyArchivedFields=1");
}
// A commonly used convenience methods to create an archive and check the results // Creates an archive and checks for errors publicstatic OutputAnalyzer runWithArchiveAndCheck(CDSOptions opts) throws Exception { return checkExec(runWithArchive(opts));
}
// check result of 'exec' operation, that is when JVM is run using the archive publicstatic OutputAnalyzer checkExec(OutputAnalyzer output, CDSOptions opts,
String... extraMatches) throws Exception { try { if ("on".equals(opts.xShareMode)) {
output.shouldContain("sharing");
}
output.shouldHaveExitValue(0);
} catch (RuntimeException e) {
checkCommonExecExceptions(output, e); return output;
}
// get the file object for the test artifact publicstatic File getTestArtifact(String name, boolean checkExistence) {
File file = new File(outputDirAsFile, name);
privatestaticfinal SimpleDateFormat timeStampFormat = new SimpleDateFormat("HH'h'mm'm'ss's'SSS");
privatestatic String defaultArchiveName;
// Call this method to start new archive with new unique name publicstaticvoid startNewArchiveName() {
defaultArchiveName = testName +
timeStampFormat.format(new Date()) + ".jsa";
}
// Format a line that defines an extra symbol in the file specify by -XX:SharedArchiveConfigFile=<file> publicstatic String formatArchiveConfigSymbol(String symbol) { int refCount = -1; // This is always -1 in the current HotSpot implementation. if (isAsciiPrintable(symbol)) { return symbol.length() + " " + refCount + ": " + symbol;
} else {
StringBuilder sb = new StringBuilder(); int utf8_length = escapeArchiveConfigString(sb, symbol); return utf8_length + " " + refCount + ": " + sb.toString();
}
}
// This method generates the same format as HashtableTextDump::put_utf8() in HotSpot, // to be used by -XX:SharedArchiveConfigFile=<file>. privatestaticint escapeArchiveConfigString(StringBuilder sb, String s) { byte arr[]; try {
arr = s.getBytes("UTF8");
} catch (java.io.UnsupportedEncodingException e) { thrownew RuntimeException("Unexpected", e);
} for (int i = 0; i < arr.length; i++) { char ch = (char)(arr[i] & 0xff); if (isAsciiPrintable(ch)) {
sb.append(ch);
} elseif (ch == '\t') {
sb.append("\\t");
} elseif (ch == '\r') {
sb.append("\\r");
} elseif (ch == '\n') {
sb.append("\\n");
} elseif (ch == '\\') {
sb.append("\\\\");
} else {
String hex = Integer.toHexString(ch); if (ch < 16) {
sb.append("\\x0");
} else {
sb.append("\\x");
}
sb.append(hex);
}
}
return arr.length;
}
privatestaticboolean isAsciiPrintable(String s) { for (int i = 0; i < s.length(); i++) { if (!isAsciiPrintable(s.charAt(i))) { returnfalse;
}
} returntrue;
}
// Do a cheap clone of the JDK. Most files can be sym-linked. However, $JAVA_HOME/bin/java and $JAVA_HOME/lib/.../libjvm.so" // must be copied, because the java.home property is derived from the canonicalized paths of these 2 files. // Set a list of {jvm, "java"} which will be physically copied. If a file needs copied physically, add it to the list. privatestatic String[] phCopied = {System.mapLibraryName("jvm"), "java"}; publicstaticvoid clone(File src, File dst) throws Exception { if (dst.exists()) { if (!dst.isDirectory()) { thrownew RuntimeException("Not a directory :" + dst);
}
} else { if (!dst.mkdir()) { thrownew RuntimeException("Cannot create directory: " + dst);
}
} // final String jvmLib = System.mapLibraryName("jvm"); for (String child : src.list()) { if (child.equals(".") || child.equals("..")) { continue;
}
File child_src = new File(src, child);
File child_dst = new File(dst, child); if (child_dst.exists()) { thrownew RuntimeException("Already exists: " + child_dst);
} if (child_src.isFile()) { boolean needPhCopy = false; for (String target : phCopied) { if (child.equals(target)) {
needPhCopy = true; break;
}
} if (needPhCopy) {
Files.copy(child_src.toPath(), /* copy data to -> */ child_dst.toPath(), new CopyOption[] { StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES});
} else {
Files.createSymbolicLink(child_dst.toPath(), /* link to -> */ child_src.toPath());
}
} else {
clone(child_src, child_dst);
}
}
}
// modulesDir, like $JDK/lib // oldName, module name under modulesDir // newName, new name for oldName publicstaticvoid rename(File fromFile, File toFile) throws Exception { if (!fromFile.exists()) { thrownew RuntimeException(fromFile.getName() + " does not exist");
}
if (toFile.exists()) { thrownew RuntimeException(toFile.getName() + " already exists");
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.