/******************************************************************************* * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Marc R. Hoffmann - initial API and implementation * *******************************************************************************/ package org.jacoco.core.runtime; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.jacoco.core.internal.instr.InstrSupport; import org.jacoco.core.test.TargetLoader; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; /** * Abstract test base for {@link IRuntime} implementations. */ public abstract class RuntimeTestBase { private RuntimeData data; private IRuntime runtime; private TestStorage storage; abstract IRuntime createRuntime(); @Before public void setup() throws Exception { data = new RuntimeData(); runtime = createRuntime(); runtime.startup(data); storage = new TestStorage(); } @After public void shutdown() { runtime.shutdown(); } @Test public void testDataAccessor() throws InstantiationException, IllegalAccessException { ITarget t = generateAndInstantiateClass(1234); data.collect(storage, storage, false); storage.assertData(1234, t.get()); } @Test public void testNoLocalVariablesInDataAccessor() throws InstantiationException, IllegalAccessException { runtime.generateDataAccessor(1001, "Target", 5, new MethodVisitor( InstrSupport.ASM_API_VERSION) { @Override public void visitVarInsn(int opcode, int var) { fail("No usage of local variables allowed."); } }); } @Test public void testExecutionRecording() throws InstantiationException, IllegalAccessException { generateAndInstantiateClass(1001).a(); data.collect(storage, storage, false); storage.assertSize(1); final boolean[] data = storage.getData(1001).getProbes(); assertTrue(data[0]); assertFalse(data[1]); } @Test public void testLoadSameClassTwice() throws InstantiationException, IllegalAccessException { generateAndInstantiateClass(1001).a(); generateAndInstantiateClass(1001).b(); data.collect(storage, storage, false); storage.assertSize(1); final boolean[] data = storage.getData(1001).getProbes(); assertTrue(data[0]); assertTrue(data[1]); } /** * Creates a new class with the given id, loads this class and instantiates * it. The constructor of the generated class will request the probe array * from the runtime under test. */ private ITarget generateAndInstantiateClass(int classid) throws InstantiationException, IllegalAccessException { final String className = "org/jacoco/test/targets/RuntimeTestTarget_" + classid; Type classType = Type.getObjectType(className); final ClassWriter writer = new ClassWriter(0); writer.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", new String[] { Type.getInternalName(ITarget.class) }); writer.visitField(InstrSupport.DATAFIELD_ACC, InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC, null, null); // Constructor GeneratorAdapter gen = new GeneratorAdapter(writer.visitMethod( Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[0]), Opcodes.ACC_PUBLIC, "<init>", "()V"); gen.visitCode(); gen.loadThis(); gen.invokeConstructor(Type.getType(Object.class), new Method("<init>", "()V")); gen.loadThis(); final int size = runtime.generateDataAccessor(classid, className, 2, gen); gen.putStatic(classType, InstrSupport.DATAFIELD_NAME, Type.getObjectType(InstrSupport.DATAFIELD_DESC)); gen.returnValue(); gen.visitMaxs(size + 1, 0); gen.visitEnd(); // get() gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, "get", "()[Z", null, new String[0]), Opcodes.ACC_PUBLIC, "get", "()[Z"); gen.visitCode(); gen.getStatic(classType, InstrSupport.DATAFIELD_NAME, Type.getObjectType(InstrSupport.DATAFIELD_DESC)); gen.returnValue(); gen.visitMaxs(1, 0); gen.visitEnd(); // a() gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, "a", "()V", null, new String[0]), Opcodes.ACC_PUBLIC, "a", "()V"); gen.visitCode(); gen.getStatic(classType, InstrSupport.DATAFIELD_NAME, Type.getObjectType(InstrSupport.DATAFIELD_DESC)); gen.push(0); gen.push(1); gen.arrayStore(Type.BOOLEAN_TYPE); gen.returnValue(); gen.visitMaxs(3, 0); gen.visitEnd(); // b() gen = new GeneratorAdapter(writer.visitMethod(Opcodes.ACC_PUBLIC, "b", "()V", null, new String[0]), Opcodes.ACC_PUBLIC, "b", "()V"); gen.visitCode(); gen.getStatic(classType, InstrSupport.DATAFIELD_NAME, Type.getObjectType(InstrSupport.DATAFIELD_DESC)); gen.push(1); gen.push(1); gen.arrayStore(Type.BOOLEAN_TYPE); gen.returnValue(); gen.visitMaxs(3, 0); gen.visitEnd(); writer.visitEnd(); final TargetLoader loader = new TargetLoader(); return (ITarget) loader.add(className.replace('/', '.'), writer.toByteArray()).newInstance(); } /** * With this interface we modify and read coverage data of the generated * class. */ public interface ITarget { /** * Returns a reference to the probe array. * * @return the probe array */ boolean[] get(); /** * The implementation will mark probe 0 as executed */ void a(); /** * The implementation will mark probe 1 as executed */ void b(); } }