blob: 90f4c05b230e1fe71ca14a28310e50b1697ad084 [file] [log] [blame]
/*******************************************************************************
* 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.internal.analysis;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import org.jacoco.core.analysis.ILine;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.internal.analysis.filter.Filters;
import org.jacoco.core.internal.analysis.filter.IFilter;
import org.jacoco.core.internal.analysis.filter.IFilterOutput;
import org.jacoco.core.internal.flow.IProbeIdGenerator;
import org.jacoco.core.internal.flow.LabelFlowAnalyzer;
import org.jacoco.core.internal.flow.MethodProbesAdapter;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.util.CheckMethodAdapter;
/**
* Unit tests for {@link MethodAnalyzer}.
*/
public class MethodAnalyzerTest implements IProbeIdGenerator {
private int nextProbeId;
private boolean[] probes;
private MethodNode method;
private IMethodCoverage result;
@Before
public void setup() {
nextProbeId = 0;
method = new MethodNode();
method.tryCatchBlocks = new ArrayList<TryCatchBlockNode>();
probes = new boolean[32];
}
public int nextId() {
return nextProbeId++;
}
// === Scenario: linear Sequence without branches ===
private void createLinearSequence() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitInsn(Opcodes.NOP);
final Label l1 = new Label();
method.visitLabel(l1);
method.visitLineNumber(1002, l1);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testLinearSequenceNotCovered1() {
createLinearSequence();
runMethodAnalzer();
assertEquals(1, nextProbeId);
assertLine(1001, 1, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void testLinearSequenceNotCovered2() {
createLinearSequence();
probes = null;
runMethodAnalzer();
assertEquals(1, nextProbeId);
assertLine(1001, 1, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void testLinearSequenceCovered() {
createLinearSequence();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 1, 0, 0);
assertLine(1002, 0, 1, 0, 0);
}
// === Scenario: simple if branch ===
private void createIfBranch() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitVarInsn(Opcodes.ILOAD, 1);
Label l1 = new Label();
method.visitJumpInsn(Opcodes.IFEQ, l1);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitLdcInsn("a");
method.visitInsn(Opcodes.ARETURN);
method.visitLabel(l1);
method.visitLineNumber(1003, l1);
method.visitLdcInsn("b");
method.visitInsn(Opcodes.ARETURN);
}
@Test
public void testIfBranchNotCovered() {
createIfBranch();
runMethodAnalzer();
assertEquals(2, nextProbeId);
assertLine(1001, 2, 0, 2, 0);
assertLine(1002, 2, 0, 0, 0);
assertLine(1003, 2, 0, 0, 0);
}
@Test
public void testIfBranchCovered1() {
createIfBranch();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
assertLine(1002, 0, 2, 0, 0);
assertLine(1003, 2, 0, 0, 0);
}
@Test
public void testIfBranchCovered2() {
createIfBranch();
probes[1] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
assertLine(1002, 2, 0, 0, 0);
assertLine(1003, 0, 2, 0, 0);
}
@Test
public void testIfBranchCovered3() {
createIfBranch();
probes[0] = true;
probes[1] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 0, 2);
assertLine(1002, 0, 2, 0, 0);
assertLine(1003, 0, 2, 0, 0);
}
// === Scenario: branch before unconditional probe ===
/**
* Covers case of {@link MethodAnalyzer#visitProbe(int)} after
* {@link MethodAnalyzer#visitJumpInsnWithProbe(int, Label, int, org.jacoco.core.internal.flow.IFrame)}.
*/
private void createIfBranchBeforeProbe() {
final Label l0 = new Label();
final Label l1 = new Label();
final Label l2 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitVarInsn(Opcodes.ILOAD, 1);
method.visitJumpInsn(Opcodes.IFNE, l1);
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitMethodInsn(Opcodes.INVOKESTATIC, "Foo", "foo", "()V",
false);
method.visitLabel(l1);
method.visitLineNumber(1003, l1);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testIfBranchBeforeProbeNotCovered() {
createIfBranchBeforeProbe();
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 2, 0, 2, 0);
}
@Test
public void testIfBranchBeforeProbeCovered1() {
createIfBranchBeforeProbe();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
}
@Test
public void testIfBranchBeforeProbeCovered2() {
createIfBranchBeforeProbe();
probes[1] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
}
@Test
public void testIfBranchBeforeProbeCovered3() {
createIfBranchBeforeProbe();
probes[0] = true;
probes[1] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 0, 2);
}
// === Scenario: branch which merges back ===
private void createIfBranchMerge() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitVarInsn(Opcodes.ILOAD, 1);
Label l1 = new Label();
method.visitJumpInsn(Opcodes.IFEQ, l1);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitInsn(Opcodes.NOP);
method.visitLabel(l1);
method.visitLineNumber(1003, l1);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testIfBranchMergeNotCovered() {
createIfBranchMerge();
runMethodAnalzer();
assertEquals(3, nextProbeId);
assertLine(1001, 2, 0, 2, 0);
assertLine(1002, 1, 0, 0, 0);
assertLine(1003, 1, 0, 0, 0);
}
@Test
public void testIfBranchMergeCovered1() {
createIfBranchMerge();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
assertLine(1002, 1, 0, 0, 0);
assertLine(1003, 1, 0, 0, 0);
}
@Test
public void testIfBranchMergeCovered2() {
createIfBranchMerge();
probes[1] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 1, 1);
assertLine(1002, 0, 1, 0, 0);
assertLine(1003, 1, 0, 0, 0);
}
@Test
public void testIfBranchMergeCovered3() {
createIfBranchMerge();
probes[0] = true;
probes[1] = true;
probes[2] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 0, 2);
assertLine(1002, 0, 1, 0, 0);
assertLine(1003, 0, 1, 0, 0);
}
// === Scenario: branch which jump backwards ===
private void createJumpBackwards() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
final Label l1 = new Label();
method.visitJumpInsn(Opcodes.GOTO, l1);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitInsn(Opcodes.RETURN);
method.visitLabel(l1);
method.visitLineNumber(1003, l1);
method.visitJumpInsn(Opcodes.GOTO, l2);
}
@Test
public void testJumpBackwardsNotCovered() {
createJumpBackwards();
runMethodAnalzer();
assertEquals(1, nextProbeId);
assertLine(1001, 1, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
assertLine(1003, 1, 0, 0, 0);
}
@Test
public void testJumpBackwardsCovered() {
createJumpBackwards();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 1, 0, 0);
assertLine(1002, 0, 1, 0, 0);
assertLine(1003, 0, 1, 0, 0);
}
// === Scenario: jump to first instruction ===
private void createJumpToFirst() {
final Label l1 = new Label();
method.visitLabel(l1);
method.visitLineNumber(1001, l1);
method.visitVarInsn(Opcodes.ALOAD, 0);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Foo", "test", "()Z",
false);
method.visitJumpInsn(Opcodes.IFEQ, l1);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testJumpToFirstNotCovered() {
createJumpToFirst();
runMethodAnalzer();
assertEquals(2, nextProbeId);
assertLine(1001, 3, 0, 2, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void testJumpToFirstCovered1() {
createJumpToFirst();
probes[0] = true;
runMethodAnalzer();
assertEquals(2, nextProbeId);
assertLine(1001, 0, 3, 1, 1);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void testJumpToFirstCovered2() {
createJumpToFirst();
probes[0] = true;
probes[1] = true;
runMethodAnalzer();
assertEquals(2, nextProbeId);
assertLine(1001, 0, 3, 0, 2);
assertLine(1002, 0, 1, 0, 0);
}
// === Scenario: table switch ===
private void createTableSwitch() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitVarInsn(Opcodes.ILOAD, 1);
Label l1 = new Label();
Label l2 = new Label();
Label l3 = new Label();
method.visitTableSwitchInsn(1, 3, l3, new Label[] { l1, l2, l1 });
method.visitLabel(l1);
method.visitLineNumber(1002, l1);
method.visitIntInsn(Opcodes.BIPUSH, 11);
method.visitVarInsn(Opcodes.ISTORE, 2);
final Label l4 = new Label();
method.visitLabel(l4);
method.visitLineNumber(1003, l4);
Label l5 = new Label();
method.visitJumpInsn(Opcodes.GOTO, l5);
method.visitLabel(l2);
method.visitLineNumber(1004, l2);
method.visitIntInsn(Opcodes.BIPUSH, 22);
method.visitVarInsn(Opcodes.ISTORE, 2);
final Label l6 = new Label();
method.visitLabel(l6);
method.visitLineNumber(1005, l6);
method.visitJumpInsn(Opcodes.GOTO, l5);
method.visitLabel(l3);
method.visitLineNumber(1006, l3);
method.visitInsn(Opcodes.ICONST_0);
method.visitVarInsn(Opcodes.ISTORE, 2);
method.visitLabel(l5);
method.visitLineNumber(1007, l5);
method.visitVarInsn(Opcodes.ILOAD, 2);
method.visitInsn(Opcodes.IRETURN);
}
@Test
public void testTableSwitchNotCovered() {
createTableSwitch();
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 2, 0, 3, 0);
assertLine(1002, 2, 0, 0, 0);
assertLine(1003, 1, 0, 0, 0);
assertLine(1004, 2, 0, 0, 0);
assertLine(1005, 1, 0, 0, 0);
assertLine(1006, 2, 0, 0, 0);
assertLine(1007, 2, 0, 0, 0);
}
@Test
public void testTableSwitchCovered1() {
createTableSwitch();
probes[0] = true;
probes[3] = true;
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 0, 2, 2, 1);
assertLine(1002, 0, 2, 0, 0);
assertLine(1003, 0, 1, 0, 0);
assertLine(1004, 2, 0, 0, 0);
assertLine(1005, 1, 0, 0, 0);
assertLine(1006, 2, 0, 0, 0);
assertLine(1007, 0, 2, 0, 0);
}
@Test
public void testTableSwitchCovered2() {
createTableSwitch();
probes[2] = true;
probes[3] = true;
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 0, 2, 2, 1);
assertLine(1002, 2, 0, 0, 0);
assertLine(1003, 1, 0, 0, 0);
assertLine(1004, 2, 0, 0, 0);
assertLine(1005, 1, 0, 0, 0);
assertLine(1006, 0, 2, 0, 0);
assertLine(1007, 0, 2, 0, 0);
}
@Test
public void testTableSwitchCovered3() {
createTableSwitch();
probes[0] = true;
probes[1] = true;
probes[2] = true;
probes[3] = true;
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 0, 2, 0, 3);
assertLine(1002, 0, 2, 0, 0);
assertLine(1003, 0, 1, 0, 0);
assertLine(1004, 0, 2, 0, 0);
assertLine(1005, 0, 1, 0, 0);
assertLine(1006, 0, 2, 0, 0);
assertLine(1007, 0, 2, 0, 0);
}
// === Scenario: table switch with merge ===
private void createTableSwitchMerge() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitInsn(Opcodes.ICONST_0);
method.visitVarInsn(Opcodes.ISTORE, 2);
final Label l1 = new Label();
method.visitLabel(l1);
method.visitLineNumber(1002, l1);
method.visitVarInsn(Opcodes.ILOAD, 1);
Label l2 = new Label();
Label l3 = new Label();
Label l4 = new Label();
method.visitTableSwitchInsn(1, 3, l4, new Label[] { l2, l3, l2 });
method.visitLabel(l2);
method.visitLineNumber(1003, l2);
method.visitIincInsn(2, 1);
method.visitLabel(l3);
method.visitLineNumber(1004, l3);
method.visitIincInsn(2, 1);
method.visitLabel(l4);
method.visitLineNumber(1005, l4);
method.visitVarInsn(Opcodes.ILOAD, 2);
method.visitInsn(Opcodes.IRETURN);
}
@Test
public void testTableSwitchMergeNotCovered() {
createTableSwitchMerge();
runMethodAnalzer();
assertEquals(5, nextProbeId);
assertLine(1001, 2, 0, 0, 0);
assertLine(1002, 2, 0, 3, 0);
assertLine(1003, 1, 0, 0, 0);
assertLine(1004, 1, 0, 0, 0);
assertLine(1005, 2, 0, 0, 0);
}
@Test
public void testTableSwitchMergeNotCovered1() {
createTableSwitchMerge();
probes[0] = true;
probes[4] = true;
runMethodAnalzer();
assertEquals(5, nextProbeId);
assertLine(1001, 0, 2, 0, 0);
assertLine(1002, 0, 2, 2, 1);
assertLine(1003, 1, 0, 0, 0);
assertLine(1004, 1, 0, 0, 0);
assertLine(1005, 0, 2, 0, 0);
}
@Test
public void testTableSwitchMergeNotCovered2() {
createTableSwitchMerge();
probes[1] = true;
probes[3] = true;
probes[4] = true;
runMethodAnalzer();
assertEquals(5, nextProbeId);
assertLine(1001, 0, 2, 0, 0);
assertLine(1002, 0, 2, 2, 1);
assertLine(1003, 1, 0, 0, 0);
assertLine(1004, 0, 1, 0, 0);
assertLine(1005, 0, 2, 0, 0);
}
@Test
public void testTableSwitchMergeNotCovered3() {
createTableSwitchMerge();
probes[2] = true;
probes[3] = true;
probes[4] = true;
runMethodAnalzer();
assertEquals(5, nextProbeId);
assertLine(1001, 0, 2, 0, 0);
assertLine(1002, 0, 2, 2, 1);
assertLine(1003, 0, 1, 0, 0);
assertLine(1004, 0, 1, 0, 0);
assertLine(1005, 0, 2, 0, 0);
}
@Test
public void testTableSwitchMergeNotCovered4() {
createTableSwitchMerge();
probes[0] = true;
probes[1] = true;
probes[2] = true;
probes[3] = true;
probes[4] = true;
runMethodAnalzer();
assertEquals(5, nextProbeId);
assertLine(1001, 0, 2, 0, 0);
assertLine(1002, 0, 2, 0, 3);
assertLine(1003, 0, 1, 0, 0);
assertLine(1004, 0, 1, 0, 0);
assertLine(1005, 0, 2, 0, 0);
}
// === Scenario: try/catch block ===
private void createTryCatchBlock() {
Label l1 = new Label();
Label l2 = new Label();
Label l3 = new Label();
Label l4 = new Label();
method.visitTryCatchBlock(l1, l2, l3, "java/lang/Exception");
method.visitLabel(l1);
method.visitLineNumber(1001, l1);
method.visitVarInsn(Opcodes.ALOAD, 0);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
"printStackTrace", "()V", false);
method.visitLabel(l2);
method.visitJumpInsn(Opcodes.GOTO, l4);
method.visitLabel(l3);
method.visitLineNumber(1002, l3);
method.visitVarInsn(Opcodes.ASTORE, 1);
method.visitLabel(l4);
method.visitLineNumber(1004, l4);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testTryCatchBlockNotCovered() {
createTryCatchBlock();
runMethodAnalzer();
assertEquals(3, nextProbeId);
assertEquals(CounterImpl.getInstance(5, 0),
result.getInstructionCounter());
assertLine(1001, 3, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
assertLine(1004, 1, 0, 0, 0);
}
@Test
public void testTryCatchBlockFullyCovered() {
createTryCatchBlock();
probes[0] = true;
probes[1] = true;
probes[2] = true;
runMethodAnalzer();
assertEquals(3, nextProbeId);
assertEquals(CounterImpl.getInstance(0, 5),
result.getInstructionCounter());
assertLine(1001, 0, 3, 0, 0);
assertLine(1002, 0, 1, 0, 0);
assertLine(1004, 0, 1, 0, 0);
}
// === Scenario: try/finally ===
private void createTryFinally() {
final Label l0 = new Label();
final Label l1 = new Label();
final Label l2 = new Label();
final Label l3 = new Label();
method.visitTryCatchBlock(l0, l2, l2, null);
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitJumpInsn(Opcodes.IFEQ, l1);
// probe[0]
method.visitInsn(Opcodes.RETURN);
method.visitLabel(l1);
// probe[1]
method.visitInsn(Opcodes.RETURN);
method.visitLabel(l2);
method.visitJumpInsn(Opcodes.IFEQ, l3);
// probe[2]
method.visitInsn(Opcodes.RETURN);
method.visitLabel(l3);
// probe[3]
method.visitInsn(Opcodes.RETURN);
}
@Test
public void testTryFinallyWithoutFilter() {
createTryFinally();
probes[0] = true;
probes[3] = true;
runMethodAnalzer();
assertEquals(4, nextProbeId);
assertLine(1001, 2, 4, 2, 2);
}
private static final IFilter TRY_FINALLY_FILTER = new IFilter() {
public void filter(final String className, final String superClassName,
final MethodNode methodNode, final IFilterOutput output) {
final AbstractInsnNode i1 = methodNode.instructions.get(2);
final AbstractInsnNode i2 = methodNode.instructions.get(7);
assertEquals(Opcodes.IFEQ, i1.getOpcode());
assertEquals(Opcodes.IFEQ, i2.getOpcode());
output.merge(i1, i2);
// Merging of already merged instructions won't change result:
output.merge(i1, i2);
}
};
@Test
public void testTryFinallyMergeSameBranch() {
createTryFinally();
probes[0] = true;
probes[2] = true;
runMethodAnalzer(TRY_FINALLY_FILTER);
assertLine(1001, 2, 3, 1, 1);
}
@Test
public void testTryFinallyMergeDifferentBranches() {
createTryFinally();
probes[0] = true;
probes[3] = true;
runMethodAnalzer(TRY_FINALLY_FILTER);
assertLine(1001, 2, 3, 0, 2);
}
private void runMethodAnalzer() {
runMethodAnalzer(Filters.NONE);
}
private void runMethodAnalzer(IFilter filter) {
LabelFlowAnalyzer.markLabels(method);
final MethodAnalyzer analyzer = new MethodAnalyzer("Foo",
"java/lang/Object", "doit", "()V", null, probes, filter);
final MethodProbesAdapter probesAdapter = new MethodProbesAdapter(
analyzer, this);
// note that CheckMethodAdapter verifies that this test does not violate
// contracts of ASM API
analyzer.accept(method, new CheckMethodAdapter(probesAdapter));
result = analyzer.getCoverage();
}
private void assertLine(int nr, int insnMissed, int insnCovered,
int branchesMissed, int branchesCovered) {
final ILine line = result.getLine(nr);
assertEquals("Instructions in line " + nr,
CounterImpl.getInstance(insnMissed, insnCovered),
line.getInstructionCounter());
assertEquals("Branches in line " + nr,
CounterImpl.getInstance(branchesMissed, branchesCovered),
line.getBranchCounter());
}
}