New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add filter for Java assert statement #1196
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors | ||
* This program and the accompanying materials are made available under | ||
* the terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Evgeny Mandrikov - initial API and implementation | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.core.test.validation.java5; | ||
|
||
import org.jacoco.core.test.validation.ValidationTestBase; | ||
import org.jacoco.core.test.validation.java5.targets.AssertTarget; | ||
|
||
/** | ||
* Test of code coverage in {@link AssertTarget}. | ||
*/ | ||
public class AssertTest extends ValidationTestBase { | ||
|
||
public AssertTest() { | ||
super(AssertTarget.class); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors | ||
* This program and the accompanying materials are made available under | ||
* the terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Evgeny Mandrikov - initial API and implementation | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.core.test.validation.java5.targets; | ||
|
||
import static org.jacoco.core.test.validation.targets.Stubs.t; | ||
|
||
/** | ||
* This target exercises assert statement. | ||
*/ | ||
public class AssertTarget { // assertFullyCovered() | ||
|
||
private AssertTarget() { | ||
} | ||
|
||
public static void main(String[] args) { | ||
assert t() : "msg"; // assertPartlyCovered(1, 1) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors | ||
* This program and the accompanying materials are made available under | ||
* the terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* 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.Type; | ||
import org.objectweb.asm.tree.MethodNode; | ||
|
||
/** | ||
* Unit tests for {@link AssertFilter}. | ||
*/ | ||
public class AssertFilterTest extends FilterTestBase { | ||
|
||
private final AssertFilter filter = new AssertFilter(); | ||
|
||
/** | ||
* <code><pre> | ||
* class Example { | ||
* void example(boolean b) { | ||
* ... | ||
* assert b : "message"; | ||
* ... | ||
* } | ||
* } | ||
* </pre></code> | ||
*/ | ||
@Test | ||
public void should_filter_static_initializer() { | ||
context.className = "Example"; | ||
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, | ||
Opcodes.ACC_STATIC, "<clinit>", "()V", null, null); | ||
|
||
m.visitLdcInsn(Type.getType("LExample;")); | ||
final Range range = new Range(); | ||
range.fromInclusive = m.instructions.getLast(); | ||
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", | ||
"desiredAssertionStatus", "()Z", false); | ||
final Label label1 = new Label(); | ||
final Label label2 = new Label(); | ||
m.visitJumpInsn(Opcodes.IFNE, label1); | ||
m.visitInsn(Opcodes.ICONST_1); | ||
m.visitJumpInsn(Opcodes.GOTO, label2); | ||
m.visitLabel(label1); | ||
m.visitInsn(Opcodes.ICONST_0); | ||
m.visitLabel(label2); | ||
m.visitFieldInsn(Opcodes.PUTSTATIC, "Example", "$assertionsDisabled", | ||
"Z"); | ||
range.toInclusive = m.instructions.getLast(); | ||
m.visitInsn(Opcodes.RETURN); | ||
|
||
filter.filter(m, context, output); | ||
|
||
assertIgnored(range); | ||
} | ||
|
||
/** | ||
* <code><pre> | ||
* class Example { | ||
* static final f; | ||
* | ||
* static { | ||
* f = !Example.class.desiredAssertionStatus(); | ||
* } | ||
* } | ||
* </pre></code> | ||
*/ | ||
@Test | ||
public void should_not_filter_static_initializer_when_field_name_does_not_match() { | ||
context.className = "Example"; | ||
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, | ||
Opcodes.ACC_STATIC, "<clinit>", "()V", null, null); | ||
|
||
m.visitLdcInsn(Type.getType("LExample;")); | ||
m.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", | ||
"desiredAssertionStatus", "()Z", false); | ||
final Label label1 = new Label(); | ||
final Label label2 = new Label(); | ||
m.visitJumpInsn(Opcodes.IFNE, label1); | ||
m.visitInsn(Opcodes.ICONST_1); | ||
m.visitJumpInsn(Opcodes.GOTO, label2); | ||
m.visitLabel(label1); | ||
m.visitInsn(Opcodes.ICONST_0); | ||
m.visitLabel(label2); | ||
m.visitFieldInsn(Opcodes.PUTSTATIC, "Foo", "f", "Z"); | ||
m.visitInsn(Opcodes.RETURN); | ||
|
||
filter.filter(m, context, output); | ||
|
||
assertIgnored(); | ||
} | ||
|
||
/** | ||
* <code><pre> | ||
* class Example { | ||
* void example(boolean b) { | ||
* ... | ||
* assert b : "message"; | ||
* ... | ||
* } | ||
* } | ||
* </pre></code> | ||
*/ | ||
@Test | ||
public void should_filter_assert() { | ||
context.className = "Example"; | ||
final MethodNode m = new MethodNode(InstrSupport.ASM_API_VERSION, 0, | ||
"example", "()V", null, null); | ||
|
||
m.visitInsn(Opcodes.NOP); | ||
m.visitFieldInsn(Opcodes.GETSTATIC, "Example", "$assertionsDisabled", | ||
"Z"); | ||
final Label label = new Label(); | ||
m.visitJumpInsn(Opcodes.IFNE, label); | ||
final Range range = new Range(m.instructions.getLast(), | ||
m.instructions.getLast()); | ||
m.visitVarInsn(Opcodes.ILOAD, 1); | ||
m.visitJumpInsn(Opcodes.IFNE, label); | ||
m.visitTypeInsn(Opcodes.NEW, "java/lang/AssertionError"); | ||
m.visitInsn(Opcodes.DUP); | ||
m.visitLdcInsn("message"); | ||
m.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/AssertionError", | ||
"<init>", "(Ljava/lang/Object;)V", false); | ||
m.visitInsn(Opcodes.ATHROW); | ||
m.visitLabel(label); | ||
m.visitInsn(Opcodes.NOP); | ||
|
||
filter.filter(m, context, output); | ||
|
||
assertIgnored(range); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/******************************************************************************* | ||
* Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors | ||
* This program and the accompanying materials are made available under | ||
* the terms of the Eclipse Public License 2.0 which is available at | ||
* http://www.eclipse.org/legal/epl-2.0 | ||
* | ||
* SPDX-License-Identifier: EPL-2.0 | ||
* | ||
* Contributors: | ||
* Evgeny Mandrikov - initial API and implementation | ||
* | ||
*******************************************************************************/ | ||
package org.jacoco.core.internal.analysis.filter; | ||
|
||
import org.objectweb.asm.Opcodes; | ||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
|
||
/** | ||
* Filters code that is generated for an <code>assert</code> statement. | ||
*/ | ||
final class AssertFilter implements IFilter { | ||
|
||
public void filter(final MethodNode methodNode, | ||
final IFilterContext context, final IFilterOutput output) { | ||
final Matcher matcher = new Matcher(); | ||
if ("<clinit>".equals(methodNode.name)) { | ||
for (final AbstractInsnNode i : methodNode.instructions) { | ||
matcher.matchSet(context.getClassName(), i, output); | ||
} | ||
} | ||
for (final AbstractInsnNode i : methodNode.instructions) { | ||
matcher.matchGet(context.getClassName(), i, output); | ||
} | ||
} | ||
|
||
private static class Matcher extends AbstractMatcher { | ||
public void matchSet(final String className, | ||
final AbstractInsnNode start, final IFilterOutput output) { | ||
cursor = start; | ||
nextIsInvoke(Opcodes.INVOKEVIRTUAL, "java/lang/Class", | ||
"desiredAssertionStatus", "()Z"); | ||
nextIs(Opcodes.IFNE); | ||
nextIs(Opcodes.ICONST_1); | ||
nextIs(Opcodes.GOTO); | ||
nextIs(Opcodes.ICONST_0); | ||
nextIsField(Opcodes.PUTSTATIC, className, "$assertionsDisabled", | ||
"Z"); | ||
if (cursor != null) { | ||
output.ignore(start, cursor); | ||
} | ||
} | ||
|
||
public void matchGet(final String className, | ||
final AbstractInsnNode start, final IFilterOutput output) { | ||
cursor = start; | ||
nextIsField(Opcodes.GETSTATIC, className, "$assertionsDisabled", | ||
"Z"); | ||
nextIs(Opcodes.IFNE); | ||
if (cursor != null) { | ||
output.ignore(cursor, cursor); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we be ignoring the entire assert test here? I still get a "1 of 2 branches missed" for my assert statements. The only way to cover both branches is to run the test twice, once with assertions disabled, then again with assertions enabled, all in the same test suite. How about I've tested the above code fragment and it marks the assert line as green, without complaining about 1 of 2 branches missed. (it also doesn't think the line has multiple branches either) |
||
} | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What instructions are left on this line? Shouldn't it be empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marchof we ignore only part of static initializer that corresponds to initialization of field
$assertionsDisabled
, so in this case there is alsoreturn
with line number of class:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether we should also ignore the
return
if there is no other code. Otherwise we have this line highlighted only if there is a assertion statement in this class.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if I add a static initializer the
return
is accounted to that line and the highlighting of the class definition disappears. I think it is more consistent if we ignore the return statement if there is no other code:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also at least default constructor has line of class declaration and part of static initializer generated for
enum
s.This will also exclude line of explicitly written empty static initializer in class with
assert
statements:I believe that our users are more concerned about branch that without this change is displayed on a line of class declaration. And not sure whether they will be concerned about just highlighting of this line or about absense of highlighting for explicitly written empty static initializers.
Should we then also for consistency also filter explicitly written empty static initializers in absence of
assert
statements? Is it worth it? I'm not against both, just wondering how deep this rabbit hole can go to not overlook something - wondering if there might be other cases that we currently overlooking?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, looks like we will end up with too many corner cases. We leave it like it is!