a81e1bf1e1
Reviewed-by: cjplummer
504 lines
20 KiB
Java
504 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2014, 2024, 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 vm.mlvm.share;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.lang.management.ManagementFactory;
|
|
import java.lang.reflect.Constructor;
|
|
import java.util.List;
|
|
|
|
import com.sun.management.HotSpotDiagnosticMXBean;
|
|
|
|
import nsk.share.Consts;
|
|
import nsk.share.ArgumentParser;
|
|
import vm.share.options.IgnoreUnknownArgumentsHandler;
|
|
import vm.share.options.OptionSupport;
|
|
|
|
/**
|
|
* This class executes a test based on (a subclass of) either:
|
|
* <ul>
|
|
* <li>{@link vm.mlvm.share.MlvmTest}
|
|
* <li>{@link java.lang.Runnable}
|
|
* </ul>
|
|
* using a number of launch() methods.
|
|
*
|
|
* Command-line parameters are parsed and set to the instance fields of the test marked with {@literal@}Option/Options annotations. See {@link vm.share.options} framework for details.
|
|
*
|
|
* Arguments for test constructor can be passed as well.
|
|
*
|
|
* Additionally the launch() methods:
|
|
* <ul>
|
|
* <li>measures test run time
|
|
* <li> handles all test status passing methods (via boolean return value, via MlvmTest.markTestFailed() calls, exception thrown from overriden run() method)
|
|
* <li>optionally dumps heap after test execution if MlvmTest.setHeapDumpAfer(true) was called
|
|
* </ul>
|
|
*
|
|
* @see vm.mlvm.share.MlvmTest
|
|
* @see vm.share.options
|
|
*
|
|
*/
|
|
public class MlvmTestExecutor {
|
|
|
|
/**
|
|
* The heap dump file name. If you call MlvmTest.setHeapDumpAfter(true), the heap is dumped into file
|
|
* specified by this constant when test finishes
|
|
*/
|
|
public static final String HEAP_DUMP_FILENAME = "heap.dump";
|
|
|
|
/**
|
|
* Launches MLVM test.
|
|
* Please see documentation for {@link #launch(Class<?> testClass, Object[] constructorArgs)} method.
|
|
*
|
|
* This version of the method is a syntactic shugar to launch test in this way:
|
|
*
|
|
* <code>
|
|
* public class MyTest extends MlvmTest {
|
|
* ...
|
|
* public static void main(String[] args) {
|
|
* MlvmTestExecutor.launch(new YourCustomArgumentParser(args));
|
|
* }
|
|
* }
|
|
* </code>
|
|
*
|
|
* @param args Command-line arguments, which are taken from supplied ArgumentParser (ArgumentParser is needed for compatibility with old tests)
|
|
and set to appropriate test instance fields using vm.share.options framework
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(ArgumentParser argumentParser) {
|
|
launch(argumentParser, null);
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test.
|
|
* Please see documentation for {@link #launch(Class<?> testClass, Object[] constructorArgs)} method.
|
|
*
|
|
* This version of the method is a syntactic shugar to launch test in this way:
|
|
*
|
|
* <code>
|
|
* public class MyTest extends MlvmTest {
|
|
* ...
|
|
* public static void main(String[] args) {
|
|
* MlvmTestExecutor.launch(new YourCustomArgumentParser(args), new Object[] { constructor-arguments... });
|
|
* }
|
|
* }
|
|
* </code>
|
|
*
|
|
* @param args Command-line arguments, which are taken from supplied ArgumentParser (ArgumentParser is needed for compatibility with old tests)
|
|
and set to appropriate test instance fields using vm.share.options framework
|
|
* @param constructorArgs Arguments, which are parsed to test constructor
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(ArgumentParser argumentParser, Object[] constructorArgs) {
|
|
Env.init(argumentParser);
|
|
launch(constructorArgs);
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test.
|
|
* Please see documentation for {@link #launch(Class<?> testClass, Object[] constructorArgs)} method.
|
|
*
|
|
* This version of the method is a syntactic shugar to launch test in this way:
|
|
*
|
|
* <code>
|
|
* public class MyTest extends MlvmTest {
|
|
* ...
|
|
* public static void main(String[] args) {
|
|
* MlvmTestExecutor.launch(args);
|
|
* }
|
|
* }
|
|
* </code>
|
|
*
|
|
* @param args Command-line arguments, which are parsed using internal ArgumentParser (for compatibility with old tests) and also set to appropriate test instance fields using vm.share.options framework
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(String[] args) {
|
|
launch(args, null);
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test.
|
|
* Please see documentation for {@link #launch(Class<?> testClass, Object[] constructorArgs)} method.
|
|
*
|
|
* This version of the method is a syntactic shugar to launch test in this way:
|
|
*
|
|
* <code>
|
|
* public class MyTest extends MlvmTest {
|
|
* ...
|
|
* public static void main(String[] args) {
|
|
* MlvmTestExecutor.launch(args, new Object[] { constructor-arguments... });
|
|
* }
|
|
* }
|
|
* </code>
|
|
*
|
|
* @param args Command-line arguments, which are parsed using internal ArgumentParser (for compatibility with old tests) and also set to appropriate test instance fields using vm.share.options framework
|
|
* @param constructorArgs Arguments, which are parsed to test constructor
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(String[] args, Object[] constructorArgs) {
|
|
Env.init(args);
|
|
launch(constructorArgs);
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test.
|
|
* Please see documentation for {@link #launch(Class<?> testClass, Object[] constructorArgs)} method.
|
|
*
|
|
* This version of the method is a syntactic shugar to launch test in this way:
|
|
*
|
|
* <code>
|
|
* public class MyTest extends MlvmTest {
|
|
* ...
|
|
* void aMethod() {
|
|
* ...
|
|
* MlvmTestExecutor.launch(new Object[] { constructor-arguments... });
|
|
* }
|
|
* }
|
|
* </code>
|
|
*
|
|
* @param constructorArgs Arguments, which are parsed to test constructor
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(Object[] constructorArgs) {
|
|
Class<?> testClass = getTestClassFromCallerStack();
|
|
|
|
if (testClass == null) {
|
|
throw new RuntimeException("TEST BUG: Can't find an instance of MlvmTest or Runnable in the stacktrace");
|
|
}
|
|
|
|
launch(testClass, constructorArgs);
|
|
}
|
|
|
|
private static Class<?> getTestClassFromCallerStack() {
|
|
try {
|
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
|
|
|
// Elements of the stack trace: 0=Thread.getStackTrace(), 1rd=this method, 2nd=launch() method
|
|
// So we start searching from the most outer frame and finish searching at element 3
|
|
for (int i = stackTrace.length - 1; i >= 3; --i) {
|
|
StackTraceElement e = stackTrace[i];
|
|
Class<?> klass = Class.forName(e.getClassName());
|
|
if (MlvmTest.class.isAssignableFrom(klass)) {
|
|
return klass;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
} catch (ClassNotFoundException e) {
|
|
throw new RuntimeException("Unable to get Class object by its name either due to a different ClassLoader (a test bug) or JVM error", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test. The method is the principal MLVM test launcher. This method in conjunction with {@link #runMlvmTest} method:
|
|
* <ol>
|
|
* <li>instantiates test class (optionally passing arguments to constructor)
|
|
* <li>parses command-line arguments using vm.share.options framework and updates the appropriate test fields
|
|
* <li>launches the tests
|
|
* <li>handles all test status passing methods (see below)
|
|
* <li>performs System.exit() with VM-testbase standard exit code 95 (success) or 97 (failure).
|
|
* </ol>
|
|
*
|
|
* <p>The following tests status passing mechanism are available to test writer:
|
|
* <ol>
|
|
* <li>Return a boolean value (true if test passed, false otherwise. Good for simple tests)
|
|
* <li>Assume that test has failed by calling {@link MlvmTest#markTestFailed()} method (suitable for complex logic and multithreaded tests)
|
|
* <li>by throwing exception from test {@link MlvmTest#run()} method
|
|
* </ol>
|
|
|
|
* <p>Additionally the launcher:
|
|
* <ul>
|
|
* <li>measures test run time and logs it
|
|
* <li>optionally dumps heap after test execution if {@link MlvmTest#setHeapDumpAfer(true)} was called
|
|
* <li>enables verbose logging on error
|
|
* </ul>
|
|
*
|
|
* @param testClass a class to instantiate. Has to be subclass of vm.mlvm.share.MlvmTest or java.lang.Runnable
|
|
* @param constructorArgs Arguments to pass to test constructor. Can be null.
|
|
* @see #runMlvmTest(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static void launch(Class<?> testClass, Object[] constructorArgs) {
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
boolean passed;
|
|
try {
|
|
Env.traceDebug(MlvmTest.getName() + " class: " + testClass);
|
|
passed = runMlvmTest(testClass, constructorArgs);
|
|
} catch (Throwable e) {
|
|
Env.complain(e, MlvmTest.getName() + " caught an exception: ");
|
|
passed = false;
|
|
}
|
|
|
|
long finishTime = System.currentTimeMillis();
|
|
Env.traceNormal("The test took " + (finishTime - startTime) + " ms");
|
|
|
|
optionallyDumpHeap();
|
|
|
|
final String testNameUC = MlvmTest.getName().toUpperCase();
|
|
if (passed) {
|
|
Env.display(testNameUC + " PASSED");
|
|
System.exit(Consts.JCK_STATUS_BASE + Consts.TEST_PASSED);
|
|
} else {
|
|
Env.display(testNameUC + " FAILED");
|
|
System.exit(Consts.JCK_STATUS_BASE + Consts.TEST_FAILED);
|
|
}
|
|
}
|
|
|
|
private static void dumpHeapWithHotspotDiagnosticMXBean(String fileName) throws IOException {
|
|
System.err.println("Dumping heap to " + fileName);
|
|
|
|
File f = new File(fileName);
|
|
if (f.exists())
|
|
f.delete();
|
|
|
|
HotSpotDiagnosticMXBean b = ManagementFactory.getPlatformMXBeans(
|
|
com.sun.management.HotSpotDiagnosticMXBean.class).get(0);
|
|
b.dumpHeap(fileName, false);
|
|
}
|
|
|
|
|
|
private static void optionallyDumpHeap() {
|
|
try {
|
|
if (MlvmTest.getHeapDumpAfter()) {
|
|
dumpHeapWithHotspotDiagnosticMXBean(HEAP_DUMP_FILENAME);
|
|
}
|
|
} catch (Exception e) {
|
|
Env.traceNormal(e, "Error dumping heap: ");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test (syntatic shugar method).
|
|
* Calls {@link #runMlvmTest(Class<?> testClass, Object[] constructorArgs)} method providing no arguments to pass to the test constructor.
|
|
*
|
|
* The method throws unchecked exception when there is an error in test logic or handling.
|
|
* Exceptions thrown from MlvmTest.run() method or test constructor are wrapped into java.lang.RuntimeException or java.lang.Error
|
|
*
|
|
* @param testClass a class to instantiate. Has to be subclass of vm.mlvm.share.MlvmTest or java.lang.Runnable
|
|
* @return true if test passed, false otherwise
|
|
* @see #runMlvmTest(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static boolean runMlvmTest(Class<?> testClass) {
|
|
return runMlvmTest(testClass, null);
|
|
}
|
|
|
|
/**
|
|
* Launches MLVM test. In details, it:
|
|
* <ol>
|
|
* <li>instantiates test class (optionally passing arguments to constructor)
|
|
* <li>parses command-line arguments using vm.share.options framework and updates the appropriate test fields
|
|
* <li>launches the tests
|
|
* <li>handles all test status passing methods (see below)
|
|
* </ol>
|
|
*
|
|
* <p>Unlike {@link #launch()} methods, it does NOT:
|
|
* <ul>
|
|
* <li>performs System.exit() with VM-testbase standard exit code 95 (success) or 97 (failure).
|
|
* <li>measures test run time and logs it
|
|
* <li>optionally dumps heap after test execution if {@link MlvmTest#setHeapDumpAfer(true)} was called
|
|
* <li>enables verbose logging on error
|
|
* </ul>
|
|
* Please see {@link #launch(Class<?> testClass, Object[] constructorArgs)} for additional details.
|
|
*
|
|
* The method throws unchecked exception when there is an error in test logic or handling.
|
|
* Exceptions thrown from MlvmTest.run() method or test constructor are wrapped into java.lang.RuntimeException or java.lang.Error
|
|
*
|
|
* @param testClass a class to instantiate. Has to be subclass of vm.mlvm.share.MlvmTest or java.lang.Runnable
|
|
* @param constructorArgs Arguments to pass to test constructor. Can be null.
|
|
* @return true if test passed, false otherwise
|
|
* @see #launch(Class<?> testClass, Object[] constructorArgs)
|
|
*/
|
|
public static boolean runMlvmTest(Class<?> testClass, Object[] constructorArgs) {
|
|
boolean passed;
|
|
Throwable exception = null;
|
|
|
|
try {
|
|
MlvmTest instance = constructMlvmTest(testClass, constructorArgs);
|
|
setupMlvmTest(instance);
|
|
|
|
instance.initializeTest();
|
|
|
|
try {
|
|
passed = runMlvmTestInstance(instance);
|
|
} catch(Throwable e) {
|
|
exception = e;
|
|
passed = false;
|
|
}
|
|
|
|
try {
|
|
instance.finalizeTest(passed);
|
|
} catch (Throwable e) {
|
|
Env.complain(e, "TEST BUG: exception thrown in finalizeTest");
|
|
if (exception == null) {
|
|
exception = e;
|
|
}
|
|
passed = false;
|
|
}
|
|
|
|
} catch (Throwable e) {
|
|
Env.complain(e, "TEST BUG: exception thrown when instantiating/preparing test for run");
|
|
exception = e;
|
|
passed = false; // never really needed, but let's keep it
|
|
}
|
|
|
|
if (exception != null) {
|
|
Env.throwAsUncheckedException(exception); // always throws
|
|
}
|
|
|
|
return passed;
|
|
}
|
|
|
|
private static void setupMlvmTest(MlvmTest instance) {
|
|
MlvmTest.setInstance(instance);
|
|
OptionSupport.setup(instance, Env.getArgParser().getRawArguments(), new IgnoreUnknownArgumentsHandler());
|
|
}
|
|
|
|
private static boolean runMlvmTestInstance(MlvmTest instance) throws Throwable {
|
|
List<Class<? extends Throwable>> expectedExceptions = instance.getRequiredExceptions();
|
|
int runsCount = instance.getRunsNumber() * instance.getStressOptions().getRunsFactor();
|
|
if (runsCount < 1) {
|
|
throw new RuntimeException("Runs number obtained via command-line options is less than 1");
|
|
}
|
|
|
|
int failedRuns = 0;
|
|
|
|
try {
|
|
for (int run = 1; run <= runsCount; ++run) {
|
|
if (runsCount > 1) {
|
|
Env.traceNormal("TEST RUN " + run + "/" + runsCount + "; Failed " + failedRuns + " runs");
|
|
}
|
|
|
|
if (run > 1) {
|
|
instance.resetTest();
|
|
}
|
|
|
|
boolean instancePassed;
|
|
if (expectedExceptions.size() == 0) {
|
|
instancePassed = instance.run();
|
|
} else {
|
|
try {
|
|
instance.run();
|
|
Env.complain("Expected exceptions: " + expectedExceptions + ", but caught none");
|
|
instancePassed = false;
|
|
} catch (Throwable e) {
|
|
if (checkExpectedException(expectedExceptions, e)) {
|
|
instancePassed = true;
|
|
} else {
|
|
Env.complain(e, "Expected exceptions: " + expectedExceptions + ", but caught: ");
|
|
instancePassed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (instance.isMarkedFailed()) {
|
|
instancePassed = false;
|
|
}
|
|
|
|
if (!instancePassed) {
|
|
++failedRuns;
|
|
}
|
|
}
|
|
} finally {
|
|
if (failedRuns > 0) {
|
|
Env.complain("Failed runs: " + failedRuns + " of " + runsCount);
|
|
}
|
|
}
|
|
|
|
return failedRuns == 0;
|
|
}
|
|
|
|
private static Object constructTest(Class<?> testClass, Object[] constructorArgs) throws Throwable {
|
|
if (constructorArgs == null || constructorArgs.length == 0) {
|
|
return testClass.newInstance();
|
|
}
|
|
|
|
for (Constructor<?> c : testClass.getConstructors()) {
|
|
Class<?> paramClasses[] = c.getParameterTypes();
|
|
if (!parametersAreAssignableFrom(paramClasses, constructorArgs)) {
|
|
continue;
|
|
}
|
|
|
|
return c.newInstance(constructorArgs);
|
|
}
|
|
|
|
throw new RuntimeException("Test bug: in class " + testClass.getName() + " no appropriate constructor found for arguments " + constructorArgs);
|
|
}
|
|
|
|
private static MlvmTest constructMlvmTest(Class<?> testClass, Object[] constructorArgs) throws Throwable {
|
|
Object testObj = constructTest(testClass, constructorArgs);
|
|
|
|
MlvmTest instance;
|
|
if (testObj instanceof MlvmTest) {
|
|
instance = (MlvmTest) testObj;
|
|
} else if (testObj instanceof Runnable) {
|
|
instance = new RunnableWrapper((Runnable) testObj);
|
|
} else {
|
|
// You can add wrapping of other types of tests here into MlvmTest if you need
|
|
throw new RuntimeException("TEST BUG: Test class should be subclass of MlvmTest or Runnable. Its type is "
|
|
+ testObj.getClass().getName());
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
private static boolean parametersAreAssignableFrom(Class<?>[] paramClasses, Object[] constructorArgs) {
|
|
if (paramClasses.length != constructorArgs.length) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < paramClasses.length; ++i) {
|
|
if (!paramClasses[i].isAssignableFrom(constructorArgs[i].getClass())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean checkExpectedException(List<Class<? extends Throwable>> expectedExceptions, Throwable caught) throws Throwable {
|
|
for (Class<? extends Throwable> expected : expectedExceptions) {
|
|
if (expected.isAssignableFrom(caught.getClass())) {
|
|
Env.traceNormal("Caught anticipated exception " + caught.getClass().getName() + ". Cause: " + caught.getCause());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static class RunnableWrapper extends MlvmTest {
|
|
private Runnable runnable;
|
|
|
|
public RunnableWrapper(Runnable r) {
|
|
runnable = r;
|
|
}
|
|
|
|
@Override
|
|
public boolean run() throws Throwable {
|
|
runnable.run();
|
|
return true;
|
|
}
|
|
}
|
|
}
|