/*
* Copyright 2016 NAVER Corp.
*
* 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 com.navercorp.pinpoint.profiler.instrument;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentContext;
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.io.InputStream;
import java.lang.reflect.Method;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author jaehong.kim
*/
public class ASMAspectWeaverTest {
private final String ORIGINAL = "com.navercorp.pinpoint.profiler.instrument.mock.AspectOriginalClass";
private final String ORIGINAL_SUB = "com.navercorp.pinpoint.profiler.instrument.mock.AspectOriginalSubClass";
private final String ASPECT = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorClass";
private final String ASPECT_NO_EXTENTS = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorNoExtendClass";
private final String ASPECT_EXTENTS_SUB = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorExtendSubClass";
private final String ERROR_ASPECT1 = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorErrorClass";
private final String ERROR_ASPECT2 = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorError2Class";
private final String ERROR_ASPECT_INVALID_EXTENTS = "com.navercorp.pinpoint.profiler.instrument.mock.AspectInterceptorInvalidExtendClass";
private final InstrumentContext pluginContext = mock(InstrumentContext.class);
@Before
public void setUp() {
when(pluginContext.injectClass(any(ClassLoader.class), any(String.class))).thenAnswer(new Answer<Class<?>>() {
@Override
public Class<?> answer(InvocationOnMock invocation) throws Throwable {
ClassLoader loader = (ClassLoader) invocation.getArguments()[0];
String name = (String) invocation.getArguments()[1];
return loader.loadClass(name);
}
});
when(pluginContext.getResourceAsStream(any(ClassLoader.class), any(String.class))).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock invocation) throws Throwable {
ClassLoader loader = (ClassLoader) invocation.getArguments()[0];
String name = (String) invocation.getArguments()[1];
if(loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
return loader.getResourceAsStream(name);
}
});
}
@Test
public void weaving() throws Exception {
weaving(ORIGINAL, ASPECT);
weaving(ORIGINAL, ASPECT_NO_EXTENTS);
weaving(ORIGINAL, ASPECT_EXTENTS_SUB);
weaving(ORIGINAL_SUB, ASPECT_EXTENTS_SUB);
weaving(ORIGINAL_SUB, ASPECT_NO_EXTENTS);
}
private void weaving(final String originalClass, final String adviceClass) throws Exception {
final Object instance = getInstnace(originalClass, adviceClass);
invoke(instance, "testVoid");
invoke(instance, "testInt");
invoke(instance, "testString");
invoke(instance, "testUtilMethod");
invoke(instance, "testNoTouch");
invoke(instance, "testInternalMethod");
invoke(instance, "testMethodCall");
}
@Test(expected = Exception.class)
public void invalidHierarchy() throws Exception {
weaving(ORIGINAL_SUB, ASPECT);
}
@Test(expected = Exception.class)
public void signatureMiss() throws Exception {
// not found method
getInstnace(ORIGINAL, ERROR_ASPECT1);
}
@Test(expected = Exception.class)
public void internalTypeMiss() throws Exception {
getInstnace(ORIGINAL, ERROR_ASPECT2);
}
@Test(expected = Exception.class)
public void invalidExtend() throws Exception {
// invalid class hierarchy.
getInstnace(ORIGINAL, ERROR_ASPECT_INVALID_EXTENTS);
}
private Object getInstnace(final String originalName, final String aspectName) throws Exception {
final ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
final ClassLoader classLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.equals(originalName)) {
try {
final ClassReader cr = new ClassReader(getClass().getResourceAsStream("/" + JavaAssistUtils.javaNameToJvmName(name) + ".class"));
final ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
final ASMClassNodeAdapter sourceClassNode = new ASMClassNodeAdapter(pluginContext, defaultClassLoader, classNode);
final ASMClassNodeAdapter adviceClassNode = ASMClassNodeAdapter.get(pluginContext, defaultClassLoader, JavaAssistUtils.javaNameToJvmName(aspectName));
final ASMAspectWeaver aspectWeaver = new ASMAspectWeaver();
aspectWeaver.weaving(sourceClassNode, adviceClassNode);
final ClassWriter cw = new ClassWriter(0);
classNode.accept(cw);
final byte[] bytecode = cw.toByteArray();
// CheckClassAdapter.verify(new ClassReader(bytecode), false, new PrintWriter(System.out));
return super.defineClass(name, bytecode, 0, bytecode.length);
} catch (Exception ex) {
throw new ClassNotFoundException("Load error: " + ex.toString(), ex);
}
} else {
return super.loadClass(name);
}
}
};
Class clazz = classLoader.loadClass(originalName);
return clazz.newInstance();
}
private Object invoke(Object o, String methodName, Object... args) {
try {
Class<?> clazz = o.getClass();
Method method = clazz.getMethod(methodName);
return method.invoke(o, args);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}