org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker Maven / Gradle / Ivy
/* * Copyright (c) 2016 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.creation.bytebuddy; import net.bytebuddy.agent.ByteBuddyAgent; import org.mockito.Incubating; import org.mockito.exceptions.base.MockitoException; import org.mockito.exceptions.base.MockitoInitializationException; import org.mockito.internal.InternalMockHandler; import org.mockito.internal.configuration.plugins.Plugins; import org.mockito.internal.creation.instance.Instantiator; import org.mockito.internal.util.Platform; import org.mockito.internal.util.concurrent.WeakConcurrentMap; import org.mockito.invocation.MockHandler; import org.mockito.mock.MockCreationSettings; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.lang.reflect.Modifier; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import static org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.EXCLUDES; import static org.mockito.internal.util.StringJoiner.join; /** * Agent and subclass based mock maker. *
** This mock maker which uses a combination of the Java instrumentation API and sub-classing rather than creating * a new sub-class to create a mock. This way, it becomes possible to mock final types and methods. This mock * maker must to be activated explicitly for supporting mocking final types and methods: *
*
* This mock maker can be activated by creating the file
/mockito-extensions/org.mockito.plugins.MockMaker
* containing the textmock-maker-inline
ororg.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker
. **
* This mock maker will make a best effort to avoid subclass creation when creating a mock. Otherwise it will use the *
org.mockito.internal.creation.bytebuddy.SubclassByteBuddyMockMaker
to create the mock class. That means * that the following condition is true **
* class Foo { } * assert mock(Foo.class).getClass() == Foo.class; *
* unless any of the following conditions is met, in such case the mock maker fall backs to the * the creation of a subclass. *
*
-
*
- the type to mock is an abstract class. *
- the mock is set to require additional interfaces. *
- the mock is explicitly set to support serialization. *
*
* Some type of the JDK cannot be mocked, this includes Class
, String
, and wrapper types.
*
*
* Nevertheless, final methods of such types are mocked when using the inlining mock maker. Mocking final types and enums * does however remain impossible when explicitly requiring serialization support or when adding ancillary interfaces. *
*
* Important behavioral changes when using inline-mocks: *
-
*
- Mockito is capable of mocking package-private methods even if they are defined in different packages than
* the mocked type. Mockito voluntarily never mocks package-visible methods within
java.*
packages.
* - Additionally to final types, Mockito can now mock types that are not visible for extension; such types * include private types in a protected package. *
- Mockito can no longer mock
native
methods. Inline mocks require byte code manipulation of a * method where native methods do not offer any byte code to manipulate.
* - Mockito cannot longer strip
synchronized
modifiers from mocked instances.
*
*
* Note that inline mocks require a Java agent to be attached. Mockito will attempt an attachment of a Java agent upon
* loading the mock maker for creating inline mocks. Such runtime attachment is only possible when using a JVM that
* is part of a JDK or when using a Java 9 VM. When running on a non-JDK VM prior to Java 9, it is however possible to
* manually add the Byte Buddy Java agent jar using the -javaagent
* parameter upon starting the JVM. Furthermore, the inlining mock maker requires the VM to support class retransformation
* (also known as HotSwap). All major VM distributions such as HotSpot (OpenJDK), J9 (IBM/Websphere) or Zing (Azul)
* support this feature.
*/
@Incubating
public class InlineByteBuddyMockMaker implements ClassCreatingMockMaker {
private static final Instrumentation INSTRUMENTATION;
private static final Throwable INITIALIZATION_ERROR;
static {
Instrumentation instrumentation;
Throwable initializationError = null;
try {
try {
instrumentation = ByteBuddyAgent.install();
if (!instrumentation.isRetransformClassesSupported()) {
throw new IllegalStateException(join(
"Byte Buddy requires retransformation for creating inline mocks. This feature is unavailable on the current VM.",
"",
"You cannot use this mock maker on this VM"));
}
File boot = File.createTempFile("mockitoboot", ".jar");
boot.deleteOnExit();
JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(boot));
try {
String source = "org/mockito/internal/creation/bytebuddy/MockMethodDispatcher";
InputStream inputStream = InlineByteBuddyMockMaker.class.getClassLoader().getResourceAsStream(source + ".raw");
if (inputStream == null) {
throw new IllegalStateException(join(
"The MockMethodDispatcher class file is not locatable: " + source + ".raw",
"",
"The class loader responsible for looking up the resource: " + InlineByteBuddyMockMaker.class.getClassLoader()
));
}
outputStream.putNextEntry(new JarEntry(source + ".class"));
try {
int length;
byte[] buffer = new byte[1024];
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
} finally {
inputStream.close();
}
outputStream.closeEntry();
} finally {
outputStream.close();
}
instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(boot));
try {
Class dispatcher = Class.forName("org.mockito.internal.creation.bytebuddy.MockMethodDispatcher");
if (dispatcher.getClassLoader() != null) {
throw new IllegalStateException(join(
"The MockMethodDispatcher must not be loaded manually but must be injected into the bootstrap class loader.",
"",
"The dispatcher class was already loaded by: " + dispatcher.getClassLoader()));
}
} catch (ClassNotFoundException cnfe) {
throw new IllegalStateException(join(
"Mockito failed to inject the MockMethodDispatcher class into the bootstrap class loader",
"",
"It seems like your current VM does not support the instrumentation API correctly."), cnfe);
}
} catch (IOException ioe) {
throw new IllegalStateException(join(
"Mockito could not self-attach a Java agent to the current VM. This feature is required for inline mocking.",
"This error occured due to an I/O error during the creation of this agent: " + ioe,
"",
"Potentially, the current VM does not support the instrumentation API correctly"), ioe);
}
} catch (Throwable throwable) {
instrumentation = null;
initializationError = throwable;
}
INSTRUMENTATION = instrumentation;
INITIALIZATION_ERROR = initializationError;
}
private final BytecodeGenerator bytecodeGenerator;
private final WeakConcurrentMap