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 automated developer testing.
It contains mocking/faking APIs and a code coverage tool, 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 JMockit developers
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.state;
import java.lang.instrument.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.*;
import java.util.concurrent.*;
import javax.annotation.*;
import static java.lang.reflect.Modifier.*;
import mockit.internal.*;
import mockit.internal.capturing.*;
import mockit.internal.expectations.mocking.*;
import mockit.internal.startup.*;
import mockit.internal.util.*;
import static mockit.internal.util.GeneratedClasses.*;
import static mockit.internal.util.Utilities.*;
/**
* Holds data about redefined/transformed classes, with methods to add/remove and query such data.
*/
public final class MockFixture
{
/**
* Similar to {@link #redefinedClasses}, but for classes modified by a ClassFileTransformer such as the
* CaptureTransformer, and containing the pre-transform bytecode instead of the modified one.
*
* @see #addTransformedClass(ClassIdentification, byte[])
* @see #getTransformedClasses()
* @see #restoreTransformedClasses(Set)
*/
@Nonnull private final Map transformedClasses;
/**
* 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 a previous definition.
*
* The modified bytecode arrays in the map allow a new redefinition to be made on top of the current redefinition
* (in the case of the Faking API), or to restore the class to a previous definition (provided the map is copied
* between redefinitions of the same class).
*
* @see #addRedefinedClass(ClassDefinition)
* @see #getRedefinedClasses()
* @see #getRedefinedClassfile(Class)
* @see #containsRedefinedClass(Class)
* @see #restoreRedefinedClasses(Map)
*/
@Nonnull private final Map, byte[]> redefinedClasses;
/**
* Maps redefined real classes to the internal name of the corresponding fake classes, when it's the case.
*
* This allows any global state associated to a fake class to be discarded when the corresponding real class is later
* restored to its original definition.
*
* @see #addRedefinedClass(String, ClassDefinition)
*/
@Nonnull private final Map, String> realClassesToFakeClasses;
/**
* A list of classes that are currently mocked.
* Said classes are also added to {@link #mockedTypesAndInstances}.
*
* @see #registerMockedClass(Class)
* @see #getMockedClasses()
* @see #isStillMocked(Object, String)
* @see #isInstanceOfMockedClass(Object)
* @see #removeMockedClasses(List)
*/
@Nonnull private final List> mockedClasses;
/**
* A map of mocked types to their corresponding {@linkplain InstanceFactory mocked instance factories}.
*
* @see #registerInstanceFactoryForMockedType(Class, InstanceFactory)
* @see #findInstanceFactory(Type)
* @see #isStillMocked(Object, String)
* @see #removeMockedClasses(List)
*/
@Nonnull private final Map mockedTypesAndInstances;
/**
* A list of "capturing" class file transformers, used by both the mocking and faking APIs.
*
* @see #addCaptureTransformer(CaptureTransformer)
* @see #areCapturedClasses(Class, Class)
* @see #isCaptured(Object)
* @see #getCaptureTransformerCount()
* @see #removeCaptureTransformers(int)
*/
@Nonnull private final List> captureTransformers;
MockFixture() {
transformedClasses = new HashMap<>(2);
redefinedClasses = new ConcurrentHashMap<>(8);
realClassesToFakeClasses = new IdentityHashMap<>(8);
mockedClasses = new ArrayList<>();
mockedTypesAndInstances = new IdentityHashMap<>();
captureTransformers = new ArrayList<>();
}
// Methods to add/remove transformed/redefined classes /////////////////////////////////////////////////////////////
public void addTransformedClass(@Nonnull ClassIdentification classId, @Nonnull byte[] pretransformClassfile) {
transformedClasses.put(classId, pretransformClassfile);
}
// Methods used by both the Mocking and Faking APIs.
public void addRedefinedClass(@Nonnull ClassDefinition newClassDefinition) {
redefinedClasses.put(newClassDefinition.getDefinitionClass(), newClassDefinition.getDefinitionClassFile());
}
public void registerMockedClass(@Nonnull Class> mockedType) {
if (!mockedClasses.contains(mockedType)) {
mockedType = getMockedClassOrInterfaceType(mockedType);
mockedClasses.add(mockedType);
}
}
// Methods used by the Mocking API.
public void redefineClasses(@Nonnull ClassDefinition... definitions) {
Startup.redefineMethods(definitions);
for (ClassDefinition def : definitions) {
addRedefinedClass(def);
}
}
public void redefineMethods(@Nonnull Map, byte[]> modifiedClassfiles) {
ClassDefinition[] classDefs = new ClassDefinition[modifiedClassfiles.size()];
int i = 0;
for (Entry, byte[]> classAndBytecode : modifiedClassfiles.entrySet()) {
Class> modifiedClass = classAndBytecode.getKey();
byte[] modifiedClassfile = classAndBytecode.getValue();
ClassDefinition classDef = new ClassDefinition(modifiedClass, modifiedClassfile);
classDefs[i++] = classDef;
addRedefinedClass(classDef);
}
Startup.redefineMethods(classDefs);
}
public boolean isStillMocked(@Nullable Object instance, @Nonnull String classDesc) {
Class> targetClass;
if (instance == null) {
targetClass = ClassLoad.loadByInternalName(classDesc);
return isClassAssignableTo(targetClass);
}
targetClass = instance.getClass();
return mockedTypesAndInstances.containsKey(targetClass) || isInstanceOfMockedClass(instance);
}
private boolean isClassAssignableTo(@Nonnull Class> toClass) {
for (Class> mockedClass : mockedClasses) {
if (toClass == mockedClass || toClass.isAssignableFrom(mockedClass)) {
return true;
}
}
return false;
}
public boolean isInstanceOfMockedClass(@Nonnull Object mockedInstance) {
Class> mockedClass = getMockedClassOrInterfaceType(mockedInstance.getClass());
Class> mockedSuperclass = mockedClass.getSuperclass();
if (mockedSuperclass != null && mockedSuperclass.isEnum()) {
return mockedClasses.contains(mockedSuperclass);
}
return mockedClasses.contains(mockedClass) || isCaptured(mockedClass);
}
public void registerInstanceFactoryForMockedType(@Nonnull Class> mockedType, @Nonnull InstanceFactory mockedInstanceFactory) {
registerMockedClass(mockedType);
mockedTypesAndInstances.put(mockedType, mockedInstanceFactory);
}
@Nullable
public InstanceFactory findInstanceFactory(@Nonnull Type mockedType) {
InstanceFactory instanceFactory = mockedTypesAndInstances.get(mockedType);
if (instanceFactory != null) {
return instanceFactory;
}
Class> mockedClass = getClassType(mockedType);
//noinspection ReuseOfLocalVariable
instanceFactory = mockedTypesAndInstances.get(mockedClass);
if (instanceFactory != null) {
return instanceFactory;
}
boolean abstractType = mockedClass.isInterface() || isAbstract(mockedClass.getModifiers());
for (Entry entry : mockedTypesAndInstances.entrySet()) {
Type registeredMockedType = entry.getKey();
Class> registeredMockedClass = getClassType(registeredMockedType);
if (abstractType) {
registeredMockedClass = getMockedClassOrInterfaceType(registeredMockedClass);
}
if (mockedClass.isAssignableFrom(registeredMockedClass)) {
instanceFactory = entry.getValue();
break;
}
}
return instanceFactory;
}
// Methods used by the Faking API.
public void addRedefinedClass(@Nonnull String fakeClassInternalName, @Nonnull ClassDefinition classDef) {
@Nonnull Class> redefinedClass = classDef.getDefinitionClass();
String previousNames = realClassesToFakeClasses.put(redefinedClass, fakeClassInternalName);
if (previousNames != null) {
realClassesToFakeClasses.put(redefinedClass, previousNames + ' ' + fakeClassInternalName);
}
addRedefinedClass(classDef);
}
// Methods used by test save-points ////////////////////////////////////////////////////////////////////////////////
void restoreTransformedClasses(@Nonnull Set previousTransformedClasses) {
if (!transformedClasses.isEmpty()) {
Set classesToRestore;
if (previousTransformedClasses.isEmpty()) {
classesToRestore = transformedClasses.keySet();
}
else {
classesToRestore = getTransformedClasses();
classesToRestore.removeAll(previousTransformedClasses);
}
if (!classesToRestore.isEmpty()) {
restoreAndRemoveTransformedClasses(classesToRestore);
}
}
}
@Nonnull
Set getTransformedClasses() {
return transformedClasses.isEmpty() ? Collections.emptySet() : new HashSet<>(transformedClasses.keySet());
}
@Nonnull
Map, byte[]> getRedefinedClasses() {
return redefinedClasses.isEmpty() ? Collections., byte[]>emptyMap() : new HashMap<>(redefinedClasses);
}
private void restoreAndRemoveTransformedClasses(@Nonnull Set classesToRestore) {
for (ClassIdentification transformedClassId : classesToRestore) {
byte[] definitionToRestore = transformedClasses.get(transformedClassId);
Startup.redefineMethods(transformedClassId, definitionToRestore);
}
transformedClasses.keySet().removeAll(classesToRestore);
}
void restoreRedefinedClasses(@Nonnull Map, byte[]> previousDefinitions) {
if (redefinedClasses.isEmpty()) {
return;
}
Iterator, byte[]>> itr = redefinedClasses.entrySet().iterator();
while (itr.hasNext()) {
Entry, byte[]> entry = itr.next();
Class> redefinedClass = entry.getKey();
byte[] currentDefinition = entry.getValue();
byte[] previousDefinition = previousDefinitions.get(redefinedClass);
if (previousDefinition == null) {
restoreDefinition(redefinedClass);
itr.remove();
}
else if (currentDefinition != previousDefinition) {
Startup.redefineMethods(redefinedClass, previousDefinition);
entry.setValue(previousDefinition);
}
}
}
private void restoreDefinition(@Nonnull Class> redefinedClass) {
if (!isGeneratedImplementationClass(redefinedClass)) {
byte[] previousDefinition = ClassFile.getClassFile(redefinedClass);
Startup.redefineMethods(redefinedClass, previousDefinition);
}
removeMockedClass(redefinedClass);
discardStateForCorrespondingFakeClassIfAny(redefinedClass);
}
private void removeMockedClass(@Nonnull Class> mockedClass) {
mockedTypesAndInstances.remove(mockedClass);
mockedClasses.remove(mockedClass);
}
private void discardStateForCorrespondingFakeClassIfAny(@Nonnull Class> redefinedClass) {
String mockClassesInternalNames = realClassesToFakeClasses.remove(redefinedClass);
TestRun.getFakeStates().removeClassState(redefinedClass, mockClassesInternalNames);
}
void removeMockedClasses(@Nonnull List> previousMockedClasses) {
int currentMockedClassCount = mockedClasses.size();
if (currentMockedClassCount > 0) {
int previousMockedClassCount = previousMockedClasses.size();
if (previousMockedClassCount == 0) {
mockedClasses.clear();
mockedTypesAndInstances.clear();
}
else if (previousMockedClassCount < currentMockedClassCount) {
mockedClasses.retainAll(previousMockedClasses);
mockedTypesAndInstances.keySet().retainAll(previousMockedClasses);
}
}
}
// Getter methods for the maps and collections of transformed/redefined/mocked classes /////////////////////////////
@Nullable
public byte[] getRedefinedClassfile(@Nonnull Class> redefinedClass) {
return redefinedClasses.get(redefinedClass);
}
public boolean containsRedefinedClass(@Nonnull Class> redefinedClass) {
return redefinedClasses.containsKey(redefinedClass);
}
@Nonnull
public List> getMockedClasses() {
return mockedClasses.isEmpty() ? Collections.>emptyList() : new ArrayList<>(mockedClasses);
}
// Methods dealing with capture transformers ///////////////////////////////////////////////////////////////////////
public void addCaptureTransformer(@Nonnull CaptureTransformer> transformer) {
captureTransformers.add(transformer);
}
// The following methods are used by test save-points to discard currently active capture transformers.
int getCaptureTransformerCount() { return captureTransformers.size(); }
void removeCaptureTransformers(int previousTransformerCount) {
int currentTransformerCount = captureTransformers.size();
for (int i = currentTransformerCount - 1; i >= previousTransformerCount; i--) {
CaptureTransformer> transformer = captureTransformers.get(i);
transformer.deactivate();
Startup.instrumentation().removeTransformer(transformer);
captureTransformers.remove(i);
}
}
// The following methods are only used by the Mocking API.
public boolean isCaptured(@Nonnull Object mockedInstance) {
if (!captureTransformers.isEmpty()) {
Class> mockedClass = getMockedClass(mockedInstance);
return isCaptured(mockedClass);
}
return false;
}
private boolean isCaptured(@Nonnull Class> mockedClass) {
for (CaptureTransformer> captureTransformer : captureTransformers) {
CaptureOfNewInstances capture = captureTransformer.getCaptureOfImplementationsIfApplicable(mockedClass);
if (capture != null) {
return true;
}
}
return false;
}
public boolean areCapturedClasses(@Nonnull Class> mockedClass1, @Nonnull Class> mockedClass2) {
for (CaptureTransformer> captureTransformer : captureTransformers) {
if (captureTransformer.areCapturedClasses(mockedClass1, mockedClass2)) {
return true;
}
}
return false;
}
}