package com.yammer.telemetry.agent; import com.yammer.telemetry.instrumentation.ClassInstrumentationHandler; import com.yammer.telemetry.agent.handlers.SubTypeInstrumentationHandler; import com.yammer.telemetry.test.TransformingClassLoader; import com.yammer.telemetry.instrumentation.TelemetryTransformer; import javassist.*; import org.junit.Test; import java.io.IOException; import static com.yammer.telemetry.agent.TelemetryTransformerTest.ClassUtils.wrapMethod; import static org.junit.Assert.*; public class TelemetryTransformerTest { @Test public void testUnmodifiedWhenNoHandlersAdded() throws Exception { TelemetryTransformer transformer = new TelemetryTransformer(); assertNull(transformer.transform(getClass().getClassLoader(), "com/yammer/telemetry/agent/test/SimpleBean", null, null, new byte[0])); } @Test public void testUnmodifiedWhenNotHandled() throws Exception { TelemetryTransformer transformer = new TelemetryTransformer(); transformer.addHandler(new ClassInstrumentationHandler() { @Override public boolean transformed(CtClass cc, ClassPool pool) { return false; } }); assertNull(transformer.transform(getClass().getClassLoader(), "com/yammer/telemetry/agent/test/SimpleBean", null, null, new byte[0])); } @Test public void testModifiedWhenHandled() throws Exception { TelemetryTransformer transformer = new TelemetryTransformer(); transformer.addHandler(new ClassInstrumentationHandler() { @Override public boolean transformed(CtClass cc, ClassPool pool) { return true; } }); // Note we didn't actually change anything but claimed we did so expect input & output bytes to match assertArrayEquals(new byte[0], transformer.transform(getClass().getClassLoader(), "com/yammer/telemetry/agent/test/SimpleBean", null, null, new byte[0])); } @Test public void testModificationViaClassLoader() throws Exception { TelemetryTransformer transformer = new TelemetryTransformer(); transformer.addHandler(new SubTypeInstrumentationHandler("com.yammer.telemetry.agent.test.SimpleBean") { @Override protected boolean transform(CtClass cc, ClassPool pool) throws NotFoundException, CannotCompileException, IOException { return wrapMethod(cc, "getValue", "{ return $proceed() + \"bar\"; }"); } }); // Ugh we need to do this via reflection to avoid loading the class first.. try (TransformingClassLoader loader = new TransformingClassLoader(transformer)) { Class<?> aClass = loader.loadClass("com.yammer.telemetry.agent.test.SimpleBean"); String value = "foo"; Object bean = aClass.newInstance(); aClass.getDeclaredMethod("setValue", String.class).invoke(bean, value); Object result = aClass.getDeclaredMethod("getValue").invoke(bean); assertEquals(String.format("%sbar", value), result); } } public static class ClassUtils { public static boolean wrapMethod(CtClass cc, String method, String body) { try { if (cc.isFrozen()) cc.defrost(); CtMethod getMethod = cc.getDeclaredMethod(method); CtMethod copiedMethod = CtNewMethod.copy(getMethod, cc.makeUniqueName(method), cc, null); copiedMethod.setModifiers(Modifier.PRIVATE); cc.addMethod(copiedMethod); getMethod.setBody(body, "this", copiedMethod.getName()); return true; } catch (NotFoundException | CannotCompileException e) { throw new RuntimeException(e); } } } }