diff options
17 files changed, 205 insertions, 24 deletions
diff --git a/jacoco-maven-plugin.test/it/it-report-nomatch/nomatch.exec b/jacoco-maven-plugin.test/it/it-report-nomatch/nomatch.exec Binary files differindex 31cad96f..b3396529 100644 --- a/jacoco-maven-plugin.test/it/it-report-nomatch/nomatch.exec +++ b/jacoco-maven-plugin.test/it/it-report-nomatch/nomatch.exec diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/CreateExecFiles.java b/org.jacoco.ant.test/src/org/jacoco/ant/CreateExecFiles.java new file mode 100644 index 00000000..cbe9f90f --- /dev/null +++ b/org.jacoco.ant.test/src/org/jacoco/ant/CreateExecFiles.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2009, 2015 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.ant; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.jacoco.core.data.ExecutionData; +import org.jacoco.core.data.ExecutionDataWriter; + +/** + * Utility class to create exec files required for some Ant tests. + */ +public class CreateExecFiles { + + private static final String BASE_LOCATION = "./src/org/jacoco/ant/data/"; + + public static void main(String[] args) throws IOException { + OutputStream out; + + out = new FileOutputStream(BASE_LOCATION + "sample1.exec"); + new ExecutionDataWriter(out); + out.close(); + + out = new FileOutputStream(BASE_LOCATION + "sample2.exec"); + new ExecutionDataWriter(out); + out.close(); + + out = new FileOutputStream(BASE_LOCATION + "nomatch.exec"); + ExecutionDataWriter writer = new ExecutionDataWriter(out); + writer.visitClassExecution(new ExecutionData(0, + "org/jacoco/ant/TestTarget", new boolean[0])); + out.close(); + } + +} diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml index 92cef76f..79a21bab 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml +++ b/org.jacoco.ant.test/src/org/jacoco/ant/InstrumentTaskTest.xml @@ -64,7 +64,7 @@ <jacoco:instrument destdir="${instr.dir}"> <fileset dir="${lib.dir}" includes="*.jar"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 14 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 15 classes to ${temp.dir}"/> <unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/> <au:assertFileDoesntExist file="${instr.dir}/META-INF/TEST.RSA" /> @@ -85,7 +85,7 @@ <jacoco:instrument destdir="${instr.dir}" removesignatures="false"> <fileset dir="${lib.dir}" includes="*.jar"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 14 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 15 classes to ${temp.dir}"/> <unzip src="${instr.dir}/test.jar" dest="${instr.dir}"/> <au:assertFileExists file="${instr.dir}/META-INF/TEST.RSA" /> @@ -96,7 +96,7 @@ <jacoco:instrument destdir="${temp.dir}"> <fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 14 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 15 classes to ${temp.dir}"/> <au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" /> <echo file="${temp.dir}/jacoco-agent.properties">destfile=test.exec</echo> @@ -113,7 +113,7 @@ <jacoco:instrument destdir="${temp.dir}"> <fileset dir="${org.jacoco.ant.instrumentTaskTest.classes.dir}" includes="**/*.class"/> </jacoco:instrument> - <au:assertLogContains text="Instrumented 14 classes to ${temp.dir}"/> + <au:assertLogContains text="Instrumented 15 classes to ${temp.dir}"/> <au:assertFileExists file="${temp.dir}/org/jacoco/ant/InstrumentTaskTest.class" /> <java classname="org.jacoco.ant.TestTarget" failonerror="true" fork="true"> diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/data/nomatch.exec b/org.jacoco.ant.test/src/org/jacoco/ant/data/nomatch.exec Binary files differindex ef7d62ae..66d78df2 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/data/nomatch.exec +++ b/org.jacoco.ant.test/src/org/jacoco/ant/data/nomatch.exec diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/data/sample1.exec b/org.jacoco.ant.test/src/org/jacoco/ant/data/sample1.exec index b9c3ab93..2e87d6ca 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/data/sample1.exec +++ b/org.jacoco.ant.test/src/org/jacoco/ant/data/sample1.exec @@ -1 +1 @@ -ÀÀ
\ No newline at end of file +ÀÀ
\ No newline at end of file diff --git a/org.jacoco.ant.test/src/org/jacoco/ant/data/sample2.exec b/org.jacoco.ant.test/src/org/jacoco/ant/data/sample2.exec index b9c3ab93..2e87d6ca 100644 --- a/org.jacoco.ant.test/src/org/jacoco/ant/data/sample2.exec +++ b/org.jacoco.ant.test/src/org/jacoco/ant/data/sample2.exec @@ -1 +1 @@ -ÀÀ
\ No newline at end of file +ÀÀ
\ No newline at end of file diff --git a/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java b/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java index f63762e9..6a9275e8 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java @@ -93,7 +93,7 @@ public class AnalyzerTest { final byte[] bytes = TargetLoader .getClassDataAsBytes(AnalyzerTest.class); executionData.get(Long.valueOf(CRC64.checksum(bytes)), - "org/jacoco/core/analysis/AnalyzerTest", 100); + "org/jacoco/core/analysis/AnalyzerTest", 200); analyzer.analyzeClass(bytes, "Test"); assertFalse(classes.get("org/jacoco/core/analysis/AnalyzerTest") .isNoMatch()); @@ -102,7 +102,7 @@ public class AnalyzerTest { @Test public void testAnalyzeClassNoIdMatch() throws IOException { executionData.get(Long.valueOf(0), - "org/jacoco/core/analysis/AnalyzerTest", 100); + "org/jacoco/core/analysis/AnalyzerTest", 200); analyzer.analyzeClass( TargetLoader.getClassDataAsBytes(AnalyzerTest.class), "Test"); assertTrue(classes.get("org/jacoco/core/analysis/AnalyzerTest") diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelFlowAnalyzerTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelFlowAnalyzerTest.java index 440b4f38..8081c675 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelFlowAnalyzerTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelFlowAnalyzerTest.java @@ -12,6 +12,8 @@ package org.jacoco.core.internal.flow; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.objectweb.asm.Opcodes.*; @@ -36,6 +38,13 @@ public class LabelFlowAnalyzerTest { } @Test + public void testInit() { + assertFalse(analyzer.successor); + assertTrue(analyzer.first); + assertNull(analyzer.lineStart); + } + + @Test public void testFlowScenario01() { assertFalse(LabelInfo.isMultiTarget(label)); assertFalse(LabelInfo.isSuccessor(label)); @@ -134,12 +143,6 @@ public class LabelFlowAnalyzerTest { } @Test - public void testInit() { - assertFalse(analyzer.successor); - assertTrue(analyzer.first); - } - - @Test public void testInsn() { testInsn(NOP, true); testInsn(ACONST_NULL, true); @@ -294,17 +297,27 @@ public class LabelFlowAnalyzerTest { } @Test + public void testLineNumber() { + analyzer.visitLineNumber(42, label); + assertSame(label, analyzer.lineStart); + } + + @Test public void testMethodInsn() { + analyzer.visitLineNumber(42, label); analyzer.visitMethodInsn(INVOKEVIRTUAL, "Foo", "doit", "()V", false); assertTrue(analyzer.successor); assertFalse(analyzer.first); + assertTrue(LabelInfo.isMethodInvocationLine(label)); } @Test public void testInvokeDynamicInsn() { + analyzer.visitLineNumber(42, label); analyzer.visitInvokeDynamicInsn("foo", "()V", null); assertTrue(analyzer.successor); assertFalse(analyzer.first); + assertTrue(LabelInfo.isMethodInvocationLine(label)); } @Test diff --git a/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelInfoTest.java b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelInfoTest.java index c5d74a6c..d83bee6e 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelInfoTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/internal/flow/LabelInfoTest.java @@ -37,6 +37,7 @@ public class LabelInfoTest { public void testDefaults() { assertFalse(LabelInfo.isMultiTarget(label)); assertFalse(LabelInfo.isSuccessor(label)); + assertFalse(LabelInfo.isMethodInvocationLine(label)); assertFalse(LabelInfo.isDone(label)); assertEquals(LabelInfo.NO_PROBE, LabelInfo.getProbeId(label)); assertNull(LabelInfo.getIntermediateLabel(label)); @@ -90,6 +91,42 @@ public class LabelInfoTest { } @Test + public void testMethodInvocationLine() { + LabelInfo.setMethodInvocationLine(label); + assertTrue(LabelInfo.isMethodInvocationLine(label)); + } + + @Test + public void testNeedsProbe() { + testNeedsProbe(false, false, false, false); + testNeedsProbe(true, false, false, false); + testNeedsProbe(false, true, false, false); + testNeedsProbe(true, true, false, false); + testNeedsProbe(false, false, true, false); + testNeedsProbe(true, false, true, true); + testNeedsProbe(false, true, true, true); + testNeedsProbe(true, true, true, true); + } + + private void testNeedsProbe(boolean multitarget, + boolean methodinvocationline, boolean successor, boolean expected) { + if (multitarget) { + LabelInfo.setTarget(label); + LabelInfo.setTarget(label); + } + if (methodinvocationline) { + LabelInfo.setMethodInvocationLine(label); + } + if (successor) { + LabelInfo.setSuccessor(label); + } + assertTrue(expected == LabelInfo.needsProbe(label)); + + // Reset: + label = new Label(); + } + + @Test public void testSetResetDone1() { LabelInfo.setDone(label); assertTrue(LabelInfo.isDone(label)); diff --git a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ExceptionsTest.java b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ExceptionsTest.java index 03517826..40080914 100644 --- a/org.jacoco.core.test/src/org/jacoco/core/test/validation/ExceptionsTest.java +++ b/org.jacoco.core.test/src/org/jacoco/core/test/validation/ExceptionsTest.java @@ -36,7 +36,7 @@ public class ExceptionsTest extends ValidationTestBase { // 1. Implicit Exception // Currently no coverage at all, as we don't see when a block aborts // somewhere in the middle. - assertLine("implicitException.before", ICounter.NOT_COVERED); + assertLine("implicitException.before", ICounter.FULLY_COVERED); assertLine("implicitException.exception", ICounter.NOT_COVERED); assertLine("implicitException.after", ICounter.NOT_COVERED); @@ -55,7 +55,7 @@ public class ExceptionsTest extends ValidationTestBase { // somewhere in the middle. assertLine("implicitExceptionTryCatch.beforeBlock", ICounter.FULLY_COVERED); - assertLine("implicitExceptionTryCatch.before", ICounter.NOT_COVERED); + assertLine("implicitExceptionTryCatch.before", ICounter.FULLY_COVERED); assertLine("implicitExceptionTryCatch.exception", ICounter.NOT_COVERED); assertLine("implicitExceptionTryCatch.after", ICounter.NOT_COVERED); assertLine("implicitExceptionTryCatch.catchBlock", @@ -89,7 +89,7 @@ public class ExceptionsTest extends ValidationTestBase { // Finally block is yellow as the non-exception path is missing. assertLine("implicitExceptionFinally.beforeBlock", ICounter.FULLY_COVERED); - assertLine("implicitExceptionFinally.before", ICounter.NOT_COVERED); + assertLine("implicitExceptionFinally.before", ICounter.FULLY_COVERED); assertLine("implicitExceptionFinally.exception", ICounter.NOT_COVERED); assertLine("implicitExceptionFinally.after", ICounter.NOT_COVERED); assertLine("implicitExceptionFinally.finallyBlock", diff --git a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java index d986e536..1095d95d 100644 --- a/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java +++ b/org.jacoco.core/src/org/jacoco/core/data/ExecutionDataWriter.java @@ -24,7 +24,7 @@ public class ExecutionDataWriter implements ISessionInfoVisitor, IExecutionDataVisitor { /** File format version, will be incremented for each incompatible change. */ - public static final char FORMAT_VERSION = 0x1006; + public static final char FORMAT_VERSION = 0x1007; /** Magic number in header for file format identification. */ public static final char MAGIC_NUMBER = 0xC0C0; diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java index 08176dfd..157416ad 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelFlowAnalyzer.java @@ -54,6 +54,11 @@ public final class LabelFlowAnalyzer extends MethodVisitor { boolean first = true; /** + * Label instance of the last line start. + */ + Label lineStart = null; + + /** * Create new instance. */ public LabelFlowAnalyzer() { @@ -94,6 +99,11 @@ public final class LabelFlowAnalyzer extends MethodVisitor { } @Override + public void visitLineNumber(final int line, final Label start) { + lineStart = start; + } + + @Override public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { visitSwitchInsn(dflt, labels); @@ -174,6 +184,7 @@ public final class LabelFlowAnalyzer extends MethodVisitor { final String name, final String desc, final boolean itf) { successor = true; first = false; + markMethodInvocationLine(); } @Override @@ -181,6 +192,13 @@ public final class LabelFlowAnalyzer extends MethodVisitor { final Handle bsm, final Object... bsmArgs) { successor = true; first = false; + markMethodInvocationLine(); + } + + private void markMethodInvocationLine() { + if (lineStart != null) { + LabelInfo.setMethodInvocationLine(lineStart); + } } @Override diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java index 956761da..c6f1b763 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/LabelInfo.java @@ -31,6 +31,8 @@ public final class LabelInfo { private boolean successor = false; + private boolean methodInvocationLine = false; + private boolean done = false; private int probeid = NO_PROBE; @@ -105,6 +107,43 @@ public final class LabelInfo { } /** + * Mark a given label as the beginning of a line with method invocations. + * + * @param label + * label to mark + */ + public static void setMethodInvocationLine(final Label label) { + create(label).methodInvocationLine = true; + } + + /** + * Checks whether the a given label has been marked as a line with method + * invocations. + * + * @param label + * label to check + * @return <code>true</code> if the label represents a line with method + * invocations + */ + public static boolean isMethodInvocationLine(final Label label) { + final LabelInfo info = get(label); + return info == null ? false : info.methodInvocationLine; + } + + /** + * Determines whether the given label needs a probe to be inserted before. + * + * @param label + * label to test + * @return <code>true</code> if a probe should be inserted before + */ + public static boolean needsProbe(final Label label) { + final LabelInfo info = get(label); + return info != null && info.successor + && (info.multiTarget || info.methodInvocationLine); + } + + /** * Mark a given label as done. * * @param label diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java index 32ef9f10..5081fffc 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/MethodProbesAdapter.java @@ -68,8 +68,7 @@ public final class MethodProbesAdapter extends MethodVisitor { // a different label for the try-catch block. if (tryCatchProbeLabels.containsKey(start)) { start = tryCatchProbeLabels.get(start); - } else if (LabelInfo.isMultiTarget(start) - && LabelInfo.isSuccessor(start)) { + } else if (LabelInfo.needsProbe(start)) { final Label probeLabel = new Label(); LabelInfo.setSuccessor(probeLabel); tryCatchProbeLabels.put(start, probeLabel); @@ -80,7 +79,7 @@ public final class MethodProbesAdapter extends MethodVisitor { @Override public void visitLabel(final Label label) { - if (LabelInfo.isMultiTarget(label) && LabelInfo.isSuccessor(label)) { + if (LabelInfo.needsProbe(label)) { if (tryCatchProbeLabels.containsKey(label)) { probesVisitor.visitLabel(tryCatchProbeLabels.get(label)); } diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html index 458f858b..24182079 100644 --- a/org.jacoco.doc/docroot/doc/changes.html +++ b/org.jacoco.doc/docroot/doc/changes.html @@ -22,6 +22,8 @@ <h3>New Features</h3> <ul> + <li>Better detection of coverage in code blocks with implicit exceptions. + (GitHub <a href="https://github.com/jacoco/jacoco/issues/310">#310</a>).</li> <li>Added lifecycle-mapping-metadata.xml for M2E (GitHub <a href="https://github.com/jacoco/jacoco/issues/203">#203</a>).</li> <li>Allow locales with country and variant for Ant report task @@ -36,6 +38,12 @@ (GitHub <a href="https://github.com/jacoco/jacoco/issues/301">#301</a>).</li> </ul> +<h3>API Changes</h3> +<ul> + <li>The exec file version has been updated and is not compatible with previous + versions.</li> +</ul> + <h2>Release 0.7.4 (2015/02/26)</h2> <h3>Fixed Bugs</h3> diff --git a/org.jacoco.doc/docroot/doc/faq.html b/org.jacoco.doc/docroot/doc/faq.html index 1748b4dc..62d81143 100644 --- a/org.jacoco.doc/docroot/doc/faq.html +++ b/org.jacoco.doc/docroot/doc/faq.html @@ -45,13 +45,13 @@ duplicate classes or create separate reports or report groups for each version. </p> -<h3>Code with exceptions shows no coverage. Why?</h3> +<h3>Source code lines with exceptions show no coverage. Why?</h3> <p> JaCoCo determines code execution with so called probes. Probes are inserted into the control flow at certain positions. Code is considered as executed when a subsequent probe has been executed. In case of exceptions such a - sequence of instructions is aborted somewhere in the middle and not marked as - executed. + sequence of instructions is aborted somewhere in the middle and the + corresponding line of source code is not marked as covered. </p> <h3>Why does the coverage report not show line coverage figures?</h3> diff --git a/org.jacoco.doc/docroot/doc/flow.html b/org.jacoco.doc/docroot/doc/flow.html index 8eec88b7..98678bbc 100644 --- a/org.jacoco.doc/docroot/doc/flow.html +++ b/org.jacoco.doc/docroot/doc/flow.html @@ -257,6 +257,27 @@ public static example()V above. </p> +<h2>Additional Probes Between Lines</h2> + +<p> + The probe insertion strategy described so far does not consider implicit + exceptions thrown for example from invoked methods. If the control flow + between two probes is interrupted by a exception not explicitly created with + a <code>throw</code> statement all instruction in between are considered as + not covered. This leads to unexpected results especially when the the block of + instructions spans multiple lines of source code. +</p> + +<p> + Therefore JaCoCo adds an additional probe between the instructions of two + lines whenever the subsequent line contains at least one method invocation. + This limits the effect of implicit exceptions from method invocations to + single lines of source. The approach only works for class files compiled with + debug information (line numbers) and does not consider implicit exceptions + from other instructions than method invocations (e.g. + <code>NullPointerException</code> or <code>ArrayIndexOutOfBoundsException</code>). +</p> + <h2>Probe Implementation</h2> <p> |