/*
* Copyright 2011 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 org.powermock.core.classloader;
import javassist.ByteArrayClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.junit.Test;
import org.powermock.core.classloader.annotations.UseClassPathAdjuster;
import org.powermock.core.transformers.MockTransformer;
import org.powermock.core.transformers.impl.ClassMockTransformer;
import org.powermock.reflect.Whitebox;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.powermock.core.classloader.MockClassLoader.MODIFY_ALL_CLASSES;
public class MockClassLoaderTest {
@Test
public void autoboxingWorks() throws Exception {
String name = this.getClass().getPackage().getName() + ".HardToTransform";
final MockClassLoader mockClassLoader = new MockClassLoader(new String[]{name});
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
Class<?> c = mockClassLoader.loadClass(name);
Object object = c.newInstance();
Whitebox.invokeMethod(object, "run");
assertThat(5).isEqualTo(Whitebox.invokeMethod(object, "testInt"));
assertThat(5L).isEqualTo(Whitebox.invokeMethod(object, "testLong"));
assertThat(5f).isEqualTo(Whitebox.invokeMethod(object, "testFloat"));
assertThat(5.0).isEqualTo(Whitebox.invokeMethod(object, "testDouble"));
assertThat(new Short("5")).isEqualTo(Whitebox.invokeMethod(object, "testShort"));
assertThat(new Byte("5")).isEqualTo(Whitebox.invokeMethod(object, "testByte"));
assertThat(true).isEqualTo(Whitebox.invokeMethod(object, "testBoolean"));
assertThat('5').isEqualTo(Whitebox.invokeMethod(object, "testChar"));
assertThat("5").isEqualTo(Whitebox.invokeMethod(object, "testString"));
}
@Test
public void callFindClassWorks() throws Exception {
MyClassloader myClassloader = new MyClassloader(new String[]{"org.mytest.myclass"});
assertEquals(String.class, myClassloader.findClassPublic("java.lang.String"));
}
@Test
public void prepareForTestHasPrecedenceOverPowerMockIgnoreAnnotatedPackages() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[]{"org.mytest.myclass"});
Whitebox.setInternalState(mockClassLoader, new String[]{"*mytest*"}, DeferSupportingClassLoader.class);
assertTrue(Whitebox.<Boolean>invokeMethod(mockClassLoader, "shouldModify", "org.mytest.myclass"));
}
@Test
public void powerMockIgnoreAnnotatedPackagesAreIgnored() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[]{"org.ikk.Jux"});
Whitebox.setInternalState(mockClassLoader, new String[]{"*mytest*"}, DeferSupportingClassLoader.class);
assertFalse(Whitebox.<Boolean>invokeMethod(mockClassLoader, "shouldModify", "org.mytest.myclass"));
}
@Test
public void powerMockIgnoreAnnotatedPackagesHavePrecedenceOverPrepareEverythingForTest() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[]{MODIFY_ALL_CLASSES});
Whitebox.setInternalState(mockClassLoader, new String[]{"*mytest*"}, DeferSupportingClassLoader.class);
assertFalse(Whitebox.<Boolean>invokeMethod(mockClassLoader, "shouldModify", "org.mytest.myclass"));
}
@Test
public void prepareForTestPackagesArePrepared() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[]{"*mytest*"});
assertTrue(Whitebox.<Boolean>invokeMethod(mockClassLoader, "shouldModify", "org.mytest.myclass"));
}
@Test
public void shouldAddIgnorePackagesToDefer() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[0]);
mockClassLoader.addIgnorePackage("test*");
String[] deferPackages = Whitebox.getInternalState(mockClassLoader, "deferPackages");
assertTrue(deferPackages.length > 1);
assertEquals("test*", deferPackages[deferPackages.length - 1]);
}
@Test
public void canFindResource() throws Exception {
final MockClassLoader mockClassLoader = new MockClassLoader(new String[0]);
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
// Force a ClassLoader that can find 'foo/bar/baz/test.txt' into
// mockClassLoader.deferTo.
mockClassLoader.deferTo = new ResourcePrefixClassLoader(getClass().getClassLoader(), "org/powermock/core/classloader/");
// MockClassLoader will only be able to find 'foo/bar/baz/test.txt' if it
// properly defers the resource lookup to its deferTo ClassLoader.
URL resource = mockClassLoader.getResource("foo/bar/baz/test.txt");
assertThat(resource).isNotNull();
assertThat(resource.getPath()).endsWith("test.txt");
}
@Test
public void canFindResources() throws Exception {
final MockClassLoader mockClassLoader = new MockClassLoader(new String[0]);
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
// Force a ClassLoader that can find 'foo/bar/baz/test.txt' into
// mockClassLoader.deferTo.
mockClassLoader.deferTo = new ResourcePrefixClassLoader(getClass().getClassLoader(), "org/powermock/core/classloader/");
// MockClassLoader will only be able to find 'foo/bar/baz/test.txt' if it
// properly defers the resources lookup to its deferTo ClassLoader.
Enumeration<URL> resources = mockClassLoader.getResources("foo/bar/baz/test.txt");
assertThat(resources.nextElement().getPath()).endsWith("test.txt");
}
@Test
public void resourcesNotDoubled() throws Exception {
final MockClassLoader mockClassLoader = new MockClassLoader(new String[0]);
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
// MockClassLoader will only be able to find 'foo/bar/baz/test.txt' if it
// properly defers the resources lookup to its deferTo ClassLoader.
Enumeration<URL> resources = mockClassLoader.getResources("org/powermock/core/classloader/foo/bar/baz/test.txt");
assertThat(resources.nextElement().getPath()).endsWith("test.txt");
assertThat(resources.hasMoreElements()).isFalse();
}
@Test
public void canFindDynamicClassFromAdjustedClasspath() throws Exception {
// Construct MockClassLoader with @UseClassPathAdjuster annotation.
// It activates our MyClassPathAdjuster class which appends our dynamic
// class to the MockClassLoader's classpool.
UseClassPathAdjuster useClassPathAdjuster = new UseClassPathAdjuster() {
public Class<? extends Annotation> annotationType() {
return UseClassPathAdjuster.class;
}
public Class<? extends ClassPathAdjuster> value() {
return MyClassPathAdjuster.class;
}
};
final MockClassLoader mockClassLoader = new MockClassLoader(new String[0], useClassPathAdjuster);
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
// setup custom classloader providing our dynamic class, for MockClassLoader to defer to
mockClassLoader.deferTo = new ClassLoader(getClass().getClassLoader()) {
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException {
if (name.equals(DynamicClassHolder.clazz.getName())) {
return DynamicClassHolder.clazz;
}
return super.loadClass(name);
}
};
// verify that MockClassLoader can successfully load the class
Class<?> dynamicTestClass = Class.forName(DynamicClassHolder.clazz.getName(), false, mockClassLoader);
assertThat(dynamicTestClass).isNotSameAs(DynamicClassHolder.clazz);
}
@Test(expected = ClassNotFoundException.class)
public void cannotFindDynamicClassInDeferredClassLoader() throws Exception {
MockClassLoader mockClassLoader = new MockClassLoader(new String[0]);
List<MockTransformer> list = new LinkedList<MockTransformer>();
list.add(new ClassMockTransformer());
mockClassLoader.setMockTransformerChain(list);
// setup custom classloader providing our dynamic class, for MockClassLoader to defer to
mockClassLoader.deferTo = new ClassLoader(getClass().getClassLoader()) {
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException {
return super.loadClass(name);
}
};
//Try to locate and load a class that is not in MockClassLoader.
Class.forName(DynamicClassHolder.clazz.getName(), false, mockClassLoader);
}
@Test
public void canLoadDefinedClass() throws Exception {
final String className = "my.ABCTestClass";
final MockClassLoader mockClassLoader = new MockClassLoader(new String[]{className});
Whitebox.invokeMethod(mockClassLoader, "defineClass", className, DynamicClassHolder.classBytes,
0, DynamicClassHolder.classBytes.length, this.getClass().getProtectionDomain());
Class.forName(className, false, mockClassLoader);
mockClassLoader.loadClass(className);
}
// helper class for canFindDynamicClassFromAdjustedClasspath()
static class MyClassPathAdjuster implements ClassPathAdjuster {
public void adjustClassPath(ClassPool classPool) {
classPool.appendClassPath(new ByteArrayClassPath(DynamicClassHolder.clazz.getName(), DynamicClassHolder.classBytes));
}
}
// helper class for canFindDynamicClassFromAdjustedClasspath()
static class DynamicClassHolder {
final static byte[] classBytes;
final static Class<?> clazz;
static {
try {
// construct a new class dynamically
ClassPool cp = ClassPool.getDefault();
final CtClass ctClass = cp.makeClass("my.ABCTestClass");
classBytes = ctClass.toBytecode();
clazz = ctClass.toClass();
} catch (Exception e) {
throw new RuntimeException("Problem constructing custom class", e);
}
}
}
@SuppressWarnings("SameParameterValue")
static class MyClassloader extends MockClassLoader {
public MyClassloader(String[] classesToMock) {
super(classesToMock);
}
@Override
protected Class findClass(String name) throws ClassNotFoundException {
if (name.startsWith("java.lang")) {
return this.getClass().getClassLoader().loadClass(name);
}
return super.findClass(name);
}
public Class<?> findClassPublic(String s) throws ClassNotFoundException {
return findClass(s);
}
}
}