mockit.internal.startup.Startup Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for developer (unit/integration) testing.
It contains mocking APIs and other tools, supporting both JUnit and TestNG.
The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested
in isolation from selected dependencies.
/*
* Copyright (c) 2006-2011 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.startup;
import java.io.*;
import java.lang.instrument.*;
import mockit.external.asm.*;
import mockit.integration.junit3.internal.*;
import mockit.integration.junit4.internal.*;
import mockit.integration.testng.internal.*;
import mockit.internal.*;
import mockit.internal.expectations.transformation.*;
import mockit.internal.state.*;
/**
* This is the "agent class" that initializes the JMockit "Java agent". It is not intended for use in client code.
* It must be public, however, so the JVM can call the {@code premain} method, which as the name implies is called
* before the {@code main} method.
*
* @see #premain(String, Instrumentation)
*/
public final class Startup
{
static final String javaSpecVersion = System.getProperty("java.specification.version");
static final boolean jdk6OrLater = "1.6".equals(javaSpecVersion) || "1.7".equals(javaSpecVersion);
private static Instrumentation instrumentation;
private static boolean initializedOnDemand;
private Startup() {}
public static boolean isJava6OrLater() { return jdk6OrLater; }
/**
* This method must only be called by the JVM, to provide the instrumentation object.
* In order for this to occur, the JVM must be started with "-javaagent:jmockit.jar" as a command line parameter
* (assuming the jar file is in the current directory).
*
* It is also possible to load other instrumentation tools at this time, according to any agent
* arguments provided as "-javaagent:jmockit.jar=agentArgs" in the JVM command line.
* There are two types of instrumentation tools:
*
* - A {@link ClassFileTransformer class file transformer}, which will be instantiated and added to the JVM
* instrumentation service. Such a class must be public and have a public constructor accepting two parameters: the
* first of type {@code Map<String, byte[]>}, which will receive a map for storing the transformed classes; and
* the second of type {@code String}, which will receive any tool arguments.
* - An external mock, which can be any class with a public no-args constructor.
* Such a class will be used to redefine one or more real classes.
* The real classes can be specified in one of two ways: by providing a regular expression matching class names as
* the tool arguments, or by annotating the external mock class with {@link mockit.MockClass}.
*
*
* @param agentArgs zero or more instrumentation tool specifications (separated by semicolons if more than
* one); each tool specification must be expressed as "<tool class name>[=tool arguments]", with
* fully qualified class names for classes available in the classpath; tool arguments are optional
* @param inst the instrumentation service provided by the JVM
*/
public static void premain(String agentArgs, Instrumentation inst) throws Exception
{
initialize(true, agentArgs, inst);
}
@SuppressWarnings({"UnusedDeclaration"})
public static void agentmain(String agentArgs, Instrumentation inst) throws Exception
{
initialize(false, agentArgs, inst);
}
private static void initialize(boolean initializeTestNG, String agentArgs, Instrumentation inst) throws IOException
{
instrumentation = inst;
StartupConfiguration config = new StartupConfiguration();
preventEventualClassLoadingConflicts();
loadInternalStartupMocksForJUnitIntegration();
if (initializeTestNG) {
try { setUpInternalStartupMock(MockTestNG.class); } catch (Error ignored) {}
}
if (agentArgs != null && agentArgs.length() > 0) {
processAgentArgs(config, agentArgs);
}
for (String toolSpec : config.defaultTools) {
loadExternalTool(config, toolSpec, true);
}
inst.addTransformer(new JMockitTransformer());
inst.addTransformer(new ExpectationsTransformer(inst));
}
private static void preventEventualClassLoadingConflicts()
{
// Pre-load certain JMockit classes to avoid NoClassDefFoundError's when mocking certain JRE classes,
// such as ArrayList.
try {
Class.forName("mockit.Delegate");
Class.forName("mockit.internal.expectations.invocation.InvocationResults");
}
catch (ClassNotFoundException ignore) {}
MockingBridge.MB.getClass();
}
private static void loadInternalStartupMocksForJUnitIntegration()
{
if (setUpInternalStartupMock(TestSuiteDecorator.class)) {
try {
setUpInternalStartupMock(JUnitTestCaseDecorator.class);
}
catch (VerifyError ignore) {
// For some reason, this error occurs when running TestNG tests from Maven.
}
setUpInternalStartupMock(RunNotifierDecorator.class);
setUpInternalStartupMock(JUnit4TestRunnerDecorator.class);
TestRun.mockFixture().turnRedefinedClassesIntoFixedOnes();
}
}
private static boolean setUpInternalStartupMock(Class> mockClass)
{
try {
new RedefinitionEngine(null, mockClass).setUpStartupMock();
return true;
}
catch (TypeNotPresentException ignore) {
// OK, ignore the startup mock if the necessary third-party class files are not in the classpath.
return false;
}
}
private static void processAgentArgs(StartupConfiguration config, String agentArgs) throws IOException
{
String[] externalToolSpecs = agentArgs.split("\\s*;\\s*");
for (String toolSpec : externalToolSpecs) {
loadExternalTool(config, toolSpec, false);
}
}
private static void loadExternalTool(StartupConfiguration config, String toolSpec, boolean byDefault)
throws IOException
{
config.extractClassNameAndArgumentsFromToolSpecification(toolSpec, byDefault);
ClassReader cr;
if (byDefault) {
try {
cr = ClassFile.readClass(config.toolClassName);
}
catch (IOException ignore) {
// OK, don't load if not in the classpath.
return;
}
}
else {
cr = ClassFile.readClass(config.toolClassName);
}
loadExternalTool(config, cr);
}
private static void loadExternalTool(StartupConfiguration config, ClassReader cr)
{
ToolLoader toolLoader = new ToolLoader(config.toolClassName, config.toolArguments);
try {
cr.accept(toolLoader, true);
}
catch (IllegalStateException ignore) {
return;
}
System.out.println("JMockit: loaded external tool " + config);
}
public static Instrumentation instrumentation()
{
verifyInitialization();
return instrumentation;
}
public static boolean wasInitializedOnDemand() { return initializedOnDemand; }
public static void verifyInitialization()
{
if (instrumentation == null) {
new AgentInitialization().initializeAccordingToJDKVersion();
initializedOnDemand = true;
System.out.println(
"WARNING: JMockit was initialized on demand, which may cause certain tests to fail;\n" +
"please check the documentation for better ways to get it initialized.");
}
}
public static boolean initializeIfNeeded()
{
if (instrumentation == null) {
try {
new AgentInitialization().initializeAccordingToJDKVersion();
return true;
}
catch (RuntimeException e) {
e.printStackTrace(); // makes sure the exception gets printed at least once
throw e;
}
}
return false;
}
public static void initializeIfPossible()
{
if (jdk6OrLater) {
initializeIfNeeded();
}
}
}