Skip to content

Commit

Permalink
PowerMockito now works with ByteBuddy (issue 579)
Browse files Browse the repository at this point in the history
  • Loading branch information
johanhaleby committed Sep 4, 2015
1 parent f174dd8 commit d73e4de
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.powermock.core.ClassReplicaCreator;
import org.powermock.core.DefaultFieldValueGenerator;
import org.powermock.core.MockRepository;
import org.powermock.core.classloader.MockClassLoader;
import org.powermock.reflect.Whitebox;

import java.lang.reflect.Method;
Expand All @@ -43,7 +44,7 @@ public static <T> T mock(Class<T> type, boolean isStatic, boolean isSpy, Object
throw new IllegalArgumentException("The class to mock cannot be null");
}

T mock = null;
T mock;
final String mockName = toInstanceName(type, mockSettings);

MockRepository.addAfterMethodRunner(new MockitoStateCleanerRunnable());
Expand Down Expand Up @@ -108,6 +109,11 @@ private static <T> MockData<T> createMethodInvocationControl(final String mockNa
InternalMockHandler mockHandler = new MockHandlerFactory().create(settings);
MethodInterceptorFilter filter = new PowerMockMethodInterceptorFilter(mockHandler, settings);
final T mock = (T) new ClassImposterizer(new InstantiatorProvider().getInstantiator(settings)).imposterise(filter, type);
ClassLoader classLoader = mock.getClass().getClassLoader();
if (classLoader instanceof MockClassLoader) {
MockClassLoader mcl = (MockClassLoader) classLoader;
mcl.cache(mock.getClass());
}
final MockitoMethodInvocationControl invocationControl = new MockitoMethodInvocationControl(
filter,
isSpy && delegator == null ? new Object() : delegator,
Expand All @@ -118,15 +124,15 @@ private static <T> MockData<T> createMethodInvocationControl(final String mockNa
}

private static String toInstanceName(Class<?> clazz, final MockSettings mockSettings) {
// if the settings define a mock name, use it
if ( mockSettings instanceof MockSettingsImpl<?> ) {
String settingName = ( (MockSettingsImpl<?>) mockSettings ).getName();
if ( settingName != null ) {
return settingName;
}
}
// else, use the class name as mock name
// if the settings define a mock name, use it
if (mockSettings instanceof MockSettingsImpl<?>) {
String settingName = ((MockSettingsImpl<?>) mockSettings).getName();
if (settingName != null) {
return settingName;
}
}

// else, use the class name as mock name
String className = clazz.getSimpleName();
if (className.length() == 0) {
return clazz.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.VoidMethodStubbable;
import org.powermock.api.mockito.repackaged.CglibMockMaker;
import org.powermock.core.classloader.MockClassLoader;

import java.util.List;

Expand All @@ -42,7 +43,14 @@ public class PowerMockMaker implements MockMaker {
private final MockMaker cglibMockMaker = new CglibMockMaker();

public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
return cglibMockMaker.createMock(settings, handler);
T mock = cglibMockMaker.createMock(settings, handler);
ClassLoader classLoader = cglibMockMaker.getClass().getClassLoader();
if (classLoader instanceof MockClassLoader) {
MockClassLoader mcl = (MockClassLoader) classLoader;
// The generated class is not picked up by PowerMock so we cache it here
mcl.cache(mock.getClass());
}
return mock;
}

public MockHandler getHandler(Object mock) {
Expand Down
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Change log next version
* Upgraded Javassist dependency to version 3.20.0-GA.
* Using soft reference in classloader cache
* MockClassloader now extends Javassist Loader classloader to implement findClass etc
* PowerMockito now works with ByteBuddy (issue 579)

Change log 1.6.2 (2015-01-03)
----------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* Defers classloading of system classes to a delegate.
Expand All @@ -34,7 +34,7 @@
* @author Jan Kronquist
*/
public abstract class DeferSupportingClassLoader extends Loader {
private Map<String, SoftReference<?>> classes;
private ConcurrentMap<String, SoftReference<Class<?>>> classes;

String deferPackages[];

Expand All @@ -56,12 +56,12 @@ public DeferSupportingClassLoader(ClassLoader classloader, String deferPackages[
} else {
deferTo = classloader;
}
classes = new HashMap<String, SoftReference<?>>();
classes = new ConcurrentHashMap<String, SoftReference<Class<?>>>();
this.deferPackages = deferPackages;
}

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
SoftReference<?> reference = classes.get(name);
SoftReference<Class<?>> reference = classes.get(name);
if (reference == null || reference.get() == null) {
final Class<?> clazz;
if (shouldDefer(deferPackages, name)) {
Expand All @@ -72,9 +72,9 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
if (resolve) {
resolveClass(clazz);
}
classes.put(name, (reference = new SoftReference<Object>(clazz)));
classes.put(name, (reference = new SoftReference<Class<?>>(clazz)));
}
return (Class<?>) reference.get();
return reference.get();
}

protected boolean shouldDefer(String[] packages, String name) {
Expand Down Expand Up @@ -157,4 +157,13 @@ protected boolean shouldModify(Iterable<String> packages, String name) {
protected abstract boolean shouldModifyClass(String s);

protected abstract boolean shouldLoadUnmodifiedClass(String className);

/**
* Register a class to the cache of this classloader
*/
public void cache(Class<?> cls) {
if (cls != null) {
classes.put(cls.getName(), new SoftReference<Class<?>>(cls));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@
*/
package org.powermock.core.classloader;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import org.powermock.core.ClassReplicaCreator;
import org.powermock.core.WildcardMatcher;
import org.powermock.core.classloader.annotations.UseClassPathAdjuster;
import org.powermock.core.spi.PowerMockPolicy;
import org.powermock.core.spi.support.InvocationSubstitute;
import org.powermock.core.transformers.MockTransformer;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Mock all classes except system classes.
* <p/>
Expand All @@ -36,7 +35,7 @@
* <li>system classes are deferred to another classloader</li>
* <li>testing framework classes are loaded, but not modified</li>
* </ol>
*
*
* @author Johan Haleby
* @author Jan Kronquist
*/
Expand All @@ -59,35 +58,35 @@ public class MockClassLoader extends DeferSupportingClassLoader {
* Classes not deferred but loaded by the mock class loader but they're not
* modified.
*/
private final String[] packagesToLoadButNotModify = new String[] { "org.junit.", "junit.", "org.easymock.",
private final String[] packagesToLoadButNotModify = new String[]{"org.junit.", "junit.", "org.easymock.",
"net.sf.cglib.", "javassist.",
"org.powermock.modules.junit4.internal.", "org.powermock.modules.junit4.legacy.internal.",
"org.powermock.modules.junit3.internal.",
"org.powermock" };
"org.powermock"};

private final String[] specificClassesToLoadButNotModify = new String[] { InvocationSubstitute.class.getName(),
private final String[] specificClassesToLoadButNotModify = new String[]{InvocationSubstitute.class.getName(),
PowerMockPolicy.class.getName(),
ClassReplicaCreator.class.getName() };
ClassReplicaCreator.class.getName()};

/*
* Classes that should always be deferred regardless of what the user
* specifies in annotations etc.
*/
private static final String[] packagesToBeDeferred = new String[] { "org.hamcrest.*", "java.*",
private static final String[] packagesToBeDeferred = new String[]{"org.hamcrest.*", "java.*",
"javax.accessibility.*", "sun.*", "org.junit.*",
"junit.*", "org.pitest.*", "org.powermock.modules.junit4.common.internal.*",
"org.powermock.modules.junit3.internal.PowerMockJUnit3RunnerDelegate*",
"org.powermock.core*", "org.jacoco.agent.rt.*" };
"org.powermock.core*", "org.jacoco.agent.rt.*"};

private ClassPool classPool = new ClassPool();

/**
* Creates a new instance of the {@link MockClassLoader} based on the
* following parameters:
*
* @param classesToMock The classes that must be modified to prepare for testability.
*
* @param classesToMock The classes that must be modified to prepare for testability.
* @param packagesToDefer Classes in these packages will be defered to the system
* class-loader.
* class-loader.
*/
public MockClassLoader(String[] classesToMock, String[] packagesToDefer, UseClassPathAdjuster useClassPathAdjuster) {
super(MockClassLoader.class.getClassLoader(), getPackagesToDefer(packagesToDefer));
Expand Down Expand Up @@ -122,10 +121,10 @@ private static String[] getPackagesToDefer(final String[] additionalDeferPackage
/**
* Creates a new instance of the {@link MockClassLoader} based on the
* following parameters:
*
* @param classesToMock The classes that must be modified to prepare for testability.
*
* @param classesToMock The classes that must be modified to prepare for testability.
* @param packagesToDefer Classes in these packages will be defered to the system
* class-loader.
* class-loader.
*/
public MockClassLoader(String[] classesToMock, String[] packagesToDefer) {
this(classesToMock, packagesToDefer, null);
Expand All @@ -134,7 +133,7 @@ public MockClassLoader(String[] classesToMock, String[] packagesToDefer) {
/**
* Creates a new instance of the {@link MockClassLoader} based on the
* following parameters:
*
*
* @param classesToMock The classes that must be modified to prepare for testability.
*/
public MockClassLoader(String[] classesToMock, UseClassPathAdjuster useClassPathAdjuster) {
Expand All @@ -144,7 +143,7 @@ public MockClassLoader(String[] classesToMock, UseClassPathAdjuster useClassPath
/**
* Creates a new instance of the {@link MockClassLoader} based on the
* following parameters:
*
*
* @param classesToMock The classes that must be modified to prepare for testability.
*/
public MockClassLoader(String[] classesToMock) {
Expand All @@ -157,10 +156,10 @@ public MockClassLoader(String[] classesToMock) {
* contained in the {@link #packagesToBeDeferred} will be ignored. How ever
* classes added here have precedence over additionally deferred (ignored)
* packages (those ignored by the user using @PrepareForTest).
*
*
* @param classes The fully qualified name of the classes that will be appended
* to the list of classes that will be byte-code modified to
* enable testability.
* to the list of classes that will be byte-code modified to
* enable testability.
*/
public void addClassesToModify(String... classes) {
if (classes != null) {
Expand All @@ -174,6 +173,7 @@ public void addClassesToModify(String... classes) {

protected Class<?> loadModifiedClass(String s) throws ClassFormatError, ClassNotFoundException {
Class<?> loadedClass;

deferTo.loadClass(s);
if (shouldModify(s) && !shouldLoadWithMockClassloaderWithoutModifications(s)) {
loadedClass = loadMockClass(s);
Expand Down
40 changes: 40 additions & 0 deletions examples/byte-buddy/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.powermock.examples</groupId>
<artifactId>powermock-examples</artifactId>
<version>1.6.3-SNAPSHOT</version>
</parent>

<artifactId>powermock-examples-byte-buddy</artifactId>
<name>${project.artifactId}</name>

<description>
Example demonstrating that PowerMock and Byte Buddy works together.
</description>

<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>0.6.15</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package powermock.examples.bytebuddy;

public class SampleClass {
}
Loading

0 comments on commit d73e4de

Please sign in to comment.