mockit.internal.state.MockFixture 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.state;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import mockit.internal.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.util.*;
/**
* Holds data about redefined real classes and their corresponding mock classes (if any), and
* provides methods to add/remove such state both from this instance and from other state holders
* with associated data.
*/
public final class MockFixture
{
/**
* Class names with their bytecode fixed definitions, that is, those to which the class should
* eventually be restored.
*
* This map is to be used by other bytecode instrumentation tools such as JMockit Coverage,
* which need to avoid having their bytecode redefinitions/transformations getting discarded when
* test tear down executes and restores mocked classes.
*
* The modified bytecode arrays in the map allow a new redefinition for a given class to be made,
* on top of the fixed definition.
*/
private final Map fixedClassDefinitions = new HashMap();
/**
* Similar to redefinedClasses
, but for classes modified by a ClassFileTransformer
* such as the CaptureTransformer, and containing the pre-transform bytecode instead of the
* modified one.
*/
private final Map transformedClasses = new HashMap(2);
/**
* Real classes currently redefined in the running JVM and their current (modified) bytecodes.
*
* The keys in the map allow each redefined real class to be later restored to its original
* definition at any moment, by re-reading the class file from disk.
*
* The modified bytecode arrays in the map allow a new redefinition for a given real class to be
* made, on top of the current redefinition.
*/
private final Map, byte[]> redefinedClasses = new HashMap, byte[]>(8);
/**
* Subset of all currently redefined classes which contain one or more native methods.
*
* This is needed because in order to restore such methods it is necessary (for some classes) to
* re-register them with the JVM.
*
* @see #reregisterNativeMethodsForRestoredClass(Class)
*/
private final Set redefinedClassesWithNativeMethods = new HashSet();
/**
* Maps redefined real classes to the internal name of the corresponding mock classes, when it's
* the case.
*
* This allows any global state associated to a mock class to be discarded when the corresponding
* real class is later restored to its original definition.
*/
private final Map, String> realClassesToMockClasses = new HashMap, String>(8);
private final Map, InstanceFactory> mockedTypesAndInstances = new HashMap, InstanceFactory>();
// Methods to add/remove redefined classes /////////////////////////////////////////////////////////////////////////
public void addFixedClass(String className, byte[] fixedClassfile)
{
fixedClassDefinitions.put(className, fixedClassfile);
}
public void addTransformedClass(String className, byte[] pretransformClassfile)
{
transformedClasses.put(className, pretransformClassfile);
}
public void addRedefinedClass(String mockClassInternalName, Class> redefinedClass, byte[] modifiedClassfile)
{
if (mockClassInternalName != null) {
String previousNames = realClassesToMockClasses.put(redefinedClass, mockClassInternalName);
if (previousNames != null) {
realClassesToMockClasses.put(redefinedClass, previousNames + ' ' + mockClassInternalName);
}
}
addRedefinedClass(redefinedClass, modifiedClassfile);
// TODO: implement support for multiple simultaneous redefinitions for each class?
// at least support the case where some class is stubbed out for the whole test class and
// then mocked in one or more tests, particularly when using @Mock(invocations = n)
}
public void addRedefinedClass(Class> redefinedClass, byte[] modifiedClassfile)
{
redefinedClasses.put(redefinedClass, modifiedClassfile);
}
public void registerInstanceFactoryForMockedType(Class> mockedType, InstanceFactory mockedInstanceFactory)
{
mockedTypesAndInstances.put(mockedType, mockedInstanceFactory);
}
public InstanceFactory findInstanceFactory(Class> mockedType)
{
if (mockedType.isInterface() || Modifier.isAbstract(mockedType.getModifiers())) {
for (Entry, InstanceFactory> entry : mockedTypesAndInstances.entrySet()) {
Class> baseType = Utilities.getMockedClassOrInterfaceType(entry.getKey());
if (baseType == mockedType) {
return entry.getValue();
}
}
return null;
}
return mockedTypesAndInstances.get(mockedType);
}
public void restoreAndRemoveTransformedClasses(Set transformedClassesToRestore)
{
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (String transformedClassName : transformedClassesToRestore) {
byte[] definitionToRestore = transformedClasses.get(transformedClassName);
redefinitionEngine.restoreToDefinition(transformedClassName, definitionToRestore);
}
transformedClasses.keySet().removeAll(transformedClassesToRestore);
}
public void restoreAndRemoveRedefinedClasses(Set> redefinedClassesToRestore)
{
RedefinitionEngine redefinitionEngine = new RedefinitionEngine();
for (Class> redefinedClass : redefinedClassesToRestore) {
redefinitionEngine.restoreOriginalDefinition(redefinedClass);
if (redefinedClassesWithNativeMethods.contains(redefinedClass.getName())) {
reregisterNativeMethodsForRestoredClass(redefinedClass);
}
discardStateForCorrespondingMockClassIfAny(redefinedClass);
}
redefinedClasses.keySet().removeAll(redefinedClassesToRestore);
mockedTypesAndInstances.keySet().removeAll(redefinedClassesToRestore);
}
private void discardStateForCorrespondingMockClassIfAny(Class> redefinedClass)
{
String mockClassesInternalNames = realClassesToMockClasses.remove(redefinedClass);
TestRun.getMockClasses().getMockStates().removeClassState(redefinedClass, mockClassesInternalNames);
}
// Methods that deal with redefined native methods /////////////////////////////////////////////////////////////////
public void addRedefinedClassWithNativeMethods(String redefinedClassInternalName)
{
redefinedClassesWithNativeMethods.add(redefinedClassInternalName.replace('/', '.'));
}
private void reregisterNativeMethodsForRestoredClass(Class> realClass)
{
Method registerNatives = null;
try {
registerNatives = realClass.getDeclaredMethod("registerNatives");
}
catch (NoSuchMethodException ignore) {
try {
registerNatives = realClass.getDeclaredMethod("initIDs");
}
catch (NoSuchMethodException alsoIgnore) {
// OK
}
}
if (registerNatives != null) {
try {
registerNatives.setAccessible(true);
registerNatives.invoke(null);
}
catch (IllegalAccessException ignore) {
// Won't happen.
}
catch (InvocationTargetException ignore) {
// Shouldn't happen either.
}
}
// OK, although another solution will be required for this particular class if it requires
// natives to be explicitly registered again (not all do, such as java.lang.Float).
}
// Getter methods for the maps of redefined classes ////////////////////////////////////////////////////////////////
public byte[] getFixedClassfile(String className)
{
return fixedClassDefinitions.get(className);
}
public int getRedefinedClassCount()
{
return redefinedClasses.size();
}
public byte[] getRedefinedClassfile(Class> redefinedClass)
{
return redefinedClasses.get(redefinedClass);
}
public Set getTransformedClasses()
{
return transformedClasses.keySet();
}
public Set> getRedefinedClasses()
{
return redefinedClasses.keySet();
}
public boolean containsRedefinedClass(Class> redefinedClass)
{
return redefinedClasses.containsKey(redefinedClass);
}
public void turnRedefinedClassesIntoFixedOnes()
{
Iterator, byte[]>> itr = redefinedClasses.entrySet().iterator();
while (itr.hasNext()) {
Entry, byte[]> classAndBytecode = itr.next();
itr.remove();
addFixedClass(classAndBytecode.getKey().getName(), classAndBytecode.getValue());
}
}
}