Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jacoco classes in bundle do not match for Transactional methods

I'm testing an app with JUnit5 and using Jacoco for coverage report. Tests are executed Ok and test reports are present.

However, Jacoco report has the following logs if service contains methods, annotated with @Transactional

[ant:jacocoReport] Classes in bundle 'my-service' do no match with execution data. For report generation the same class files must be used as at runtime.
[ant:jacocoReport] Execution data for class mypackage/SampleService does not match. 

This error occurres for all @Service classes methods, annotated with @Transactional, plain classes coverage is calculated ok.

Here's a sample test:

@SpringBootTest
@ExtendWith(SpringExtension.class)
public class MyServiceTest {

    @Autowired
    private SampleService sampleService;

    @Test
    public void doWork(){
        sampleService.doWork();
    }
}

Works fine. Coverage is non-zero:

public class SampleService {

    public void doWork(){
        System.out.println("HEY");
    }
}

0% coverage:

public class SampleService {

    @Transactional
    public void doWork(){
        System.out.println("HEY");
    }
}

Transactional creates a proxy around actuall class. But, isn't there an out-of-box way for Jacoco to handle such a common situation?

I've tried @EnableAspectJAutoProxy annotaion with different flag variations, checked that up-to-date Jupiter engine and Jacoco plugin are used

Here's gradle config:

subprojects {
    test {       
        useJUnitPlatform()
    }

    jacocoTestReport {
        afterEvaluate {
            classDirectories.from = files(classDirectories.files.collect {
                fileTree(dir: it, exclude: '*Test.java')
            })
        }

        reports {
            html.enabled = true
            xml.enabled = true
            csv.enabled = false
        }
    }
}

Any help appreciated

like image 716
Ermintar Avatar asked Apr 29 '20 11:04

Ermintar


2 Answers

I tried it with a test project, similar to what you've described, however I couldn't reproduce the issue. The only difference that I see between your project and mine, is that I've used maven instead of gradle.

Here is the test project: https://github.com/gybandi/jacocotest

And here is the jacoco result for it (using org.springframework.transaction.annotation.Transactional annotation): jacoco test report

If this doesn't help you, could you upload your test project to github or some other place?

Edit: @MikaelF posted a link to another answer, which shows how to add offline instrumentation for jacoco.

The solution that was described there worked for me, after I added the following block to build.gradle:

task instrument(dependsOn: [classes, project.configurations.jacocoAnt]) {

    inputs.files classes.outputs.files
    File outputDir = new File(project.buildDir, 'instrumentedClasses')
    outputs.dir outputDir
    doFirst {
        project.delete(outputDir)
        ant.taskdef(
                resource: 'org/jacoco/ant/antlib.xml',
                classpath: project.configurations.jacocoAnt.asPath,
                uri: 'jacoco'
        )
        def instrumented = false
            if (file(sourceSets.main.java.outputDir).exists()) {
                def instrumentedClassedDir = "${outputDir}/${sourceSets.main.java}"
                ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
                    fileset(dir: sourceSets.main.java.outputDir, includes: '**/*.class')
                }
                //Replace the classes dir in the test classpath with the instrumented one
                sourceSets.test.runtimeClasspath -= files(sourceSets.main.java.outputDir)
                sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
                instrumented = true
            }
        if (instrumented) {
            //Disable class verification based on https://github.com/jayway/powermock/issues/375
            test.jvmArgs += '-noverify'
        }
    }
}
test.dependsOn instrument

There seems to be an open ticket on the jacoco plugin's github about this as well: https://github.com/gradle/gradle/issues/2429

like image 198
gybandi Avatar answered Oct 16 '22 23:10

gybandi


Based on single module instrumentation example https://stackoverflow.com/a/31916686/7096763 (and updated version for gradle 5+ by @MikaelF) here's example for multimodule instrumentation:

subprojects {  subproject ->
    subproject.ext.jacocoOfflineSourceSets = [ 'main' ]
    task doJacocoOfflineInstrumentation(dependsOn: [ classes, subproject.configurations.jacocoAnt ]) {
        inputs.files classes.outputs.files
        File outputDir = new File(subproject.buildDir, 'instrumentedClasses')
        outputs.dir outputDir
        doFirst {
            project.delete(outputDir)
            ant.taskdef(
                    resource: 'org/jacoco/ant/antlib.xml',
                    classpath: subproject.configurations.jacocoAnt.asPath,
                    uri: 'jacoco'
            )
            def instrumented = false
            jacocoOfflineSourceSets.each { sourceSetName ->
                if (file(sourceSets[sourceSetName].output.classesDirs[0]).exists()) {
                    def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
                    ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
                        fileset(dir: sourceSets[sourceSetName].output.classesDirs[0], includes: '**/*.class')
                    }
                    //Replace the classes dir in the test classpath with the instrumented one
                    sourceSets.test.runtimeClasspath -= files(sourceSets[sourceSetName].output.classesDirs[0])
                    sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
                    instrumented = true
                }
            }
            if (instrumented) {
                //Disable class verification based on https://github.com/jayway/powermock/issues/375
                test.jvmArgs += '-noverify'
            }
        }
    }
    test.dependsOn doJacocoOfflineInstrumentation
}

full example here: https://github.com/lizardeye/jacocomultimodulesample

Still, I think this is a durty hack, which can be easily broken with gradle or jacoco updates

like image 35
Ermintar Avatar answered Oct 17 '22 00:10

Ermintar