/* * 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 * <em>before</em> 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). * <p/> * It is also possible to load other <em>instrumentation tools</em> 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: * <ol> * <li>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.</li> * <li>An <em>external mock</em>, 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}.</li> * </ol> * * @param agentArgs zero or more <em>instrumentation tool specifications</em> (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(); } } }