/* * Copyright 2012 Kantega AS * * 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.kantega.revoc.instrumentation; import org.kantega.helloworld.HelloWorld; import org.kantega.revoc.instrumentation.testclasses.ClassWithLongMethod; import org.kantega.revoc.instrumentation.testclasses.SimpleClass; import org.kantega.revoc.instrumentation.testclasses.SyntheticClass; import org.kantega.revoc.registry.CoverageData; import org.kantega.revoc.registry.Registry; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.util.TraceClassVisitor; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import static org.kantega.revoc.demo.ClassUtils.invokeMainMethodUsingReflection; import static org.junit.Assert.*; /** * */ public class CoverageClassVisitorTest { @Before public void setup() { Registry.resetRegistry(); } @Test public void shouldRegisterLineAndBranchPoints() throws IOException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException { final Class clazz = SimpleClass.class; CoverageClassVisitor visitor = new InstrumentationTemplate(clazz).run(); int classId = visitor.getClassId(); // Then final String vmClassName = clazz.getName().replace('.', '/'); assertEquals(vmClassName, visitor.getClassName()); assertFalse(visitor.isInterface()); assertEquals(1, visitor.getInnerClasses().size()); assertEquals(vmClassName + "$InnerClass", visitor.getInnerClasses().get(0)); assertEquals(9, visitor.getExistingLines().cardinality()); assertTrue("Expected HelloWorld to have code on line 22", visitor.getExistingLines().get(22)); assertTrue("Expected HelloWorld to have code on line 25", visitor.getExistingLines().get(25)); assertTrue("Expected HelloWorld to have code on line 26", visitor.getExistingLines().get(26)); assertTrue("Expected HelloWorld to have code on line 28", visitor.getExistingLines().get(28)); assertTrue("Expected HelloWorld to have code on line 29", visitor.getExistingLines().get(29)); assertTrue("Expected HelloWorld to have code on line 31", visitor.getExistingLines().get(31)); CoverageData data = Registry.getCoverageData(); assertEquals(-1, data.getLinesVisited(classId)[20]); assertEquals(1, data.getLinesVisited(classId)[21]); assertTrue(data.getLinesVisitTimes(classId)[21] != 0); assertEquals(11, data.getLinesVisited(classId)[24]); assertEquals(10, data.getLinesVisited(classId)[25]); assertEquals(1, data.getLinesVisited(classId)[27]); assertTrue(data.getLinesVisitTimes(classId)[27] != 0); assertEquals(0, data.getLinesVisited(classId)[28]); assertEquals(0, data.getLinesVisitTimes(classId)[28]); assertEquals(1, data.getLinesVisited(classId)[30]); assertEquals(1, data.getLinesVisited(classId)[31]); assertEquals(data.getLinesVisitTimes(classId)[30], data.getLinesVisitTimes(classId)[31]); } @Test public void shouldWorkWithLargeMethod() throws IOException, InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException { // Want it to work with or without tracking time or branches for (final boolean trackTime : new boolean[]{true, false}) { for (final boolean trackBranches : new boolean[]{false}) { Registry.resetRegistry(); final Class clazz = ClassWithLongMethod.class; CoverageClassVisitor visitor = new InstrumentationTemplate(clazz) { protected void configureClassVisitor(CoverageClassVisitor visitor) { visitor.setTrackTime(trackTime); visitor.setTrackBranches(trackBranches); } }.run(); int classId = visitor.getClassId(); // Then assertEquals(377, visitor.getExistingLines().cardinality()); CoverageData data = Registry.getCoverageData(); assertEquals(1, data.getLinesVisited(classId)[26]); assertEquals(0, data.getLinesVisited(classId)[27]); assertEquals(1, data.getLinesVisited(classId)[29]); assertEquals(0, data.getLinesVisited(classId)[30]); assertEquals(1, data.getLinesVisited(classId)[32]); assertEquals(1, data.getLinesVisited(classId)[33]); assertEquals(0, data.getLinesVisited(classId)[35]); if(trackTime) { assertTrue(0 != data.getLinesVisitTimes(classId)[26]); assertEquals(0, data.getLinesVisitTimes(classId)[27]); } if(trackBranches) { assertEquals(1, data.getBranchPointsForLine(classId, 26).length); assertEquals(0, data.getBranchPointsForLine(classId, 26)[0].getAfter()); assertEquals(1, data.getBranchPointsForLine(classId, 32)[0].getAfter()); } } } } @Test public void syntheticMethodsShouldNotBeImplemented() { final Class clazz = SyntheticClass.Inner.class; CoverageClassVisitor visitor = new InstrumentationTemplate(clazz) { @Override protected void executeCode(ClassWriter cw, Class clazz) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException { } }.run(); // Then line 11 in the synthetic method should not be registered assertFalse(visitor.getExistingLines().get(11)); } @Test @Ignore public void largeNumberOfClasses() { for(int i = 0; i <= Short.MAX_VALUE;i++) { int cid = Registry.registerClass("class" + i, getClass().getClassLoader(), "class" + i + ".java"); ArrayList<String> methodNames = new ArrayList<String>(); methodNames.add("<init>"); methodNames.add("main"); ArrayList<String> methodDescs = new ArrayList<String>(); methodDescs.add(""); methodDescs.add(""); Registry.registerMethods(0, methodNames, methodDescs, new HashMap<Integer, List<Integer>>()); } final Class clazz = SimpleClass.class; CoverageClassVisitor visitor = new InstrumentationTemplate(clazz).run(); assertEquals(Short.MAX_VALUE +1, visitor.getClassId()); } class InstrumentationTemplate { private final Class clazz; public InstrumentationTemplate(Class clazz) { this.clazz = clazz; } public CoverageClassVisitor run() { try { InputStream inputStream = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class"); int classId = Registry.newClassId(clazz.getName().replace('.', '/'), clazz.getClassLoader()); ClassReader cr = new ClassReader(inputStream); ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); CoverageClassVisitor visitor = new CoverageClassVisitor(new TraceClassVisitor(cw,new PrintWriter(System.out)), classId); configureClassVisitor(visitor); cr.accept(visitor, ClassReader.EXPAND_FRAMES); registerClassInfo(classId, visitor); executeCode(cw, clazz); return visitor; } catch (IOException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } protected void executeCode(ClassWriter cw, Class clazz) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException { invokeMainMethodUsingReflection(clazz.getName(), cw.toByteArray()); } protected void registerClassInfo(int classId, CoverageClassVisitor visitor) { Registry.registerClass(HelloWorld.class.getName(), HelloWorld.class.getClassLoader(), visitor.getSource()); Registry.registerLines(classId, visitor.getLineIndexes()); Registry.registerBranchPoints(classId, visitor.getBranchPoints()); Registry.registerMethods(classId, visitor.getMethodNames(), visitor.getMethodDescs(), visitor.getMethodLineNumbers()); } protected void configureClassVisitor(CoverageClassVisitor visitor) { } } }