/*******************************************************************************
* Copyright (c) 2009, 2023 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* 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 java.util.HashSet;
import java.util.Set;
import org.jacoco.core.analysis.ILine;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.data.ExecutionDataWriter;
import org.jacoco.core.internal.analysis.filter.FilterContextMock;
import org.jacoco.core.internal.analysis.filter.Filters;
import org.jacoco.core.internal.analysis.filter.IFilter;
import org.jacoco.core.internal.analysis.filter.IFilterContext;
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 with and without ignore filtering ===
private void createLinearSequence() {
final Label l0 = new Label();
method.visitLabel(l0);
method.visitLineNumber(1001, l0);
method.visitInsn(Opcodes.NOP);
method.visitInsn(Opcodes.NOP);
final Label l1 = new Label();
method.visitLabel(l1);
method.visitLineNumber(1002, l1);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void linear_instruction_sequence_should_create_1_probe() {
createLinearSequence();
runMethodAnalzer();
assertEquals(1, nextProbeId);
}
@Test
public void linear_instruction_sequence_should_show_missed_when_no_probe_is_executed() {
createLinearSequence();
runMethodAnalzer();
assertLine(1001, 2, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void linear_instruction_sequence_should_show_missed_when_probearray_is_null() {
createLinearSequence();
probes = null;
runMethodAnalzer();
assertLine(1001, 2, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void linear_instruction_sequence_should_show_covered_when_probe_is_executed() {
createLinearSequence();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 2, 0, 0);
assertLine(1002, 0, 1, 0, 0);
}
/** Filters the NOP instructions as ignored */
private static final IFilter NOP_FILTER = new IFilter() {
public void filter(final MethodNode methodNode,
final IFilterContext context, final IFilterOutput output) {
final AbstractInsnNode i1 = methodNode.instructions.get(2);
final AbstractInsnNode i2 = methodNode.instructions.get(3);
assertEquals(Opcodes.NOP, i1.getOpcode());
assertEquals(Opcodes.NOP, i2.getOpcode());
output.ignore(i1, i2);
}
};
@Test
public void linear_instruction_sequence_should_ignore_instructions_when_filter_is_applied() {
createLinearSequence();
probes[0] = true;
runMethodAnalzer(NOP_FILTER);
assertEquals(1002, result.getFirstLine());
assertEquals(1002, result.getLastLine());
assertLine(1001, 0, 0, 0, 0);
assertLine(1002, 0, 1, 0, 0);
}
// === Scenario: method invocation after zero line number
/**
* @see org.jacoco.core.internal.flow.LabelFlowAnalyzer#visitLineNumber(int,
* Label)
* @see org.jacoco.core.internal.instr.ZeroLineNumberTest
*/
private void createZeroLineNumber() {
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(0, l1);
method.visitMethodInsn(Opcodes.INVOKESTATIC, "Foo", "foo", "()V",
false);
method.visitInsn(Opcodes.NOP);
final Label l2 = new Label();
method.visitLabel(l2);
method.visitLineNumber(1002, l2);
method.visitInsn(Opcodes.RETURN);
}
@Test
public void zero_line_number_should_create_1_probe() {
createZeroLineNumber();
runMethodAnalzer();
assertEquals(1, nextProbeId);
// workaround for zero line number can be removed if needed
// during change of exec file version
assertEquals(0x1007, ExecutionDataWriter.FORMAT_VERSION);
}
@Test
public void zero_line_number_should_show_missed_when_no_probes_are_executed() {
createZeroLineNumber();
runMethodAnalzer();
assertLine(1001, 1, 0, 0, 0);
assertLine(0, 2, 0, 0, 0);
assertLine(1002, 1, 0, 0, 0);
}
@Test
public void zero_line_number_should_show_covered_when_probe_is_executed() {
createZeroLineNumber();
probes[0] = true;
runMethodAnalzer();
assertLine(1001, 0, 1, 0, 0);
assertLine(0, 0, 2, 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 if_branch_should_create_2_probes() {
createIfBranch();
runMethodAnalzer();
assertEquals(2, nextProbeId);
}
@Test
public void if_branch_should_show_missed_when_no_probes_are_executed() {
createIfBranch();
runMethodAnalzer();
assertLine(1001, 2, 0, 2, 0);
assertLine(1002, 2, 0, 0, 0);
assertLine(1003, 2, 0, 0, 0);
}
@Test
public void if_branch_should_show_partial_branch_coverage_when_probe_for_first_branch_is_executed() {
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 if_branch_should_show_partial_branch_coverage_when_probe_for_second_branch_is_executed() {
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 if_branch_should_show_full_branch_coverage_when_probes_for_both_branches_are_executed() {
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#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", "f