diff options
Diffstat (limited to 'org.jacoco.core.test/src/org/jacoco/core/internal/analysis')
40 files changed, 3019 insertions, 448 deletions
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/BundleCoverageImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/BundleCoverageImplTest.java index 82ad63fa..71d8e150 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/BundleCoverageImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/BundleCoverageImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassAnalyzerTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassAnalyzerTest.java index ec65035f..372c6023 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassAnalyzerTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassAnalyzerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassCoverageImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassCoverageImplTest.java index b219a878..7a53abf8 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassCoverageImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/ClassCoverageImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -93,7 +93,7 @@ public class ClassCoverageImplTest { assertEquals(CounterImpl.COUNTER_0_0, node.getInstructionCounter()); assertEquals(CounterImpl.COUNTER_0_0, node.getBranchCounter()); assertEquals(CounterImpl.COUNTER_0_0, node.getMethodCounter()); - assertEquals(CounterImpl.COUNTER_1_0, node.getClassCounter()); + assertEquals(CounterImpl.COUNTER_0_0, node.getClassCounter()); } @Test diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java index 8e8f769e..9e55820f 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/CounterImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionTest.java new file mode 100644 index 00000000..e7e167e2 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionTest.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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.Arrays; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests for {@link Instruction}. + */ +public class InstructionTest { + + private Instruction instruction; + + @Before + public void setup() { + instruction = new Instruction(123); + } + + @Test + public void getLine_should_return_line_number() { + assertEquals(123, instruction.getLine()); + } + + @Test + public void new_instance_should_have_no_coverage_and_no_branches() { + assertEquals(CounterImpl.COUNTER_1_0, + instruction.getInstructionCounter()); + assertEquals(CounterImpl.COUNTER_0_0, instruction.getBranchCounter()); + } + + @Test + public void addBranchWithInstruction_should_not_increment_branches_when_only_one_branch_is_added() { + instruction.addBranch(new Instruction(122), 0); + + assertEquals(CounterImpl.COUNTER_0_0, instruction.getBranchCounter()); + } + + @Test + public void addBranchWithInstruction_should_increment_branches_when_two_branches_are_added() { + instruction.addBranch(new Instruction(122), 0); + instruction.addBranch(new Instruction(123), 1); + + assertEquals(CounterImpl.getInstance(2, 0), + instruction.getBranchCounter()); + } + + @Test + public void addBranchWithInstruction_should_propagate_existing_coverage_status() { + final Instruction target = new Instruction(122); + target.addBranch(true, 0); + + instruction.addBranch(target, 0); + + assertEquals(CounterImpl.COUNTER_0_1, + instruction.getInstructionCounter()); + } + + @Test + public void addBranchWithProbe_should_increment_branches_when_covered() { + instruction.addBranch(true, 0); + instruction.addBranch(true, 1); + + assertEquals(CounterImpl.getInstance(0, 1), + instruction.getInstructionCounter()); + assertEquals(CounterImpl.getInstance(0, 2), + instruction.getBranchCounter()); + } + + @Test + public void addBranchWithProbe_should_increment_branches_when_not_covered() { + instruction.addBranch(false, 0); + instruction.addBranch(false, 1); + + assertEquals(CounterImpl.getInstance(1, 0), + instruction.getInstructionCounter()); + assertEquals(CounterImpl.getInstance(2, 0), + instruction.getBranchCounter()); + } + + @Test + public void addBranchWithProbe_should_increment_branches_when_partly_covered() { + instruction.addBranch(false, 0); + instruction.addBranch(true, 1); + + assertEquals(CounterImpl.getInstance(0, 1), + instruction.getInstructionCounter()); + assertEquals(CounterImpl.getInstance(1, 1), + instruction.getBranchCounter()); + } + + @Test + public void addBranchWithProbe_should_propagate_coverage_status_to_existing_predecessors() { + final Instruction i1 = new Instruction(124); + final Instruction i2 = new Instruction(125); + instruction.addBranch(i1, 3); + i1.addBranch(i2, 5); + + i2.addBranch(true, 8); + + assertEquals(CounterImpl.COUNTER_0_1, + instruction.getInstructionCounter()); + } + + @Test + public void addBranch_should_count_large_number_of_branches() { + for (int branch = 0; branch < 0x1000; branch++) { + instruction.addBranch(true, branch); + } + + assertEquals(CounterImpl.getInstance(0, 0x1000), + instruction.getBranchCounter()); + } + + @Test + public void addBranch_should_propagate_coverage_status_over_very_long_sequence() { + Instruction next = instruction; + for (int i = 0; i < 0x10000; i++) { + final Instruction insn = new Instruction(i); + next.addBranch(insn, 0); + next = insn; + } + next.addBranch(true, 0); + + assertEquals(CounterImpl.COUNTER_0_1, + instruction.getInstructionCounter()); + } + + @Test + public void merge_should_calculate_superset_of_covered_branches() { + final Instruction i1 = new Instruction(124); + i1.addBranch(false, 1); + i1.addBranch(false, 2); + i1.addBranch(true, 3); + i1.addBranch(true, 4); + final Instruction i2 = new Instruction(124); + i2.addBranch(false, 1); + i2.addBranch(true, 2); + i2.addBranch(false, 3); + i2.addBranch(true, 4); + + instruction = i1.merge(i2); + + assertEquals(CounterImpl.getInstance(1, 3), + instruction.getBranchCounter()); + } + + @Test + public void replaceBranches_should_calculate_coverage_on_new_branches() { + Instruction i1 = new Instruction(1); + Instruction i2 = new Instruction(2); + Instruction i3 = new Instruction(3); + i3.addBranch(true, 0); + + instruction = instruction.replaceBranches(Arrays.asList(i1, i2, i3)); + + assertEquals(CounterImpl.getInstance(2, 1), + instruction.getBranchCounter()); + } +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionsBuilderTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionsBuilderTest.java new file mode 100644 index 00000000..b938caa7 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/InstructionsBuilderTest.java @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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.Map; + +import org.jacoco.core.analysis.ISourceFileCoverage; +import org.jacoco.core.internal.flow.LabelInfo; +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.InsnNode; + +/** + * Unit tests for {@link InstructionsBuilder}. + */ +public class InstructionsBuilderTest { + + private InstructionsBuilder builder; + + @Before + public void setup() { + builder = new InstructionsBuilder(new boolean[] { false, true }); + } + + @Test + public void current_line_number_should_be_applied_to_instructions() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + + builder.setCurrentLine(10); + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + InsnNode i3 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i3); + + builder.setCurrentLine(20); + InsnNode i4 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i4); + + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(ISourceFileCoverage.UNKNOWN_LINE, map.get(i1).getLine()); + assertEquals(10, map.get(i2).getLine()); + assertEquals(10, map.get(i3).getLine()); + assertEquals(20, map.get(i4).getLine()); + } + + @Test + public void null_probearray_should_not_mark_instruction_as_covered() { + builder = new InstructionsBuilder(null); + + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + builder.addProbe(5, 0); + + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_1_0, + map.get(i1).getInstructionCounter()); + } + + @Test + public void unexecuted_probe_should_not_mark_instruction_as_covered() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + builder.addProbe(0, 0); + + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_1_0, + map.get(i1).getInstructionCounter()); + } + + @Test + public void executed_probe_should_mark_instruction_as_covered() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + builder.addProbe(1, 0); + + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_0_1, + map.get(i1).getInstructionCounter()); + } + + @Test + public void subsequent_instructions_should_be_linked_by_default() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + + // mark i2 as covered + builder.addProbe(1, 0); + + // coverage should be propagated to i1 + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_0_1, + map.get(i1).getInstructionCounter()); + } + + @Test + public void subsequent_instructions_should_not_be_linked_when_noSuccessor_was_called() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + builder.noSuccessor(); + + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + + // mark i2 as covered + builder.addProbe(1, 0); + + // coverage should not be propagated to i1 + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_1_0, + map.get(i1).getInstructionCounter()); + } + + @Test + public void subsequent_instructions_should_be_linked_after_label_marked_as_successor() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + + Label l = new Label(); + LabelInfo.setSuccessor(l); + builder.addLabel(l); + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + + // mark i2 as covered + builder.addProbe(1, 0); + + // coverage should be propagated to i1 + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_0_1, + map.get(i1).getInstructionCounter()); + } + + @Test + public void subsequent_instructions_should_not_be_linked_after_label_not_marked_as_successor() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + + builder.addLabel(new Label()); + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + + // mark i2 as covered + builder.addProbe(1, 0); + + // coverage should not be propagated to i1 + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_1_0, + map.get(i1).getInstructionCounter()); + } + + @Test + public void jumps_should_propagate_coverage_status() { + InsnNode i1 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i1); + Label l2 = new Label(); + builder.addJump(l2, 0); + + builder.addLabel(l2); + InsnNode i2 = new InsnNode(Opcodes.NOP); + builder.addInstruction(i2); + + // mark i2 as covered + builder.addProbe(1, 0); + + // coverage should be propagated to i1 + Map<AbstractInsnNode, Instruction> map = builder.getInstructions(); + assertEquals(CounterImpl.COUNTER_0_1, + map.get(i1).getInstructionCounter()); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/LineImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/LineImplTest.java index ffc3bfc1..115c72e6 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/LineImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/LineImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodAnalyzerTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodAnalyzerTest.java index c312e60c..01b29db4 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodAnalyzerTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodAnalyzerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -14,11 +14,15 @@ 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.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; @@ -109,8 +113,8 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { /** Filters the NOP instructions as ignored */ private static final IFilter NOP_FILTER = new IFilter() { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + 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()); @@ -125,6 +129,9 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { 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); } @@ -318,22 +325,24 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { public void if_branch_merge_should_show_partial_branch_coverage_when_probe_for_first_branch_is_executed() { createIfBranchMerge(); probes[0] = true; + probes[2] = true; runMethodAnalzer(); assertLine(1001, 0, 2, 1, 1); assertLine(1002, 1, 0, 0, 0); - assertLine(1003, 1, 0, 0, 0); + assertLine(1003, 0, 1, 0, 0); } @Test public void if_branch_merge_should_show_partial_branch_coverage_when_probe_for_second_branch_is_executed() { createIfBranchMerge(); probes[1] = true; + probes[2] = true; runMethodAnalzer(); assertLine(1001, 0, 2, 1, 1); assertLine(1002, 0, 1, 0, 0); - assertLine(1003, 1, 0, 0, 0); + assertLine(1003, 0, 1, 0, 0); } @Test @@ -447,7 +456,7 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { assertLine(1002, 0, 1, 0, 0); } - // === Scenario: table switch === + // === Scenario: table switch with and without replace filtering === private void createTableSwitch() { final Label l0 = new Label(); @@ -492,6 +501,41 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { assertEquals(4, nextProbeId); } + private static final IFilter SWITCH_FILTER = new IFilter() { + public void filter(final MethodNode methodNode, + final IFilterContext context, final IFilterOutput output) { + final AbstractInsnNode i = methodNode.instructions.get(3); + assertEquals(Opcodes.TABLESWITCH, i.getOpcode()); + final AbstractInsnNode t1 = methodNode.instructions.get(6); + assertEquals(Opcodes.BIPUSH, t1.getOpcode()); + final AbstractInsnNode t2 = methodNode.instructions.get(13); + assertEquals(Opcodes.BIPUSH, t2.getOpcode()); + + final Set<AbstractInsnNode> newTargets = new HashSet<AbstractInsnNode>(); + newTargets.add(t1); + newTargets.add(t2); + output.replaceBranches(i, newTargets); + } + }; + + @Test + public void table_switch_with_filter_should_show_2_branches_when_original_replaced() { + createTableSwitch(); + runMethodAnalzer(SWITCH_FILTER); + + assertLine(1001, 2, 0, 2, 0); + } + + @Test + public void table_switch_with_filter_should_show_full_branch_coverage_when_new_targets_covered() { + createTableSwitch(); + probes[0] = true; + probes[1] = true; + runMethodAnalzer(SWITCH_FILTER); + + assertLine(1001, 0, 2, 0, 2); + } + @Test public void table_switch_should_show_missed_when_no_probes_are_executed() { createTableSwitch(); @@ -708,13 +752,12 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { public void try_catch_should_show_exception_handler_missed_when_probe_is_not_executed() { createTryCatchBlock(); probes[0] = true; - probes[1] = true; - probes[0] = true; + probes[2] = true; runMethodAnalzer(); assertLine(1001, 0, 3, 0, 0); - assertLine(1002, 0, 1, 0, 0); - assertLine(1003, 1, 0, 0, 0); + assertLine(1002, 1, 0, 0, 0); + assertLine(1003, 0, 1, 0, 0); } @Test @@ -777,8 +820,8 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { } private static final IFilter TRY_FINALLY_FILTER = new IFilter() { - public void filter(final String className, final String superClassName, - final MethodNode methodNode, final IFilterOutput output) { + 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(7); assertEquals(Opcodes.IFEQ, i1.getOpcode()); @@ -845,14 +888,21 @@ public class MethodAnalyzerTest implements IProbeIdGenerator { private void runMethodAnalzer(IFilter filter) { LabelFlowAnalyzer.markLabels(method); - final MethodAnalyzer analyzer = new MethodAnalyzer("Foo", - "java/lang/Object", "doit", "()V", null, probes, filter); + InstructionsBuilder builder = new InstructionsBuilder(probes); + final MethodAnalyzer analyzer = new MethodAnalyzer(builder); + 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(); + + MethodCoverageImpl mc = new MethodCoverageImpl("doit", "V()", null); + MethodCoverageCalculator mcc = new MethodCoverageCalculator( + builder.getInstructions()); + filter.filter(method, new FilterContextMock(), mcc); + mcc.calculate(mc); + result = mc; } private void assertLine(int nr, int insnMissed, int insnCovered, diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageCalculatorTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageCalculatorTest.java new file mode 100644 index 00000000..306bc792 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageCalculatorTest.java @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import org.jacoco.core.analysis.ISourceFileCoverage; +import org.junit.Before; +import org.junit.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; + +/** + * Unit tests for {@link MethodCoverageCalculator}. + */ +public class MethodCoverageCalculatorTest { + + private Map<AbstractInsnNode, Instruction> instructions; + + // The purpose of this list is to link instruction nodes + private InsnList list; + + private MethodCoverageImpl coverage; + + @Before + public void setup() { + instructions = new HashMap<AbstractInsnNode, Instruction>(); + coverage = new MethodCoverageImpl("run", "()V", null); + list = new InsnList(); + } + + @Test + public void should_report_instructions() { + addInsn(1, true); + addInsn(2, true); + addInsn(2, false); + addInsn(3, false); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 0); + assertLine(2, 1, 1, 0, 0); + assertLine(3, 1, 0, 0, 0); + } + + @Test + public void should_report_instructions_with_branches() { + addInsn(1, false, false); + addInsn(2, false, false, true); + addInsn(3, false, true, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.calculate(coverage); + + assertLine(1, 1, 0, 2, 0); + assertLine(2, 0, 1, 2, 1); + assertLine(3, 0, 1, 1, 2); + } + + @Test + public void should_ignore_single_instruction() { + addInsn(1, true); + InsnNode i1 = addInsn(1, false); + addInsn(2, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.ignore(i1, i1); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 0); // only one instruction not filtered + assertLine(2, 0, 1, 0, 0); + } + + @Test + public void should_ignore_instruction_range() { + addInsn(1, true); + InsnNode i1 = addInsn(2, false); + addInsn(2, false); + addInsn(2, false); + addInsn(2, false); + InsnNode i2 = addInsn(2, false); + addInsn(3, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.ignore(i1, i2); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 0); + assertLine(2, 0, 0, 0, 0); // all instructions filtered in line 2 + assertLine(3, 0, 1, 0, 0); + } + + @Test + public void should_exclude_ignored_instructions_from_computation_of_first_and_last_lines() { + InsnNode i1 = addInsn(1, false); + addInsn(2, false); + InsnNode i3 = addInsn(3, false); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.ignore(i1, i1); + c.ignore(i3, i3); + c.calculate(coverage); + + assertEquals(2, coverage.getFirstLine()); + assertEquals(2, coverage.getLastLine()); + } + + @Test + public void should_merge_instructions() { + addInsn(1, true); + InsnNode i1 = addInsn(2, false, true); + InsnNode i2 = addInsn(2, true, false); + addInsn(3, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.merge(i1, i2); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 0); + assertLine(2, 0, 1, 0, 2); // one fully covered instruction left + assertLine(3, 0, 1, 0, 0); + } + + @Test + public void should_merge_multiple_instructions() { + InsnNode i1 = addInsn(1, true, false, false); + InsnNode i2 = addInsn(1, false, true, false); + InsnNode i3 = addInsn(1, false, false, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.merge(i1, i2); + c.merge(i2, i3); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 3); // one fully covered instruction left + } + + @Test + public void should_merge_instructions_redundant() { + addInsn(1, true); + InsnNode i1 = addInsn(2, false, true); + InsnNode i2 = addInsn(2, true, false); + addInsn(3, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.merge(i1, i2); + c.merge(i2, i1); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 0); + assertLine(2, 0, 1, 0, 2); // one fully covered instruction left + assertLine(3, 0, 1, 0, 0); + } + + @Test + public void should_replace_branches() { + InsnNode i1 = addInsn(1); + InsnNode i2 = addInsn(2, true); + InsnNode i3 = addInsn(2, true); + InsnNode i4 = addInsn(2, false); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.replaceBranches(i1, + new HashSet<AbstractInsnNode>(Arrays.asList(i2, i3, i4))); + c.calculate(coverage); + + assertLine(1, 0, 1, 1, 2); // branches coverage status replaced + assertLine(2, 1, 2, 0, 0); // still in place + } + + @Test + public void should_replace_branches_with_merged_instructions() { + InsnNode i1 = addInsn(1, false, false, false); + InsnNode i2 = addInsn(2, true); + InsnNode i3 = addInsn(2, false); + InsnNode i4 = addInsn(2, false); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.merge(i4, i3); + c.merge(i3, i2); + c.replaceBranches(i1, + new HashSet<AbstractInsnNode>(Arrays.asList(i2, i3, i4))); + c.calculate(coverage); + + assertLine(1, 0, 1, 0, 3); + } + + @Test + public void should_work_without_lines() { + addInsn(ISourceFileCoverage.UNKNOWN_LINE, false); + addInsn(ISourceFileCoverage.UNKNOWN_LINE, false); + addInsn(ISourceFileCoverage.UNKNOWN_LINE, true); + + MethodCoverageCalculator c = new MethodCoverageCalculator(instructions); + c.calculate(coverage); + + assertEquals(ISourceFileCoverage.UNKNOWN_LINE, coverage.getFirstLine()); + assertEquals(ISourceFileCoverage.UNKNOWN_LINE, coverage.getLastLine()); + assertEquals(CounterImpl.getInstance(2, 1), + coverage.getInstructionCounter()); + } + + private void assertLine(int idx, int mi, int ci, int mb, int cb) { + assertEquals("instructions", CounterImpl.getInstance(mi, ci), + coverage.getLine(idx).getInstructionCounter()); + assertEquals("branches", CounterImpl.getInstance(mb, cb), + coverage.getLine(idx).getBranchCounter()); + } + + private InsnNode addInsn(int line, boolean... branches) { + Instruction i = new Instruction(line); + int idx = 0; + for (boolean covered : branches) { + i.addBranch(covered, idx++); + } + InsnNode node = new InsnNode(Opcodes.NOP); + list.add(node); + instructions.put(node, i); + return node; + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageImplTest.java index fd04f448..5a100b0e 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/MethodCoverageImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/PackageCoverageTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/PackageCoverageTest.java index 76cf684a..1d81a027 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/PackageCoverageTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/PackageCoverageTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceFileCoverageImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceFileCoverageImplTest.java index 69183e85..ed83ebaf 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceFileCoverageImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceFileCoverageImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceNodeImplTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceNodeImplTest.java index 7e9a4233..25010428 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceNodeImplTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/SourceNodeImplTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/StringPoolTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/StringPoolTest.java index 18ca05d1..eca2460c 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/StringPoolTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/StringPoolTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java new file mode 100644 index 00000000..f659632d --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AbstractMatcherTest.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +/** + * Unit tests for {@link AbstractMatcher}. + */ +public class AbstractMatcherTest { + + private final AbstractMatcher matcher = new AbstractMatcher() { + }; + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "method_name", "()V", null, null); + + @Test + public void skipNonOpcodes() { + m.visitFrame(Opcodes.F_FULL, 0, null, 0, null); + final Label label = new Label(); + m.visitLabel(label); + m.visitLineNumber(42, label); + m.visitInsn(Opcodes.NOP); + + // should skip all non opcodes + matcher.cursor = m.instructions.getFirst(); + matcher.skipNonOpcodes(); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not change cursor when it points on instruction with opcode + matcher.skipNonOpcodes(); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.skipNonOpcodes(); + } + + @Test + public void nextIs() { + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.NOP); + + // should set cursor to null when opcode mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIs(Opcodes.ATHROW); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + matcher.cursor = m.instructions.getFirst(); + matcher.nextIs(Opcodes.NOP); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.nextIs(Opcodes.NOP); + } + + @Test + public void nextIsSwitch() { + // should set cursor to null when opcode mismatch + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.NOP); + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsSwitch(); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + m.instructions.clear(); + m.visitInsn(Opcodes.NOP); + m.visitTableSwitchInsn(0, 0, new Label()); + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsSwitch(); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should set cursor to next instruction when match + m.instructions.clear(); + m.visitInsn(Opcodes.NOP); + m.visitLookupSwitchInsn(new Label(), null, new Label[0]); + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsSwitch(); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.nextIsSwitch(); + } + + @Test + public void nextIsVar() { + m.visitInsn(Opcodes.NOP); + m.visitVarInsn(Opcodes.ILOAD, 42); + + // should set cursor to null when opcode mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsVar(Opcodes.ALOAD, "name"); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsVar(Opcodes.ILOAD, "name"); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should set cursor to null when var mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.vars.put("name", new VarInsnNode(Opcodes.ILOAD, 13)); + matcher.nextIsVar(Opcodes.ILOAD, "name"); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + matcher.cursor = m.instructions.getFirst(); + matcher.vars.put("name", new VarInsnNode(Opcodes.ILOAD, 42)); + matcher.nextIsVar(Opcodes.ILOAD, "name"); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.nextIsVar(Opcodes.ILOAD, "name"); + } + + @Test + public void nextIsInvoke() { + m.visitInsn(Opcodes.NOP); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "owner", "name", "()V", false); + + // should set cursor to null when opcode mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsInvoke(Opcodes.INVOKESTATIC, "owner", "name", "()V"); + assertNull(matcher.cursor); + + // should set cursor to null when owner mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsInvoke(Opcodes.INVOKEVIRTUAL, "another_owner", "name", + "()V"); + assertNull(matcher.cursor); + + // should set cursor to null when name mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsInvoke(Opcodes.INVOKEVIRTUAL, "owner", "another_name", + "()V"); + assertNull(matcher.cursor); + + // should set cursor to null when descriptor mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsInvoke(Opcodes.INVOKEVIRTUAL, "owner", "name", + "(Lanother_descriptor;)V"); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsInvoke(Opcodes.INVOKEVIRTUAL, "owner", "name", "()V"); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.nextIsInvoke(Opcodes.INVOKEVIRTUAL, "owner", "name", "()V"); + } + + @Test + public void nextIsType() { + m.visitInsn(Opcodes.NOP); + m.visitTypeInsn(Opcodes.NEW, "descriptor"); + + // should set cursor to null when opcode mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsType(Opcodes.CHECKCAST, "descriptor"); + assertNull(matcher.cursor); + + // should set cursor to null when descriptor mismatch + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsType(Opcodes.NEW, "another_descriptor"); + assertNull(matcher.cursor); + + // should set cursor to next instruction when match + matcher.cursor = m.instructions.getFirst(); + matcher.nextIsType(Opcodes.NEW, "descriptor"); + assertSame(m.instructions.getLast(), matcher.cursor); + + // should not do anything when cursor is null + matcher.cursor = null; + matcher.nextIsType(Opcodes.NEW, "descriptor"); + } + + @Test + public void firstIsALoad0() { + // should set cursor to null when opcode mismatch + m.visitInsn(Opcodes.NOP); + matcher.firstIsALoad0(m); + assertNull(matcher.cursor); + + // should set cursor to null when var mismatch + m.instructions.clear(); + m.visitVarInsn(Opcodes.ALOAD, 1); + matcher.firstIsALoad0(m); + assertNull(matcher.cursor); + + // should set cursor to first instruction when match + m.instructions.clear(); + m.visitVarInsn(Opcodes.ALOAD, 0); + matcher.firstIsALoad0(m); + assertSame(m.instructions.getLast(), matcher.cursor); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilterTest.java new file mode 100644 index 00000000..b8a0331b --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/AnnotationGeneratedFilterTest.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link AnnotationGeneratedFilter}. + */ +public class AnnotationGeneratedFilterTest extends FilterTestBase { + + private final IFilter filter = new AnnotationGeneratedFilter(); + + @Test + public void should_filter_methods_annotated_with_runtime_visible_org_groovy_transform_Generated() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitAnnotation("Lgroovy/transform/Generated;", true); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_filter_methods_annotated_with_runtime_invisible_lombok_Generated() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitAnnotation("Llombok/Generated;", false); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_filter_classes_annotated_with_runtime_visible_org_immutables_value_Generated() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + context.classAnnotations.add("Lorg/immutables/value/Generated;"); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_filter_classes_annotated_with_runtime_visible_org_apache_avro_specific_AvroGenerated() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "readExternal", "()V", null, null); + + m.visitInsn(Opcodes.NOP); + + context.classAnnotations + .add("Lorg/apache/avro/specific/AvroGenerated;"); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_filter_when_annotation_is_inner() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + context.classAnnotations.add("Lorg/example/Class$Generated;"); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_not_filter_when_no_annotations() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_other_annotations() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitAnnotation("LOtherAnnotation;", true); + + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + context.classAnnotations.add("LOtherAnnotation;"); + + filter.filter(m, context, output); + + assertIgnored(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java new file mode 100644 index 00000000..32bc9df3 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumEmptyConstructorFilterTest.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link EnumEmptyConstructorFilter}. + */ +public class EnumEmptyConstructorFilterTest extends FilterTestBase { + + private final EnumEmptyConstructorFilter filter = new EnumEmptyConstructorFilter(); + + @Test + public void should_filter() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.RETURN); + context.superClassName = "java/lang/Enum"; + + filter.filter(m, context, output); + + assertIgnored(new Range(m.instructions.getFirst(), m.instructions.getLast())); + } + + /** + * <code><pre> + * enum E { + * ; + * private E() { + * ... + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_non_empty_constructor() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.RETURN); + context.superClassName = "java/lang/Enum"; + + filter.filter(m, context, output); + + assertIgnored(); + } + + /** + * <code><pre> + * enum E { + * ; + * private E(long p) { + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_constructor_with_additional_parameters() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;IJ)V", null, + null); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Enum", "<init>", + "(Ljava/lang/String;I)V", false); + m.visitInsn(Opcodes.RETURN); + context.superClassName = "java/lang/Enum"; + + filter.filter(m, context, output); + + assertIgnored(); + } + + /** + * <code><pre> + * enum E { + * ; + * private void method(String p1, int p2) { + * } + * } + * </pre></code> + */ + @Test + public void should_not_filter_non_constructor() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "method", "(Ljava/lang/String;I)V", null, + null); + m.visitInsn(Opcodes.NOP); + context.superClassName = "java/lang/Enum"; + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_non_Enum() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, + null); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertIgnored(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumFilterTest.java index 9d7fb2ca..23f69f4e 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/EnumFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,33 +11,28 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - import org.jacoco.core.internal.instr.InstrSupport; import org.junit.Test; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -public class EnumFilterTest implements IFilterOutput { +/** + * Unit tests for {@link EnumFilter}. + */ +public class EnumFilterTest extends FilterTestBase { private final EnumFilter filter = new EnumFilter(); - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - @Test public void testValues() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "values", "()[LFoo;", null, null); m.visitInsn(Opcodes.NOP); + context.superClassName = "java/lang/Enum"; - filter.filter("Foo", "java/lang/Enum", m, this); + filter.filter(m, context, output); - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); + assertMethodIgnored(m); } @Test @@ -46,10 +41,9 @@ public class EnumFilterTest implements IFilterOutput { "values", "()V", null, null); m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Enum", m, this); + filter.filter(m, context, output); - assertNull(fromInclusive); - assertNull(toInclusive); + assertIgnored(); } @Test @@ -57,11 +51,11 @@ public class EnumFilterTest implements IFilterOutput { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "valueOf", "(Ljava/lang/String;)LFoo;", null, null); m.visitInsn(Opcodes.NOP); + context.superClassName = "java/lang/Enum"; - filter.filter("Foo", "java/lang/Enum", m, this); + filter.filter(m, context, output); - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); + assertMethodIgnored(m); } @Test @@ -69,11 +63,11 @@ public class EnumFilterTest implements IFilterOutput { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "valueOf", "()V", null, null); m.visitInsn(Opcodes.NOP); + context.superClassName = "java/lang/Enum"; - filter.filter("Foo", "java/lang/Enum", m, this); + filter.filter(m, context, output); - assertNull(fromInclusive); - assertNull(toInclusive); + assertIgnored(); } @Test @@ -82,21 +76,9 @@ public class EnumFilterTest implements IFilterOutput { "values", "()[LFoo;", null, null); m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(fromInclusive); - assertNull(toInclusive); - } - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertIgnored(); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterContextMock.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterContextMock.java new file mode 100644 index 00000000..a6c881d3 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterContextMock.java @@ -0,0 +1,48 @@ +/*******************************************************************************
+ * Copyright (c) 2009, 2019 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.filter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * {@link IFilterContext} mock for unit tests.
+ */
+public class FilterContextMock implements IFilterContext {
+
+ public String className = "Foo";
+ public String superClassName = "java/lang/Object";
+ public Set<String> classAnnotations = new HashSet<String>();
+ public String sourceFileName = "Foo.java";
+ public String sourceDebugExtension;
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getSuperClassName() {
+ return superClassName;
+ }
+
+ public Set<String> getClassAnnotations() {
+ return classAnnotations;
+ }
+
+ public String getSourceFileName() {
+ return sourceFileName;
+ }
+
+ public String getSourceDebugExtension() {
+ return sourceDebugExtension;
+ }
+
+}
diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterTestBase.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterTestBase.java new file mode 100644 index 00000000..2378bee3 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FilterTestBase.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * Base class for tests of {@link IFilter} implementations. + */ +public abstract class FilterTestBase { + + protected final FilterContextMock context = new FilterContextMock(); + + private final List<Range> ignoredRanges = new ArrayList<Range>(); + + private final Map<AbstractInsnNode, Set<AbstractInsnNode>> replacedBranches = new HashMap<AbstractInsnNode, Set<AbstractInsnNode>>(); + + protected final IFilterOutput output = new IFilterOutput() { + public void ignore(final AbstractInsnNode fromInclusive, + final AbstractInsnNode toInclusive) { + final Range range = new Range(); + range.fromInclusive = fromInclusive; + range.toInclusive = toInclusive; + ignoredRanges.add(range); + } + + public void merge(final AbstractInsnNode i1, + final AbstractInsnNode i2) { + fail(); + } + + public void replaceBranches(final AbstractInsnNode source, + final Set<AbstractInsnNode> newTargets) { + replacedBranches.put(source, newTargets); + } + }; + + final void assertIgnored(Range... ranges) { + assertArrayEquals(ranges, ignoredRanges.toArray(new Range[0])); + } + + final void assertMethodIgnored(final MethodNode m) { + assertIgnored( + new Range(m.instructions.getFirst(), m.instructions.getLast())); + } + + final void assertNoReplacedBranches() { + assertTrue(replacedBranches.isEmpty()); + } + + final void assertReplacedBranches(final AbstractInsnNode source, + final Set<AbstractInsnNode> newTargets) { + assertEquals(Collections.singletonMap(source, newTargets), + replacedBranches); + } + + static class Range { + AbstractInsnNode fromInclusive; + AbstractInsnNode toInclusive; + + Range() { + } + + Range(AbstractInsnNode fromInclusive, AbstractInsnNode toInclusive) { + this.fromInclusive = fromInclusive; + this.toInclusive = toInclusive; + } + + @Override + public boolean equals(Object obj) { + if (obj.getClass() == Range.class) { + final Range other = (Range) obj; + return this.fromInclusive.equals(other.fromInclusive) + && this.toInclusive.equals(other.toInclusive); + } + return false; + } + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FinallyFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FinallyFilterTest.java index 5e0ed6c9..6a9cf67f 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FinallyFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/FinallyFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -24,6 +24,9 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; +/** + * Unit tests for {@link FinallyFilter}. + */ public class FinallyFilterTest implements IFilterOutput { private final IFilter filter = new FinallyFilter(); @@ -383,7 +386,7 @@ public class FinallyFilterTest implements IFilterOutput { } private void execute() { - filter.filter("", "", m, this); + filter.filter(m, new FilterContextMock(), this); assertEquals("ignored", toIndexes(expectedIgnored), toIndexes(actualIgnored)); assertEquals("merged", toIndexes(expectedMerged), @@ -418,4 +421,9 @@ public class FinallyFilterTest implements IFilterOutput { } } + public void replaceBranches(final AbstractInsnNode source, + final Set<AbstractInsnNode> newTargets) { + fail(); + } + } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilterTest.java deleted file mode 100644 index 6cf0febe..00000000 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/GroovyGeneratedFilterTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.filter; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -import org.jacoco.core.internal.instr.InstrSupport; -import org.junit.Test; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodNode; - -public class GroovyGeneratedFilterTest implements IFilterOutput { - - private final IFilter filter = new GroovyGeneratedFilter(); - - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - - @Test - public void testNoAnnotations() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(fromInclusive); - assertNull(toInclusive); - } - - @Test - public void testOtherAnnotation() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - m.visitAnnotation("Lother/Annotation;", true); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(fromInclusive); - assertNull(toInclusive); - } - - @Test - public void testGroovyGeneratedAnnotation() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - m.visitAnnotation("Lgroovy/transform/Generated;", true); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); - } - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } - - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); - } - -} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java new file mode 100644 index 00000000..127ae309 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinCoroutineFilterTest.java @@ -0,0 +1,376 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit test for {@link KotlinCoroutineFilter}. + */ +public class KotlinCoroutineFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinCoroutineFilter(); + + @Test + public void should_filter_suspending_lambdas_generated_by_Kotlin_1_3_30() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "invokeSuspend", "(Ljava/lang/Object;)Ljava/lang/Object;", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLabel(new Label()); + final Range range1 = new Range(); + range1.fromInclusive = m.instructions.getLast(); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false); + m.visitVarInsn(Opcodes.ASTORE, 4); + + m.visitVarInsn(Opcodes.ALOAD, 0); + // line of "runBlocking" + m.visitFieldInsn(Opcodes.GETFIELD, "Target", "label", "I"); + final Label dflt = new Label(); + final Label state0 = new Label(); + final Label state1 = new Label(); + m.visitTableSwitchInsn(0, 1, dflt, state0, state1); + + m.visitLabel(state0); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/ResultKt", + "throwOnFailure", "(Ljava/lang/Object;)V", false); + range1.toInclusive = m.instructions.getLast(); + } + + // line before "suspendingFunction" + m.visitInsn(Opcodes.NOP); + + // line of "suspendingFunction" + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false); + + m.visitInsn(Opcodes.DUP); + final Range range2 = new Range(); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 4); + final Label continuationLabelAfterLoadedResult = new Label(); + m.visitJumpInsn(Opcodes.IF_ACMPNE, continuationLabelAfterLoadedResult); + // line of "runBlocking" + m.visitVarInsn(Opcodes.ALOAD, 4); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(state1); + + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Target", "I$0", "I"); + m.visitVarInsn(Opcodes.ISTORE, 3); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "kotlin/ResultKt", + "throwOnFailure", "(Ljava/lang/Object;)V", false); + } + m.visitVarInsn(Opcodes.ALOAD, 1); + range2.toInclusive = m.instructions.getLast(); + m.visitLabel(continuationLabelAfterLoadedResult); + + // line after "suspendingFunction" + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(dflt); + final Range range0 = new Range(); + range0.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("call to 'resume' before 'invoke' with coroutine"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "<init>", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ATHROW); + range0.toInclusive = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(range0, range1, range2); + } + + /** + * <pre> + * runBlocking { + * val x = 42 + * nop(x) + * suspendingFunction() + * nop(x) + * } + * </pre> + */ + @Test + public void should_filter_suspending_lambdas() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "invokeSuspend", "(Ljava/lang/Object;)Ljava/lang/Object;", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLabel(new Label()); + final Range range1 = new Range(); + range1.fromInclusive = m.instructions.getLast(); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false); + m.visitVarInsn(Opcodes.ASTORE, 4); + + m.visitVarInsn(Opcodes.ALOAD, 0); + // line of "runBlocking" + m.visitFieldInsn(Opcodes.GETFIELD, "Target", "label", "I"); + final Label dflt = new Label(); + final Label state0 = new Label(); + final Label state1 = new Label(); + m.visitTableSwitchInsn(0, 1, dflt, state0, state1); + + m.visitLabel(state0); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + range1.toInclusive = m.instructions.getLast(); + m.visitLabel(label); + } + + // line before "suspendingFunction" + m.visitInsn(Opcodes.NOP); + + // line of "suspendingFunction" + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false); + + m.visitInsn(Opcodes.DUP); + final Range range2 = new Range(); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 4); + final Label continuationLabelAfterLoadedResult = new Label(); + m.visitJumpInsn(Opcodes.IF_ACMPNE, continuationLabelAfterLoadedResult); + // line of "runBlocking" + m.visitVarInsn(Opcodes.ALOAD, 4); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(state1); + + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, "Target", "I$0", "I"); + m.visitVarInsn(Opcodes.ISTORE, 3); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + final Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + m.visitLabel(label); + } + m.visitVarInsn(Opcodes.ALOAD, 1); + range2.toInclusive = m.instructions.getLast(); + m.visitLabel(continuationLabelAfterLoadedResult); + + // line after "suspendingFunction" + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(dflt); + final Range range0 = new Range(); + range0.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("call to 'resume' before 'invoke' with coroutine"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "<init>", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ATHROW); + range0.toInclusive = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(range0, range1, range2); + } + + /** + * <pre> + * suspend fun example() { + * suspendingFunction() + * nop() + * } + * </pre> + */ + @Test + public void should_filter_suspending_functions() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_STATIC, "example", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + final int continuationArgumentIndex = 0; + final int continuationIndex = 2; + + m.visitVarInsn(Opcodes.ALOAD, continuationArgumentIndex); + final Range range1 = new Range(); + range1.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.INSTANCEOF, "ExampleKt$example$1"); + final Label createStateInstance = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, createStateInstance); + + m.visitVarInsn(Opcodes.ALOAD, continuationArgumentIndex); + m.visitTypeInsn(Opcodes.CHECKCAST, "ExampleKt$example$1"); + m.visitVarInsn(Opcodes.ASTORE, continuationIndex); + + m.visitVarInsn(Opcodes.ALOAD, continuationIndex); + m.visitFieldInsn(Opcodes.GETFIELD, "ExampleKt$example$1", "label", "I"); + + m.visitLdcInsn(Integer.valueOf(Integer.MIN_VALUE)); + m.visitInsn(Opcodes.IAND); + m.visitJumpInsn(Opcodes.IFEQ, createStateInstance); + + m.visitVarInsn(Opcodes.ALOAD, continuationIndex); + m.visitInsn(Opcodes.DUP); + m.visitFieldInsn(Opcodes.GETFIELD, "ExampleKt$example$1", "label", "I"); + + m.visitLdcInsn(Integer.valueOf(Integer.MIN_VALUE)); + m.visitInsn(Opcodes.ISUB); + m.visitFieldInsn(Opcodes.PUTFIELD, "ExampleKt$example$1", "label", "I"); + + final Label afterCoroutineStateCreated = new Label(); + m.visitJumpInsn(Opcodes.GOTO, afterCoroutineStateCreated); + + m.visitLabel(createStateInstance); + + m.visitTypeInsn(Opcodes.NEW, "ExampleKt$example$1"); + m.visitInsn(Opcodes.DUP); + m.visitVarInsn(Opcodes.ALOAD, continuationArgumentIndex); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "ExampleKt$example$1", + "<init>", "(Lkotlin/coroutines/Continuation;)V", false); + + m.visitVarInsn(Opcodes.ASTORE, continuationIndex); + + m.visitLabel(afterCoroutineStateCreated); + + m.visitVarInsn(Opcodes.ALOAD, continuationIndex); + m.visitFieldInsn(Opcodes.GETFIELD, "ExampleKt$example$1", "result", + "Ljava/lang/Object;"); + m.visitVarInsn(Opcodes.ASTORE, 1); + + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/coroutines/intrinsics/IntrinsicsKt", + "getCOROUTINE_SUSPENDED", "()Ljava/lang/Object;", false); + + // line of "fun" + m.visitVarInsn(Opcodes.ASTORE, 3); + + m.visitVarInsn(Opcodes.ALOAD, continuationIndex); + m.visitFieldInsn(Opcodes.GETFIELD, "ExampleKt$example$1", "label", "I"); + final Label dflt = new Label(); + final Label state0 = new Label(); + final Label state1 = new Label(); + m.visitTableSwitchInsn(0, 1, dflt, state0, state1); + + m.visitLabel(state0); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + range1.toInclusive = m.instructions.getLast(); + m.visitLabel(label); + } + + // line of "suspendingFunction" + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "", "suspendingFunction", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", false); + + m.visitInsn(Opcodes.DUP); + final Range range2 = new Range(); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ALOAD, 3); + final Label continuationLabelAfterLoadedResult = new Label(); + m.visitJumpInsn(Opcodes.IF_ACMPNE, continuationLabelAfterLoadedResult); + // line of "fun" + m.visitVarInsn(Opcodes.ALOAD, 3); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(state1); + + { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + m.visitTypeInsn(Opcodes.INSTANCEOF, "kotlin/Result$Failure"); + final Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + m.visitTypeInsn(Opcodes.CHECKCAST, "kotlin/Result$Failure"); + m.visitFieldInsn(Opcodes.GETFIELD, "kotlin/Result$Failure", + "exception", "Ljava/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + m.visitInsn(Opcodes.POP); + m.visitLabel(label); + } + m.visitVarInsn(Opcodes.ALOAD, 1); + range2.toInclusive = m.instructions.getLast(); + m.visitLabel(continuationLabelAfterLoadedResult); + + // line after "suspendingFunction" + m.visitInsn(Opcodes.NOP); + m.visitInsn(Opcodes.ARETURN); + + m.visitLabel(dflt); + final Range range0 = new Range(); + range0.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("call to 'resume' before 'invoke' with coroutine"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", "<init>", + "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ATHROW); + range0.toInclusive = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(range0, range1, range2); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java new file mode 100644 index 00000000..8dfa765e --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinDefaultArgumentsFilterTest.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit test for {@link KotlinDefaultArgumentsFilter}. + */ +public class KotlinDefaultArgumentsFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinDefaultArgumentsFilter(); + + private static MethodNode createMethod(final int access, final String name, + final String descriptor) { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + access, name, descriptor, null, null); + + m.visitVarInsn(Opcodes.ILOAD, 2); + m.visitInsn(Opcodes.ICONST_1); + m.visitInsn(Opcodes.IAND); + final Label label = new Label(); + m.visitJumpInsn(Opcodes.IFEQ, label); + // default argument + m.visitLdcInsn(Integer.valueOf(42)); + m.visitVarInsn(Opcodes.ISTORE, 1); + m.visitLabel(label); + + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ILOAD, 1); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "Target", "origin", "(I)V", + false); + m.visitInsn(Opcodes.RETURN); + + return m; + } + + @Test + public void should_filter() { + final MethodNode m = createMethod(Opcodes.ACC_SYNTHETIC, + "origin$default", "(LTarget;IILjava/lang/Object;)V"); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + filter.filter(m, context, output); + + assertIgnored(new Range(m.instructions.get(3), m.instructions.get(3))); + } + + @Test + public void should_not_filter_when_not_kotlin() { + final MethodNode m = createMethod(Opcodes.ACC_SYNTHETIC, + "not_kotlin_synthetic$default", + "(LTarget;IILjava/lang/Object;)V"); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_suffix_absent() { + final MethodNode m = createMethod(Opcodes.ACC_SYNTHETIC, + "synthetic_without_suffix", "(LTarget;IILjava/lang/Object;)V"); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_not_synthetic() { + final MethodNode m = createMethod(0, "not_synthetic$default", + "(LTarget;IILjava/lang/Object;)V"); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + filter.filter(m, context, output); + + assertIgnored(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilterTest.java new file mode 100644 index 00000000..8a6e8356 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinGeneratedFilterTest.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Nikolay Krasko - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link KotlinGeneratedFilter}. + */ +public class KotlinGeneratedFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinGeneratedFilter(); + + @Test + public void testNoLinesForKotlinWithDebug() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void testWithLinesForKotlinWithDebug() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitAnnotation("Lother/Annotation;", false); + m.visitLineNumber(12, new Label()); + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void testNoLinesNonKotlinWithDebug() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void testNoLinesForKotlinNoDebug() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + context.sourceFileName = null; + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void testWithLinesForKotlinNoDebug() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "hashCode", "()I", null, null); + m.visitInsn(Opcodes.ICONST_0); + m.visitInsn(Opcodes.IRETURN); + m.visitLineNumber(12, new Label()); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + context.sourceFileName = null; + + filter.filter(m, context, output); + + assertIgnored(); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilterTest.java new file mode 100644 index 00000000..fe3b9553 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilterTest.java @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link KotlinInlineFilter}. + */ +public class KotlinInlineFilterTest extends FilterTestBase { + + private final KotlinInlineFilter filter = new KotlinInlineFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "callsite", "()V", null, null); + + @Test + public void should_filter() { + context.sourceFileName = "callsite.kt"; + context.sourceDebugExtension = "" // + + "SMAP\n" // + + "callsite.kt\n" // OutputFileName=callsite.kt + + "Kotlin\n" // DefaultStratumId=Kotlin + + "*S Kotlin\n" // StratumID=Kotlin + + "*F\n" // FileSection + + "+ 1 callsite.kt\n" // FileID=1,FileName=callsite.kt + + "CallsiteKt\n" // + + "+ 2 a.kt\n" // FileID=2,FileName=a.kt + + "AKt\n" // + + "+ 3 b.kt\n" // FileID=3,FileName=b.kt + + "BKt\n" // + + "*L\n" // LineSection + + "1#1,8:1\n" // InputStartLine=1,LineFileID=1,RepeatCount=8,OutputStartLine=1 + + "2#2,2:9\n" // InputStartLine=2,LineFileID=2,RepeatCount=2,OutputStartLine=9 + + "2#3,2:11\n" // InputStartLine=2,LineFileID=3,RepeatCount=2,OutputStartLine=11 + + "*E\n"; // EndSection + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLineNumber(2, new Label()); + m.visitInsn(Opcodes.NOP); + + m.visitLineNumber(9, new Label()); + shouldIgnorePrevious(m); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Stubs", "nop", "()V", false); + shouldIgnorePrevious(m); + m.visitLineNumber(10, new Label()); + shouldIgnorePrevious(m); + m.visitInsn(Opcodes.NOP); + shouldIgnorePrevious(m); + + m.visitLineNumber(3, new Label()); + m.visitInsn(Opcodes.NOP); + + m.visitLineNumber(11, new Label()); + shouldIgnorePrevious(m); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Stubs", "nop", "()V", false); + shouldIgnorePrevious(m); + m.visitLineNumber(12, new Label()); + shouldIgnorePrevious(m); + m.visitInsn(Opcodes.NOP); + shouldIgnorePrevious(m); + + m.visitLineNumber(4, new Label()); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(expectedRanges.toArray(new Range[0])); + + // should not reparse: + context.sourceDebugExtension = ""; + filter.filter(m, context, output); + } + + /** + * <pre> + * inline fun inlined_top_level() { + * Stubs.nop() + * } + * + * class Callsite { + * fun inlined() { + * Stubs.nop() + * } + * + * fun callsite { + * inlined_top_level() + * inlined() + * } + * } + * </pre> + */ + @Test + public void should_filter_when_in_same_file() { + context.sourceFileName = "example.kt"; + context.sourceDebugExtension = "" // + + "SMAP\n" // + + "example.kt\n" // OutputFileName=example.kt + + "Kotlin\n" // DefaultStratumId=Kotlin + + "*S Kotlin\n" // StratumID=Kotlin + + "*F\n" // FileSection + + "+ 1 example.kt\n" // FileID=1,FileName=example.kt + + "Callsite\n" // + + "+ 2 example.kt\n" // FileID=2,FileName=example.kt + + "ExampleKt\n" // + + "*L\n" // LineSection + + "1#1,15:1\n" // InputStartLine=1,LineFileID=1,RepeatCount=10,OutputStartLine=1 + + "7#1,2:18\n" // InputStartLine=7,LineFileID=1,RepeatCount=2,OutputStartLine=18 + + "2#2,2:16\n" // InputStartLine=2,LineFileID=2,RepeatCount=2,OutputStartLine=16 + + "*E\n"; // EndSection + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLineNumber(11, new Label()); + m.visitInsn(Opcodes.NOP); + m.visitLineNumber(16, new Label()); + shouldIgnorePrevious(m); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Stubs", "nop", "()V", false); + shouldIgnorePrevious(m); + m.visitLineNumber(17, new Label()); + shouldIgnorePrevious(m); + m.visitInsn(Opcodes.NOP); + shouldIgnorePrevious(m); + + m.visitLineNumber(12, new Label()); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitLineNumber(18, new Label()); + shouldIgnorePrevious(m); + m.visitMethodInsn(Opcodes.INVOKESTATIC, "Stubs", "nop", "()V", false); + shouldIgnorePrevious(m); + m.visitLineNumber(19, new Label()); + shouldIgnorePrevious(m); + m.visitInsn(Opcodes.NOP); + shouldIgnorePrevious(m); + + m.visitLineNumber(13, new Label()); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(expectedRanges.toArray(new Range[0])); + } + + @Test + public void should_not_parse_SourceDebugExtension_attribute_when_no_kotlin_metadata_annotation() { + context.sourceDebugExtension = "SMAP"; + + m.visitLineNumber(1, new Label()); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_not_filter_when_no_SourceDebugExtension_attribute() { + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + m.visitLineNumber(1, new Label()); + m.visitInsn(Opcodes.RETURN); + + filter.filter(m, context, output); + + assertIgnored(); + } + + @Test + public void should_throw_exception_when_SMAP_incomplete() { + context.sourceDebugExtension = "" // + + "SMAP\n"; + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + try { + filter.filter(m, context, output); + fail("exception expected"); + } catch (final IllegalStateException e) { + assertEquals("Unexpected SMAP line: null", e.getMessage()); + } + } + + @Test + public void should_throw_exception_when_unexpected_FileInfo() { + context.sourceFileName = "callsite.kt"; + context.sourceDebugExtension = "" // + + "SMAP\n" // + + "callsite.kt\n" // + + "Kotlin\n" // + + "*S Kotlin\n" // + + "*F\n" // + + "xxx"; + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + try { + filter.filter(m, context, output); + fail("exception expected"); + } catch (final IllegalStateException e) { + assertEquals("Unexpected SMAP line: xxx", e.getMessage()); + } + } + + @Test + public void should_throw_exception_when_no_SourceFileId_for_SourceFile() { + context.sourceFileName = "example.kt"; + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + context.sourceDebugExtension = "" // + + "SMAP\n" // + + "example.kt\n" // + + "Kotlin\n" // + + "*S Kotlin\n" // + + "*F\n" // + + "+ 1 another.kt\n" // + + "AnotherKt\n" // + + "*L\n" // + + "*E\n"; + + try { + filter.filter(m, context, output); + fail("exception expected"); + } catch (final IllegalStateException e) { + assertEquals("Unexpected SMAP FileSection", e.getMessage()); + } + } + + @Test + public void should_throw_exception_when_unexpected_LineInfo() { + context.sourceFileName = "callsite.kt"; + context.sourceDebugExtension = "" // + + "SMAP\n" // + + "callsite.kt\n" // + + "Kotlin\n" // + + "*S Kotlin\n" // + + "*F\n" // + + "+ 1 callsite.kt\n" // + + "Callsite\n" // + + "*L\n" // + + "xxx"; + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + + try { + filter.filter(m, context, output); + fail("exception expected"); + } catch (final IllegalStateException e) { + assertEquals("Unexpected SMAP line: xxx", e.getMessage()); + } + } + + private final List<Range> expectedRanges = new ArrayList<Range>(); + + private void shouldIgnorePrevious(final MethodNode m) { + expectedRanges.add( + new Range(m.instructions.getLast(), m.instructions.getLast())); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java new file mode 100644 index 00000000..c9e34bab --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinLateinitFilterTest.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Fabian Mastenbroek - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +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; + +/** + * Unit tests for {@link KotlinLateinitFilter}. + */ +public class KotlinLateinitFilterTest extends FilterTestBase { + + private final KotlinLateinitFilter filter = new KotlinLateinitFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + @Test + public void testLateinitBranchIsFiltered() { + final Label l1 = new Label(); + final Label l2 = new Label(); + + m.visitLabel(l1); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitFieldInsn(Opcodes.GETFIELD, + "com/better/alarm/background/VibrationService", "wakeLock", + "Landroid/os/PowerManager$WakeLock;"); + m.visitInsn(Opcodes.DUP); + m.visitJumpInsn(Opcodes.IFNONNULL, l2); + + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + + m.visitLdcInsn("wakelock"); + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", + "throwUninitializedPropertyAccessException", + "(Ljava/lang/String;)V", false); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(l2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "android/os/PowerManager$WakeLock", "acquire", "", false); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilterTest.java new file mode 100644 index 00000000..0d08ae3f --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinNotNullOperatorFilterTest.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link KotlinNotNullOperatorFilter}. + */ +public class KotlinNotNullOperatorFilterTest extends FilterTestBase { + + private final KotlinNotNullOperatorFilter filter = new KotlinNotNullOperatorFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "example", "()V", null, null); + + /** + * <pre> + * return x!!.length + * </pre> + */ + @Test + public void should_filter() { + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.DUP); + + final Range range = new Range(); + final Label label = new Label(); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + range.fromInclusive = m.instructions.getLast(); + // no line number here and hence no probe + m.visitMethodInsn(Opcodes.INVOKESTATIC, + "kotlin/jvm/internal/Intrinsics", "throwNpe", "()V", false); + range.toInclusive = m.instructions.getLast(); + + m.visitLabel(label); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", + "()I", false); + m.visitInsn(Opcodes.IRETURN); + + filter.filter(m, context, output); + + assertIgnored(range); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilterTest.java new file mode 100644 index 00000000..9fc3b3da --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinUnsafeCastOperatorFilterTest.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +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; + +/** + * Unit tests for {@link KotlinUnsafeCastOperatorFilter}. + */ +public class KotlinUnsafeCastOperatorFilterTest extends FilterTestBase { + + private final KotlinUnsafeCastOperatorFilter filter = new KotlinUnsafeCastOperatorFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + @Test + public void should_filter() { + final Label label = new Label(); + + m.visitInsn(Opcodes.DUP); + m.visitJumpInsn(Opcodes.IFNONNULL, label); + final AbstractInsnNode expectedFrom = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "kotlin/TypeCastException"); + m.visitInsn(Opcodes.DUP); + m.visitLdcInsn("null cannot be cast to non-null type kotlin.String"); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, "kotlin/TypeCastException", + "<init>", "(Ljava/lang/String;)V", false); + m.visitInsn(Opcodes.ATHROW); + final AbstractInsnNode expectedTo = m.instructions.getLast(); + m.visitLabel(label); + + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFrom, expectedTo)); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilterTest.java new file mode 100644 index 00000000..04d40a05 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenFilterTest.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.jacoco.core.internal.instr.InstrSupport; +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; + +/** + * Unit tests for {@link KotlinWhenFilter}. + */ +public class KotlinWhenFilterTest extends FilterTestBase { + + private final KotlinWhenFilter filter = new KotlinWhenFilter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + @Test + public void should_filter_implicit_else() { + final Label label = new Label(); + + final Range range1 = new Range(); + + m.visitInsn(Opcodes.NOP); + + m.visitJumpInsn(Opcodes.IFEQ, label); + range1.fromInclusive = m.instructions.getLast(); + range1.toInclusive = m.instructions.getLast(); + + m.visitInsn(Opcodes.NOP); + + final Range range2 = new Range(); + m.visitLabel(label); + range2.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "kotlin/NoWhenBranchMatchedException"); + m.visitInsn(Opcodes.DUP); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "kotlin/NoWhenBranchMatchedException", "<init>", "()V", false); + m.visitInsn(Opcodes.ATHROW); + range2.toInclusive = m.instructions.getLast(); + + filter.filter(m, context, output); + + assertIgnored(range1, range2); + assertNoReplacedBranches(); + } + + @Test + public void should_not_filter_explicit_else() { + final Label label = new Label(); + + m.visitInsn(Opcodes.NOP); + + m.visitJumpInsn(Opcodes.IFEQ, label); + + m.visitInsn(Opcodes.NOP); + + m.visitLabel(label); + m.visitTypeInsn(Opcodes.NEW, "kotlin/NoWhenBranchMatchedException"); + m.visitInsn(Opcodes.DUP); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "kotlin/NoWhenBranchMatchedException", "<init>", "()V", false); + m.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Throwable"); + m.visitInsn(Opcodes.ATHROW); + + filter.filter(m, context, output); + + assertIgnored(); + assertNoReplacedBranches(); + } + + @Test + public void should_filter_implicit_default() { + final Label case1 = new Label(); + final Label caseDefault = new Label(); + final Label after = new Label(); + + m.visitInsn(Opcodes.NOP); + + m.visitTableSwitchInsn(0, 0, caseDefault, case1); + final AbstractInsnNode switchNode = m.instructions.getLast(); + final Set<AbstractInsnNode> newTargets = new HashSet<AbstractInsnNode>(); + + m.visitLabel(case1); + m.visitInsn(Opcodes.ICONST_1); + newTargets.add(m.instructions.getLast()); + m.visitJumpInsn(Opcodes.GOTO, after); + + final Range range1 = new Range(); + m.visitLabel(caseDefault); + range1.fromInclusive = m.instructions.getLast(); + m.visitTypeInsn(Opcodes.NEW, "kotlin/NoWhenBranchMatchedException"); + m.visitInsn(Opcodes.DUP); + m.visitMethodInsn(Opcodes.INVOKESPECIAL, + "kotlin/NoWhenBranchMatchedException", "<init>", "()V", false); + m.visitInsn(Opcodes.ATHROW); + range1.toInclusive = m.instructions.getLast(); + + m.visitLabel(after); + + filter.filter(m, context, output); + + assertIgnored(range1); + assertReplacedBranches(switchNode, newTargets); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilterTest.java new file mode 100644 index 00000000..df42803d --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/KotlinWhenStringFilterTest.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.jacoco.core.internal.instr.InstrSupport; +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; + +/** + * Unit tests for {@link KotlinWhenStringFilter}. + */ +public class KotlinWhenStringFilterTest extends FilterTestBase { + + private final IFilter filter = new KotlinWhenStringFilter(); + + @Test + public void should_filter() { + final Set<AbstractInsnNode> expectedNewTargets = new HashSet<AbstractInsnNode>(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + final Label h1 = new Label(); + final Label sameHash = new Label(); + final Label h2 = new Label(); + final Label case1 = new Label(); + final Label case2 = new Label(); + final Label case3 = new Label(); + final Label defaultCase = new Label(); + + // filter should not remember this unrelated slot + m.visitLdcInsn(""); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + + // switch (...) + m.visitVarInsn(Opcodes.ASTORE, 2); + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I", false); + m.visitTableSwitchInsn(97, 98, defaultCase, h1, h2); + + // case "a" + m.visitLabel(h1); + final AbstractInsnNode expectedFromInclusive = m.instructions.getLast(); + + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + m.visitJumpInsn(Opcodes.IFEQ, sameHash); + m.visitJumpInsn(Opcodes.GOTO, case1); + + // case "\u0000a" + m.visitLabel(sameHash); + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("\u0000a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + m.visitJumpInsn(Opcodes.IFEQ, defaultCase); + m.visitJumpInsn(Opcodes.GOTO, case2); + + // case "b" + m.visitLabel(h2); + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("\u0000a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + m.visitJumpInsn(Opcodes.IFEQ, defaultCase); + m.visitJumpInsn(Opcodes.GOTO, case3); + final AbstractInsnNode expectedToInclusive = m.instructions.getLast(); + + m.visitLabel(case1); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(case2); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(case3); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(defaultCase); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + + filter.filter(m, context, output); + + assertReplacedBranches(expectedFromInclusive.getPrevious(), + expectedNewTargets); + assertIgnored(new Range(expectedFromInclusive, expectedToInclusive)); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilterTest.java deleted file mode 100644 index 3e2b497a..00000000 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/LombokGeneratedFilterTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009, 2018 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.filter; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - -import org.jacoco.core.internal.instr.InstrSupport; -import org.junit.Test; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodNode; - -public class LombokGeneratedFilterTest implements IFilterOutput { - - private final IFilter filter = new LombokGeneratedFilter(); - - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - - @Test - public void testNoAnnotations() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(fromInclusive); - assertNull(toInclusive); - } - - @Test - public void testOtherAnnotation() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - m.visitAnnotation("Lother/Annotation;", false); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(fromInclusive); - assertNull(toInclusive); - } - - @Test - public void testLombokGeneratedAnnotation() { - final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, - "hashCode", "()I", null, null); - m.visitAnnotation("Llombok/Generated;", false); - - m.visitInsn(Opcodes.ICONST_0); - m.visitInsn(Opcodes.IRETURN); - - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); - } - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } - - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); - } - -} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilterTest.java index 24c67609..ac42e64e 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/PrivateEmptyNoArgConstructorFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,22 +11,18 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import org.jacoco.core.internal.instr.InstrSupport; import org.junit.Test; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -public class PrivateEmptyNoArgConstructorFilterTest implements IFilterOutput { +/** + * Unit tests for {@link PrivateEmptyNoArgConstructorFilter}. + */ +public class PrivateEmptyNoArgConstructorFilterTest extends FilterTestBase { private final IFilter filter = new PrivateEmptyNoArgConstructorFilter(); - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - @Test public void test() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, @@ -37,20 +33,9 @@ public class PrivateEmptyNoArgConstructorFilterTest implements IFilterOutput { "()V", false); m.visitInsn(Opcodes.RETURN); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); - } - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertMethodIgnored(m); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java new file mode 100644 index 00000000..3f726185 --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchEcjFilterTest.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.jacoco.core.internal.instr.InstrSupport; +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; + +/** + * Unit tests for {@link StringSwitchEcjFilter}. + */ +public class StringSwitchEcjFilterTest extends FilterTestBase { + + private final IFilter filter = new StringSwitchEcjFilter(); + + @Test + public void should_filter() { + final Set<AbstractInsnNode> expectedNewTargets = new HashSet<AbstractInsnNode>(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + final Label case1 = new Label(); + final Label case2 = new Label(); + final Label case3 = new Label(); + final Label caseDefault = new Label(); + final Label h1 = new Label(); + final Label h2 = new Label(); + + // filter should not remember this unrelated slot + m.visitLdcInsn(""); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + + // switch (...) + m.visitInsn(Opcodes.DUP); + m.visitVarInsn(Opcodes.ASTORE, 2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I", false); + m.visitTableSwitchInsn(97, 98, caseDefault, h1, h2); + final AbstractInsnNode switchNode = m.instructions.getLast(); + + m.visitLabel(h1); + + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "a", then goto its case + m.visitJumpInsn(Opcodes.IFNE, case1); + + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("\0a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "\0a", then goto its case + m.visitJumpInsn(Opcodes.IFNE, case2); + + // goto default case + m.visitJumpInsn(Opcodes.GOTO, caseDefault); + + m.visitLabel(h2); + + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("b"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "b", then goto its case + m.visitJumpInsn(Opcodes.IFNE, case3); + + // goto default case + m.visitJumpInsn(Opcodes.GOTO, caseDefault); + final AbstractInsnNode expectedToInclusive = m.instructions.getLast(); + + m.visitLabel(case1); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(case2); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(case3); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(caseDefault); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + + filter.filter(m, context, output); + + assertReplacedBranches(switchNode, expectedNewTargets); + assertIgnored(new Range(switchNode.getNext(), expectedToInclusive)); + } + + @Test + public void should_filter_when_default_is_first() { + final Set<AbstractInsnNode> expectedNewTargets = new HashSet<AbstractInsnNode>(); + + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + final Label case1 = new Label(); + final Label caseDefault = new Label(); + final Label h1 = new Label(); + + // filter should not remember this unrelated slot + m.visitLdcInsn(""); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 1); + + // switch (...) + m.visitInsn(Opcodes.DUP); + m.visitVarInsn(Opcodes.ASTORE, 2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", + "()I", false); + m.visitLookupSwitchInsn(caseDefault, new int[] { 97 }, + new Label[] { h1 }); + final AbstractInsnNode switchNode = m.instructions.getLast(); + + m.visitLabel(h1); + + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitLdcInsn("a"); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", + "(Ljava/lang/Object;)Z", false); + // if equal "a", then goto its case + m.visitJumpInsn(Opcodes.IFNE, case1); + + final AbstractInsnNode expectedToInclusive = m.instructions.getLast(); + + m.visitLabel(caseDefault); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + m.visitLabel(case1); + m.visitInsn(Opcodes.RETURN); + expectedNewTargets.add(m.instructions.getLast()); + + filter.filter(m, context, output); + + assertReplacedBranches(switchNode, expectedNewTargets); + assertIgnored(new Range(switchNode.getNext(), expectedToInclusive)); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java index 2dd9359d..592c6998 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/StringSwitchJavacFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,10 +11,6 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - import org.jacoco.core.internal.instr.InstrSupport; import org.junit.Test; import org.objectweb.asm.Label; @@ -22,23 +18,24 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -public class StringSwitchJavacFilterTest implements IFilterOutput { +/** + * Unit tests for {@link StringSwitchJavacFilter}. + */ +public class StringSwitchJavacFilterTest extends FilterTestBase { private final IFilter filter = new StringSwitchJavacFilter(); private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "name", "()V", null, null); - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; + private AbstractInsnNode expectedFromInclusive; + private AbstractInsnNode expectedToInclusive; - @Test - public void should_filter_code_generated_by_javac() { + private void createFirstSwitch() { final Label h1 = new Label(); final Label h1_2 = new Label(); final Label h2 = new Label(); final Label secondSwitch = new Label(); - final Label cases = new Label(); m.visitInsn(Opcodes.ICONST_M1); m.visitVarInsn(Opcodes.ISTORE, 2); @@ -48,7 +45,7 @@ public class StringSwitchJavacFilterTest implements IFilterOutput { "()I", false); m.visitLookupSwitchInsn(secondSwitch, new int[] { 97, 98 }, new Label[] { h1, h2 }); - final AbstractInsnNode fromInclusive = m.instructions.getLast(); + expectedFromInclusive = m.instructions.getLast(); m.visitLabel(h1); m.visitVarInsn(Opcodes.ALOAD, 1); @@ -87,15 +84,34 @@ public class StringSwitchJavacFilterTest implements IFilterOutput { m.visitVarInsn(Opcodes.ISTORE, 2); m.visitLabel(secondSwitch); - final AbstractInsnNode toInclusive = m.instructions.getLast(); + expectedToInclusive = m.instructions.getLast(); m.visitVarInsn(Opcodes.ILOAD, 2); + } + + @Test + public void should_filter_code_generated_by_javac() { + createFirstSwitch(); + + final Label cases = new Label(); m.visitTableSwitchInsn(0, 2, cases); m.visitLabel(cases); - filter.filter("Foo", "java/lang/Object", m, this); + filter.filter(m, context, output); + + assertIgnored(new Range(expectedFromInclusive, expectedToInclusive)); + } + + @Test + public void should_filter_when_javac_generates_lookupswitch() { + createFirstSwitch(); + + final Label cases = new Label(); + m.visitLookupSwitchInsn(cases, null, new Label[] {}); + m.visitLabel(cases); + + filter.filter(m, context, output); - assertEquals(fromInclusive, this.fromInclusive); - assertEquals(toInclusive, this.toInclusive); + assertIgnored(new Range(expectedFromInclusive, expectedToInclusive)); } @Test @@ -140,21 +156,9 @@ public class StringSwitchJavacFilterTest implements IFilterOutput { m.visitLabel(cases); - filter.filter("Foo", "java/lang/Object", m, this); - - assertNull(this.fromInclusive); - assertNull(this.toInclusive); - } - - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertIgnored(); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilterTest.java index 5e2b4379..a40b2349 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SynchronizedFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,28 +11,23 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - import org.jacoco.core.internal.instr.InstrSupport; 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.LabelNode; import org.objectweb.asm.tree.MethodNode; -public class SynchronizedFilterTest implements IFilterOutput { +/** + * Unit tests for {@link SynchronizedFilter}. + */ +public class SynchronizedFilterTest extends FilterTestBase { private final SynchronizedFilter filter = new SynchronizedFilter(); private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "name", "()V", null, null); - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - @Test public void javac() { final Label start = new Label(); @@ -64,9 +59,10 @@ public class SynchronizedFilterTest implements IFilterOutput { m.visitLabel(exit); m.visitInsn(Opcodes.RETURN); - filter.filter("Foo", "java/lang/Object", m, this); - assertEquals(handler.info, fromInclusive); - assertEquals(((LabelNode) exit.info).getPrevious(), toInclusive); + filter.filter(m, context, output); + + assertIgnored(new Range((LabelNode) handler.info, + ((LabelNode) exit.info).getPrevious())); } /** @@ -117,8 +113,9 @@ public class SynchronizedFilterTest implements IFilterOutput { m.visitLabel(exit); m.visitInsn(Opcodes.RETURN); - filter.filter("Foo", "java/lang/Object", m, this); - assertNull(fromInclusive); + filter.filter(m, context, output); + + assertIgnored(); } @Test @@ -152,20 +149,10 @@ public class SynchronizedFilterTest implements IFilterOutput { m.visitLabel(exit); m.visitInsn(Opcodes.RETURN); - filter.filter("Foo", "java/lang/Object", m, this); - assertEquals(handler.info, fromInclusive); - assertEquals(((LabelNode) exit.info).getPrevious(), toInclusive); - } - - public void ignore(AbstractInsnNode fromInclusive, - AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertIgnored(new Range((LabelNode) handler.info, + ((LabelNode) exit.info).getPrevious())); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java index 06344d52..5bbae983 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/SyntheticFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,33 +11,27 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - import org.jacoco.core.internal.instr.InstrSupport; import org.junit.Test; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodNode; -public class SyntheticFilterTest implements IFilterOutput { +/** + * Unit tests for {@link SyntheticFilter}. + */ +public class SyntheticFilterTest extends FilterTestBase { private final SyntheticFilter filter = new SyntheticFilter(); - private AbstractInsnNode fromInclusive; - private AbstractInsnNode toInclusive; - @Test public void testNonSynthetic() { final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, "name", "()V", null, null); m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); + filter.filter(m, context, output); - assertNull(fromInclusive); - assertNull(toInclusive); + assertIgnored(); } @Test @@ -46,10 +40,9 @@ public class SyntheticFilterTest implements IFilterOutput { Opcodes.ACC_SYNTHETIC, "name", "()V", null, null); m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); + filter.filter(m, context, output); - assertEquals(m.instructions.getFirst(), fromInclusive); - assertEquals(m.instructions.getLast(), toInclusive); + assertMethodIgnored(m); } @Test @@ -58,21 +51,50 @@ public class SyntheticFilterTest implements IFilterOutput { Opcodes.ACC_SYNTHETIC, "lambda$1", "()V", null, null); m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); + filter.filter(m, context, output); - assertNull(fromInclusive); - assertNull(toInclusive); + assertIgnored(); } - public void ignore(final AbstractInsnNode fromInclusive, - final AbstractInsnNode toInclusive) { - assertNull(this.fromInclusive); - this.fromInclusive = fromInclusive; - this.toInclusive = toInclusive; + @Test + public void should_not_filter_method_with_suffix_default_in_kotlin_classes() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE, "example$default", + "(LTarget;Ljava/lang/String;Ijava/lang/Object;)V", null, null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertIgnored(); } - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + @Test + public void should_filter_synthetic_method_with_suffix_default_in_non_kotlin_classes() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE, "example$default", + "(LTarget;Ljava/lang/String;Ijava/lang/Object;)V", null, null); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertMethodIgnored(m); + } + + @Test + public void should_not_filter_synthetic_methods_whose_last_argument_is_kotlin_coroutine_continuation() { + final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, + Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC, "example", + "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", null, + null); + context.classAnnotations + .add(KotlinGeneratedFilter.KOTLIN_METADATA_DESC); + m.visitInsn(Opcodes.NOP); + + filter.filter(m, context, output); + + assertIgnored(); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java index 248ca4e9..2a930d0d 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesEcjFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,20 +11,16 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.List; - import org.jacoco.core.internal.instr.InstrSupport; 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; -public class TryWithResourcesEcjFilterTest implements IFilterOutput { +/** + * Unit tests for {@link TryWithResourcesEcjFilter}. + */ +public class TryWithResourcesEcjFilterTest extends FilterTestBase { private final TryWithResourcesEcjFilter filter = new TryWithResourcesEcjFilter(); @@ -310,15 +306,9 @@ public class TryWithResourcesEcjFilterTest implements IFilterOutput { // additional handlers m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(2, from.size()); - - assertEquals(range0.fromInclusive, from.get(0)); - assertEquals(range0.toInclusive, to.get(0)); + filter.filter(m, context, output); - assertEquals(range1.fromInclusive, from.get(1)); - assertEquals(range1.toInclusive, to.get(1)); + assertIgnored(range0, range1); } /** @@ -598,32 +588,9 @@ public class TryWithResourcesEcjFilterTest implements IFilterOutput { // additional handlers m.visitInsn(Opcodes.NOP); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(2, from.size()); - - assertEquals(range0.fromInclusive, from.get(0)); - assertEquals(range0.toInclusive, to.get(0)); - - assertEquals(range1.fromInclusive, from.get(1)); - assertEquals(range1.toInclusive, to.get(1)); - } - - static class Range { - AbstractInsnNode fromInclusive; - AbstractInsnNode toInclusive; - } - - private final List<AbstractInsnNode> from = new ArrayList<AbstractInsnNode>(); - private final List<AbstractInsnNode> to = new ArrayList<AbstractInsnNode>(); - - public void ignore(AbstractInsnNode from, AbstractInsnNode to) { - this.from.add(from); - this.to.add(to); - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertIgnored(range0, range1); } } diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11FilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11FilterTest.java new file mode 100644 index 00000000..cab9ee1f --- /dev/null +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavac11FilterTest.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2009, 2019 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: + * Evgeny Mandrikov - initial API and implementation + * + *******************************************************************************/ +package org.jacoco.core.internal.analysis.filter; + +import org.jacoco.core.internal.instr.InstrSupport; +import org.junit.Test; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +/** + * Unit tests for {@link TryWithResourcesJavac11Filter}. + */ +public class TryWithResourcesJavac11FilterTest extends FilterTestBase { + + private final TryWithResourcesJavac11Filter filter = new TryWithResourcesJavac11Filter(); + + private final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, + "name", "()V", null, null); + + /** + * <pre> + * try (r = new ...) { + * ... + * } + * </pre> + */ + @Test + public void without_null_check() { + final Range range1 = new Range(); + final Range range2 = new Range(); + + final Label e = new Label(); + final Label t = new Label(); + + final Label handler = new Label(); + m.visitTryCatchBlock(handler, handler, handler, "java/lang/Throwable"); + + m.visitInsn(Opcodes.NOP); + + m.visitVarInsn(Opcodes.ALOAD, 0); + range1.fromInclusive = m.instructions.getLast(); + m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Resource", "close", + "()V", false); + m.visitJumpInsn(Opcodes.GOTO, e); + range1.toInclusive = m.instructions.getLast(); + + m.visitLabel(handler); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Resource", "close", + "()V", false); + m.visitJumpInsn(Opcodes.GOTO, t); + + m.visitVarInsn(Opcodes.ASTORE, 2); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", + "addSuppressed", "(Ljava/lang/Throwable;)V", false); + m.visitLabel(t); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ATHROW); + range2.toInclusive = m.instructions.getLast(); + + m.visitLabel(e); + + filter.filter(m, context, output); + + assertIgnored(range1, range2); + } + + /** + * <pre> + * try (r = open()) { + * ... + * } + * </pre> + */ + @Test + public void with_null_check() { + final Range range1 = new Range(); + final Range range2 = new Range(); + + final Label e = new Label(); + final Label t = new Label(); + + final Label handler = new Label(); + m.visitTryCatchBlock(handler, handler, handler, "java/lang/Throwable"); + + m.visitInsn(Opcodes.NOP); + + m.visitVarInsn(Opcodes.ALOAD, 0); + range1.fromInclusive = m.instructions.getLast(); + m.visitJumpInsn(Opcodes.IFNULL, e); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Resource", "close", + "()V", false); + m.visitJumpInsn(Opcodes.GOTO, e); + range1.toInclusive = m.instructions.getLast(); + + m.visitLabel(handler); + range2.fromInclusive = m.instructions.getLast(); + m.visitVarInsn(Opcodes.ASTORE, 1); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitJumpInsn(Opcodes.IFNULL, t); + m.visitVarInsn(Opcodes.ALOAD, 0); + m.visitMethodInsn(Opcodes.INVOKEINTERFACE, "Resource", "close", + "()V", false); + m.visitJumpInsn(Opcodes.GOTO, t); + + m.visitVarInsn(Opcodes.ASTORE, 2); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitVarInsn(Opcodes.ALOAD, 2); + m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", + "addSuppressed", "(Ljava/lang/Throwable;)V", false); + m.visitLabel(t); + m.visitVarInsn(Opcodes.ALOAD, 1); + m.visitInsn(Opcodes.ATHROW); + range2.toInclusive = m.instructions.getLast(); + + m.visitLabel(e); + + filter.filter(m, context, output); + + assertIgnored(range1, range2); + } + +} diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java index 83109eae..002ad339 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/analysis/filter/TryWithResourcesJavacFilterTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + * Copyright (c) 2009, 2019 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 @@ -11,20 +11,16 @@ *******************************************************************************/ package org.jacoco.core.internal.analysis.filter; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.List; - import org.jacoco.core.internal.instr.InstrSupport; 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; -public class TryWithResourcesJavacFilterTest implements IFilterOutput { +/** + * Unit tests for {@link TryWithResourcesJavacFilter}. + */ +public class TryWithResourcesJavacFilterTest extends FilterTestBase { private final TryWithResourcesJavacFilter filter = new TryWithResourcesJavacFilter(); @@ -219,21 +215,9 @@ public class TryWithResourcesJavacFilterTest implements IFilterOutput { m.visitVarInsn(Opcodes.ALOAD, 8); m.visitInsn(Opcodes.ATHROW); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(4, from.size()); - - assertEquals(range0.fromInclusive, from.get(0)); - assertEquals(range0.toInclusive, to.get(0)); - - assertEquals(range1.fromInclusive, from.get(1)); - assertEquals(range1.toInclusive, to.get(1)); + filter.filter(m, context, output); - assertEquals(range2.fromInclusive, from.get(2)); - assertEquals(range2.toInclusive, to.get(2)); - - assertEquals(range3.fromInclusive, from.get(3)); - assertEquals(range3.toInclusive, to.get(3)); + assertIgnored(range0, range1, range2, range3); } /** @@ -548,21 +532,9 @@ public class TryWithResourcesJavacFilterTest implements IFilterOutput { m.visitVarInsn(Opcodes.ALOAD, 11); m.visitInsn(Opcodes.ATHROW); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(4, from.size()); - - assertEquals(range0.fromInclusive, from.get(0)); - assertEquals(range0.toInclusive, to.get(0)); - - assertEquals(range1.fromInclusive, from.get(1)); - assertEquals(range1.toInclusive, to.get(1)); - - assertEquals(range2.fromInclusive, from.get(2)); - assertEquals(range2.toInclusive, to.get(2)); + filter.filter(m, context, output); - assertEquals(range3.fromInclusive, from.get(3)); - assertEquals(range3.toInclusive, to.get(3)); + assertIgnored(range0, range1, range2, range3); } /** @@ -726,15 +698,9 @@ public class TryWithResourcesJavacFilterTest implements IFilterOutput { m.visitLabel(end); - filter.filter("Foo", "java/lang/Object", m, this); + filter.filter(m, context, output); - assertEquals(2, from.size()); - - assertEquals(range0.fromInclusive, from.get(0)); - assertEquals(range0.toInclusive, to.get(0)); - - assertEquals(range1.fromInclusive, from.get(1)); - assertEquals(range1.toInclusive, to.get(1)); + assertIgnored(range0, range1); } /** @@ -794,26 +760,9 @@ public class TryWithResourcesJavacFilterTest implements IFilterOutput { m.visitVarInsn(Opcodes.ALOAD, 4); m.visitInsn(Opcodes.ATHROW); - filter.filter("Foo", "java/lang/Object", m, this); - - assertEquals(0, from.size()); - } - - static class Range { - AbstractInsnNode fromInclusive; - AbstractInsnNode toInclusive; - } - - private final List<AbstractInsnNode> from = new ArrayList<AbstractInsnNode>(); - private final List<AbstractInsnNode> to = new ArrayList<AbstractInsnNode>(); - - public void ignore(AbstractInsnNode from, AbstractInsnNode to) { - this.from.add(from); - this.to.add(to); - } + filter.filter(m, context, output); - public void merge(final AbstractInsnNode i1, final AbstractInsnNode i2) { - fail(); + assertIgnored(); } } |