1 /******************************************************************************* 2 * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.runtime; 13 14 import static org.junit.Assert.assertEquals; 15 import static org.junit.Assert.assertSame; 16 17 import org.jacoco.core.JaCoCo; 18 import org.jacoco.core.instr.MethodRecorder; 19 import org.jacoco.core.internal.instr.InstrSupport; 20 import org.jacoco.core.test.TargetLoader; 21 import org.junit.Before; 22 import org.junit.BeforeClass; 23 import org.junit.Test; 24 import org.objectweb.asm.ClassWriter; 25 import org.objectweb.asm.Opcodes; 26 import org.objectweb.asm.Type; 27 import org.objectweb.asm.commons.GeneratorAdapter; 28 import org.objectweb.asm.commons.Method; 29 30 /** 31 * Unit tests for {@link OfflineInstrumentationAccessGenerator}. 32 */ 33 public class OfflineInstrumentationAccessGeneratorTest { 34 35 private IExecutionDataAccessorGenerator generator; 36 37 private static boolean[] probes; 38 39 // runtime stub getProbes(final long classid, final String classname, final int probecount)40 public static boolean[] getProbes(final long classid, 41 final String classname, final int probecount) { 42 return probes; 43 } 44 45 @BeforeClass setupClass()46 public static void setupClass() { 47 probes = new boolean[3]; 48 } 49 50 @Before setup()51 public void setup() { 52 String name = getClass().getName().replace('.', '/'); 53 generator = new OfflineInstrumentationAccessGenerator(name); 54 } 55 56 @Test testRuntimeAccess()57 public void testRuntimeAccess() throws Exception { 58 ITarget target = generateAndInstantiateClass(123); 59 assertSame(probes, target.get()); 60 } 61 62 @Test testRuntimeClassName()63 public void testRuntimeClassName() throws Exception { 64 generator = new OfflineInstrumentationAccessGenerator(); 65 MethodRecorder actual = new MethodRecorder(); 66 generator.generateDataAccessor(987654321, "foo/Bar", 17, 67 actual.getVisitor()); 68 69 MethodRecorder expected = new MethodRecorder(); 70 expected.getVisitor().visitLdcInsn(Long.valueOf(987654321)); 71 expected.getVisitor().visitLdcInsn("foo/Bar"); 72 expected.getVisitor().visitIntInsn(Opcodes.BIPUSH, 17); 73 String rtname = JaCoCo.RUNTIMEPACKAGE.replace('.', '/') + "/Offline"; 74 expected.getVisitor().visitMethodInsn(Opcodes.INVOKESTATIC, rtname, 75 "getProbes", "(JLjava/lang/String;I)[Z", false); 76 77 assertEquals(expected, actual); 78 } 79 80 /** 81 * Creates a new class with the given id, loads this class and instantiates 82 * it. The constructor of the generated class will request the probe array 83 * from the access generator under test. 84 */ generateAndInstantiateClass(int classid)85 private ITarget generateAndInstantiateClass(int classid) 86 throws InstantiationException, IllegalAccessException { 87 88 final String className = "org/jacoco/test/targets/RuntimeTestTarget_" 89 + classid; 90 Type classType = Type.getObjectType(className); 91 92 final ClassWriter writer = new ClassWriter(0); 93 writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null, 94 "java/lang/Object", 95 new String[] { Type.getInternalName(ITarget.class) }); 96 97 writer.visitField(InstrSupport.DATAFIELD_ACC, 98 InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC, null, 99 null); 100 101 // Constructor 102 GeneratorAdapter gen = new GeneratorAdapter(writer.visitMethod( 103 Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[0]), 104 Opcodes.ACC_PUBLIC, "<init>", "()V"); 105 gen.visitCode(); 106 gen.loadThis(); 107 gen.invokeConstructor(Type.getType(Object.class), new Method("<init>", 108 "()V")); 109 gen.loadThis(); 110 final int size = generator.generateDataAccessor(classid, className, 2, 111 gen); 112 gen.putStatic(classType, InstrSupport.DATAFIELD_NAME, 113 Type.getObjectType(InstrSupport.DATAFIELD_DESC)); 114 gen.returnValue(); 115 gen.visitMaxs(size + 1, 0); 116 gen.visitEnd(); 117 118 // get() 119 gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, 120 "get", "()[Z", null, new String[0]), Opcodes.ACC_PUBLIC, "get", 121 "()[Z"); 122 gen.visitCode(); 123 gen.getStatic(classType, InstrSupport.DATAFIELD_NAME, 124 Type.getObjectType(InstrSupport.DATAFIELD_DESC)); 125 gen.returnValue(); 126 gen.visitMaxs(1, 0); 127 gen.visitEnd(); 128 129 writer.visitEnd(); 130 131 final TargetLoader loader = new TargetLoader(); 132 return (ITarget) loader.add(className.replace('/', '.'), 133 writer.toByteArray()).newInstance(); 134 } 135 136 /** 137 * With this interface access read coverage data of the generated class. 138 */ 139 public interface ITarget { 140 141 /** 142 * Returns a reference to the probe array. 143 * 144 * @return the probe array 145 */ get()146 boolean[] get(); 147 148 } 149 150 } 151