diff --git a/pitest-entry/src/test/java/org/pitest/coverage/CoverageTransformerTest.java b/pitest-entry/src/test/java/org/pitest/coverage/CoverageTransformerTest.java new file mode 100644 index 000000000..26d9efd05 --- /dev/null +++ b/pitest-entry/src/test/java/org/pitest/coverage/CoverageTransformerTest.java @@ -0,0 +1,66 @@ +package org.pitest.coverage; + +import org.junit.Test; +import org.mockito.Mockito; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.pitest.bytecode.analysis.ClassTree; +import org.pitest.bytecode.analysis.MethodTree; +import org.pitest.classpath.ClassloaderByteArraySource; +import sun.pitest.CodeCoverageStore; +import sun.pitest.InvokeReceiver; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +// additional tests for coverage transformer which are +// easier to write within pitest-entry due to access to +// ClassTree +public class CoverageTransformerTest { + + ClassloaderByteArraySource byteSource = ClassloaderByteArraySource.fromContext(); + CoverageTransformer underTest = new CoverageTransformer(s -> true); + + @Test + public void doesNotDuplicateClinitWhenSynthetic() { + + byte[] bytes = bytesForClassWithSyntheticStaticInit(); + + CodeCoverageStore.init(Mockito.mock(InvokeReceiver.class)); + + byte[] transformed = underTest.transform(null, "anything", null, null, bytes); + + ClassTree instrumentedClass = ClassTree.fromBytes(transformed); + + List clinitMethods = instrumentedClass.methods().stream() + .filter(m -> m.rawNode().name.equals("")) + .collect(Collectors.toList()); + + assertThat(clinitMethods).hasSize(1); + } + + private byte[] bytesForClassWithSyntheticStaticInit() { + ClassTree classWithStaticInit = ClassTree.fromBytes(byteSource.getBytes(HasStaticInit.class.getName()).get()); + MethodTree clinit = classWithStaticInit.methods().stream() + .filter(m -> m.rawNode().name.equals("")) + .findAny() + .get(); + + clinit.rawNode().access = Opcodes.ACC_SYNTHETIC; + + byte[] bytes = asBytes(classWithStaticInit); + return bytes; + } + + private byte[] asBytes(ClassTree tree) { + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + tree.rawNode().accept(classWriter); + return classWriter.toByteArray(); + } +} + +class HasStaticInit { + static String FOO = ""; +} diff --git a/pitest/src/main/java/org/pitest/classinfo/MethodFilteringAdapter.java b/pitest/src/main/java/org/pitest/classinfo/MethodFilteringAdapter.java index 7c71afae3..cdeecdac6 100644 --- a/pitest/src/main/java/org/pitest/classinfo/MethodFilteringAdapter.java +++ b/pitest/src/main/java/org/pitest/classinfo/MethodFilteringAdapter.java @@ -37,6 +37,8 @@ private boolean shouldInstrument(final int access, final String name, @Override public final MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { + + preVisitMethod(access, name, desc, signature, exceptions); final MethodVisitor methodVisitor = this.cv.visitMethod(access, name, desc, signature, exceptions); if (shouldInstrument(access, name, desc, signature, exceptions)) { @@ -47,6 +49,10 @@ public final MethodVisitor visitMethod(final int access, final String name, } } + protected void preVisitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // noop + } + public abstract MethodVisitor visitMethodIfRequired(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor methodVisitor); diff --git a/pitest/src/main/java/org/pitest/coverage/CoverageClassVisitor.java b/pitest/src/main/java/org/pitest/coverage/CoverageClassVisitor.java index 4ddad4df4..2172c32f2 100644 --- a/pitest/src/main/java/org/pitest/coverage/CoverageClassVisitor.java +++ b/pitest/src/main/java/org/pitest/coverage/CoverageClassVisitor.java @@ -59,16 +59,18 @@ public void visit(int version, int access, String name, String signature, this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0; } + @Override + protected void preVisitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (name.equals("")) { + foundClinit = true; + } + } @Override public MethodVisitor visitMethodIfRequired(final int access, final String name, final String desc, final String signature, final String[] exceptions, final MethodVisitor methodVisitor) { - if (name.equals("")) { - foundClinit = true; - } - return new CoverageAnalyser(this, this.classId, this.probeCount, methodVisitor, access, name, desc, signature, exceptions); diff --git a/pitest/src/test/java/org/pitest/coverage/CoverageTransformerTest.java b/pitest/src/test/java/org/pitest/coverage/CoverageTransformerTest.java index f216ede58..b4b35c1bc 100644 --- a/pitest/src/test/java/org/pitest/coverage/CoverageTransformerTest.java +++ b/pitest/src/test/java/org/pitest/coverage/CoverageTransformerTest.java @@ -62,7 +62,7 @@ public void shouldNotTransformClassesNotMatchingPredicate() { } @Test - public void shouldTransformClasseMatchingPredicate() { + public void shouldTransformClassesMatchingPredicate() { final CoverageTransformer testee = new CoverageTransformer( s -> true); final byte[] bs = this.bytes.getBytes(String.class.getName()).get(); @@ -71,7 +71,7 @@ public void shouldTransformClasseMatchingPredicate() { } @Test - public void shouldGenerateValidClasses() throws IllegalClassFormatException { + public void shouldGenerateValidClasses() { assertValidClass(String.class); assertValidClass(Integer.class); assertValidClass(Vector.class);