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