I am using the Parceler library on Android (https://github.com/johncarl81/parceler)
And I am also using Jacoco for code coverage.
However after adding Parceler running my test code coverage with Jacoco now fails with the following error:
Caused by: java.io.FileNotFoundException:
/Users/me/android/myapp/blah/app/build/intermediates/classes/demo/com/me/blah/appname/model/User$Parcelable$Creator$0.class
(No such file or directory)
The User class is as follows:
#Parcel
public class User {
#SerializedName("firstName")
String firstName;
#SerializedName("lastName")
String lastName;
#SerializedName("profileImage")
ProfileImage profileImage;
#SerializedName("username")
String username;
// empty constructor needed by the Parceler library
public User() {
}
}
And my Jacoco file is like this:
apply plugin: 'jacoco'
android {
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
project.afterEvaluate {
android.applicationVariants.all { variant ->
def name = variant.name
def testTaskName = "test${name.capitalize()}UnitTest"
tasks.create(name: "${testTaskName}Coverage", type: JacocoReport, dependsOn: "$testTaskName") {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${name.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${name}",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*$ViewBinder*.*',
'**/*$ViewInjector*.*',
'**/Lambda$*.class',
'**/Lambda.class',
'**/*Lambda.class',
'**/*Lambda*.class',
'**/*InjectAdapter*.*',
'**/*StaticInjection*.*',
'**/*ModuleAdapter*.*']
)
sourceDirectories = files(['src/main/java'].plus(android.sourceSets[name].java.srcDirs))
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
}
}
I am wondering do I need to add the generated Parceler files to the excludes list in my Jacoco file? If so how do I do that? I have tried various variations but none seem to work.
The error complains that the following file is missing:
User$Parcelable$Creator$0.class
Which it is and instead the file is generated as follows:
User$$Parcelable.class
Can anyone explain this?
I just exclude it in jacocoAndroidCoverage.gradle, like this and it works now
def coverageSourceDirs = [
'../app/src/main/java'
]
task jacocoStagingDebugCoverageReport(type:JacocoReport, dependsOn: ["connectedStagingDebugAndroidTest"]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for staging debug"
classDirectories = fileTree(
dir: '../app/build/intermediates/classes/staging/debug/com/',
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/*MembersInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*$Lambda$*.class',
'**/*Factory*.class',
'**/*$Builder*',
'**/*$Parcelable*.*',
'**/*DaggerApplicationComponent*.class',
'**/api']
)
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files('build/outputs/code-coverage/connected/flavors/staging/coverage.ec')
reports {
xml.enabled = true
html.enabled = true
}
}
Try this:
jacocoAndroidUnitTestReport {
excludes += ['**/*Creator.class']
}
Related
I have the following classes in separated files:
#Path("/products")
#Produces(MediaType.APPLICATION_JSON)
class ProductController{
#Inject
private ProductService productService;
}
#ApplicationScoped
public class ProductService {
#Inject
private ProductRepository productRepository;
}
#ApplicationScoped
public class ProductRepository implements PanacheRepositoryBase<Product, UUID> {
}
And when I run the tests coverage, all controllers and services and other packages are correctly recognized by JaCoCo, but all repositories aren't, resulting in 0% coverage in this package. And I'm sure that they are being used by services.
My guess is because JaCoCo only recognizes up to 1 level of "proxy", due to #AppicationScoped annotation, but I'm not sure.
I also tried offline intrumentation, but the result is the same.
This is my gradle config now:
plugins {
id 'java'
id 'io.quarkus'
id 'jacoco'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// a lot of deps not related to tests
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileJava {
options.encoding = 'UTF-8'
options.compilerArgs << '-parameters'
}
compileTestJava {
options.encoding = 'UTF-8'
}
test {
useJUnitPlatform()
jacoco {
destinationFile = file("$buildDir/jacoco/test.exec")
}
finalizedBy jacocoTestReport
}
jacoco {
toolVersion = '0.8.6'
reportsDir = file("$buildDir/reports/jacoco")
}
jacocoTestCoverageVerification {
executionData fileTree("$buildDir/jacoco/").include("*.exec")
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it)
}))
}
}
jacocoTestReport {
executionData fileTree("$buildDir/jacoco/").include("*.exec")
reports {
xml.enabled false
csv.enabled false
html.destination file("${buildDir}/reports/jacoco")
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it)
}))
}
}
And this is the other "version" of gradle config, with offline instrumentation:
configurations {
jacocoAnt
jacocoRuntime
}
dependencies {
jacocoAnt group: 'org.jacoco', name: 'org.jacoco.ant', version: '0.8.5', classifier: 'nodeps'
jacocoRuntime group: 'org.jacoco', name: 'org.jacoco.agent', version: '0.8.5', classifier: 'runtime'
}
test {
useJUnitPlatform()
jacoco {
destinationFile = file("$buildDir/reports/jacoco/jacoco-sonar/jacoco-coverage.exec")
}
}
jacoco {
toolVersion = "0.8.5"
reportsDir = file("$buildDir/customJacocoReportDir")
}
jacocoTestReport {
reports {
xml.enabled false
csv.enabled false
html.destination file("${buildDir}/jacocoHtml")
}
}
jacocoTestCoverageVerification {
executionData fileTree("$buildDir/reports/jacoco/jacoco-sonar/").include("*.exec")
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it)
}))
}
violationRules {
rule {
limit {
minimum = 0.74
}
}
}
}
task instrument(dependsOn: ['classes']) {
ext.outputDir = buildDir.path + '/reports/classes-instrumented'
doLast {
ant.taskdef(name: 'instrument',
classname: 'org.jacoco.ant.InstrumentTask',
classpath: configurations.jacocoAnt.asPath)
ant.instrument(destdir: outputDir) {
sourceSets.main.output.classesDirs.each { fileset(dir: it) }
}
}
}
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(instrument)) {
tasks.withType(Test) {
doFirst {
classpath = files(instrument.outputDir) + classpath + configurations.jacocoRuntime
}
}
}
}
task report(dependsOn: ['instrument', 'test']) {
doLast {
ant.taskdef(name: 'report',
classname: 'org.jacoco.ant.ReportTask',
classpath: configurations.jacocoAnt.asPath)
ant.report() {
executiondata {
ant.file(file: buildDir.path + '/reports/jacoco/jacoco-sonar/jacoco-coverage.exec')
}
structure(name: 'Example') {
classfiles {
sourceSets.main.output.classesDirs.each { fileset(dir: it) }
}
sourcefiles {
fileset(dir: 'src/main/java')
}
}
html(destdir: buildDir.path + '/reports/jacoco')
}
}
}
Why it happens?
I was facing the same problem as you, after include the following extension the coverage problem for #ApplicationScoped annotated classes was solved.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
<scope>test</scope>
</dependency>
Obviously, since you are using Gradle, you should change it. I also needed to remove jacoco-maven-plugin plugin from maven plugins section. Probably you will need to remove everything that you included for custom instrumentation.
Quarkus bytecode instrumentation and JaCoCo can step on each other toes, JaCoCo default mode uses an agent the inject some bytecode and this can be incompatible with Quarkus' own bytecode injection.
You can switch JaCoCo to offline instrumentation instead, please follow this section of the Quarkus test coverage guide:
https://quarkus.io/guides/tests-with-coverage#instrumenting-the-classes-instead
I have into the same problem quite a lot of people have here, which is getting proper code coverage information when using Jacoco/Gradle and Powermock.
I have read all the various threads here and in other places and I have successfully managed to create a task (for Gradle 6.4) that does offline instrumentation of my project's classes. For reference the code that does this is the following:
task instrumentClasses(dependsOn: [ classes, project.configurations.jacocoAnt ]) {
inputs.files classes.outputs.files
File outputDir = new File(project.buildDir, 'instrumented')
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
jacocoOfflineSourceSets.each { sourceSetName ->
if (file(sourceSets[sourceSetName as String].output.classesDirs.singleFile.absolutePath).exists()) {
def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
fileset(dir: sourceSets[sourceSetName as String].output.classesDirs.singleFile, includes: '**/*.class')
}
//Replace the classes dir in the test classpath with the instrumented one
sourceSets.test.runtimeClasspath -= sourceSets[sourceSetName as String].output.classesDirs
sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
instrumented = true
}
}
if (instrumented) {
//Disable class verification based on https://github.com/jayway/powermock/issues/375
test.jvmArgs += '-noverify'
}
}
}
Now, for the most part this seems to work alright. I have successfully verified that my classes are now properly instrumented and I'm seeing a Jacoco produced report which has the correct information. Problem is though that my SonarQube server still lists the classes in question as non covered. Regarding this I have no idea as to what I need to do to resolve it.
For reference I am using the following version of the sonarqube plugin:
"org.sonarqube" version "2.7"
And my CI runs the Gradle task in the following manner:
- ./gradlew jacocoTestReport sonarqube ${SONAR_GRADLE_EXTRA_PARAMS} -Dsonar.projectKey=${CI_PROJECT_ID} -Dsonar.host.url=${SONAR_URL} -Dsonar.login=${SONAR_LOGIN} -Dsonar.branch.name=${CI_COMMIT_REF_NAME};
I do get that it must be some configuration issue with either SonarQube or the way I run the Gradle task but I am not really sure as to what is the culprit.
If you are able to generate the aggregated jacoco report (aggregated from all source sets), then you can simply specify that in your sonarqube task while running (and sonar will just pick the exact coverage info that jacoco calculated)
./gradlew sonarqube -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=XXXX -Dsonar.organization=XXXXX -Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco-report.xml
FYI I am creating the aggregated report at build/jacoco-report.xml
Below is my gradle configuration (might be useful for you)
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'jacoco'
id "org.sonarqube" version "2.8"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
sourceSets {
intTest {
compileClasspath += sourceSets.main.output + sourceSets.test.output
runtimeClasspath += sourceSets.main.output + sourceSets.test.output
}
}
configurations {
intTestImplementation.extendsFrom testImplementation
intTestRuntimeOnly.extendsFrom testRuntimeOnly
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true //To print logs
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.intTest.output.classesDirs
classpath = sourceSets.intTest.runtimeClasspath
shouldRunAfter test
testLogging.showStandardStreams = true //To print logs
}
jacocoTestReport {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
reports {
xml.enabled true
csv.enabled false
xml.destination file("${buildDir}/jacoco-report.xml")
html.destination file("${buildDir}/jacocoHtml")
}
mustRunAfter(test, integrationTest) // integration tests are required to run before generating the report
}
jacocoTestCoverageVerification {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
violationRules {
rule {
limit {
counter = 'INSTRUCTION'
minimum = 0.94
}
limit {
counter = 'BRANCH'
minimum = 1.0
}
}
}
}
check.dependsOn(integrationTest, jacocoTestCoverageVerification)
tasks.withType(Test) {
finalizedBy jacocoTestReport
}
I have a multimodule project and some TestNG tests, covering them.
My gradle config looks like:
subprojects { subproject ->
jacocoTestReport {
additionalSourceDirs = files(sourceSets.main.allSource.srcDirs)
classDirectories = files(sourceSets.main.output)
sourceDirectories = files(sourceSets.main.allSource.srcDirs)
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
}
test {
useTestNG()
}
test.finalizedBy(project.tasks.jacocoTestReport)
}
In the report I expected to see code coverage based on sources from main package (provided by sourceSets.main),however all java classes from test package are also included. So the coverage result is invalid.
How can the config be fixed?
All my tests had a naming pattern, so I could exclude them by regex:
jacocoTestReport {
additionalSourceDirs = files(sourceSets.main.allSource.srcDirs)
classDirectories = files(sourceSets.main.output)
sourceDirectories = files(sourceSets.main.allSource.srcDirs)
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it, exclude: 'path/to/test/*Test.java')
})
}
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
}
I'm trying to clean build project with Spring boot plugin and getting the following message:
Execution failed for task ':lecture05:findMainClass'.
Unable to find a single main class from the following candidates [ru.atom.boot.mm.MatchMakerApp, ru.atom.boot.hw.HelloSpringBoot]
I can't find any information for this case here. I've found a couple of questions like this, but this is for maven. How to config my project correctly?
I was trying to add
bootRepackage {
mainClass = 'ru.atom.boot.mm.MatchMakerApp'
}
to build.gradle
My root project:
plugins {
id 'org.springframework.boot' version '1.5.8.RELEASE'
id 'com.github.kt3k.coveralls' version '2.6.3'
}
bootRepackage {
mainClass = 'ru.atom.boot.mm.MatchMakerApp'
}
ext {
jdkVersion = 1.9
jettyVersion = "9.4.7.v20170914"
junitVersion = "4.12"
jacksonVersion = "2.9.1"
log4jVersion = "2.7"
jetbrainsAnnotationVersion = "15.0"
okhttpVersion = "3.6.0"
jerseyVersion = "2.26"
gsonjVersion = "2.7"
postgresVersion = "9.4-1200-jdbc41"
jetbrainsAnnotationVersion = "15.0"
hibernateVersion = "5.2.3.Final"
websocketVersion = "9.4.3.v20170317"
jolVersion = "0.8"
}
allprojects {
group = "technoatom"
version = "1.0-SNAPSHOT"
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'checkstyle'
apply plugin: 'jacoco'
repositories {
mavenCentral()
}
sourceCompatibility = jdkVersion
targetCompatibility = jdkVersion
}
subprojects {
checkstyle {
ignoreFailures = false
toolVersion = '7.5'
configFile = rootProject.file('config/checkstyle/checkstyle.xml')
}
tasks.withType(Checkstyle) {
reports {
xml.enabled false
html.destination
"$rootProject.buildDir/report/${project.name}.html"
html.stylesheet
resources.text.fromFile(rootProject.file('config/checkstyle/checkstyle-custom.xsl'))
}
}
}
ext.libraries = [
spring_boot : [
"org.springframework.boot:spring-boot-starter-web",
"org.springframework.boot:spring-boot-starter-actuator"
],
spring_boot_test : "org.springframework.boot:spring-boot-starter-test",
jetty_server : "org.eclipse.jetty:jetty-server:$jettyVersion",
jetty_servlet: "org.eclipse.jetty:jetty-servlet:$jettyVersion",
junit: "junit:junit:$junitVersion",
log4j: [
"org.apache.logging.log4j:log4j-api:$log4jVersion",
"org.apache.logging.log4j:log4j-core:$log4jVersion"
],
jetbrainsAnnotations: "org.jetbrains:annotations:$jetbrainsAnnotationVersion",
okhttp: "com.squareup.okhttp3:okhttp:$okhttpVersion",
jersey_server: "org.glassfish.boot.core:boot-server:$jerseyVersion",
jersey_hk2: "org.glassfish.boot.inject:boot-hk2:$jerseyVersion",
jersey_containers: "org.glassfish.boot.containers:boot-container-servlet:$jerseyVersion",
jersey_test:
"org.glassfish.boot.test-framework.providers:boot-test-framework-provider-grizzly2:$jerseyVersion",
gson: "com.google.code.gson:gson:$gsonjVersion",
postgres: "org.postgresql:postgresql:$postgresVersion",
hibernate: "org.hibernate:hibernate-core:$hibernateVersion",
websocketclient: "org.eclipse.jetty.websocket:websocket-client:$websocketVersion",
websocketserver: "org.eclipse.jetty.websocket:websocket-server:$websocketVersion",
websocketapi: "org.eclipse.jetty.websocket:websocket-api:$websocketVersion",
jackson: "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion",
jol: "org.openjdk.jol:jol-core:$jolVersion",
jol_samples: "org.openjdk.jol:jol-samples:$jolVersion"
]
jacocoTestReport {
additionalSourceDirs =
files(subprojects.sourceSets.main.allSource.srcDirs)
sourceDirectories =
files(subprojects.sourceSets.main.allSource.srcDirs)
classDirectories = files(subprojects.sourceSets.main.output)
executionData = files(subprojects.jacocoTestReport.executionData)
onlyIf = {
true
}
reports {
xml.enabled = true
html.enabled = true
}
doFirst {
executionData = files(executionData.findAll {
it.exists()
})
}
}
coveralls {
sourceDirs =
files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath
}
Subproject, I'm trying to build, that has two Main classes:
dependencies {
compile rootProject.libraries.spring_boot
compile rootProject.libraries.log4j
testCompile rootProject.libraries.junit
testCompile rootProject.libraries.spring_boot_test
}
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
}
test {
java {
srcDirs = ['src/test/java']
}
}
}
Try to replace it with:
springBoot {
mainClass = 'ru.atom.boot.mm.MatchMakerApp'
}
As jprism mentioned you can read more in Spring Boot plugin docs
We use in our java project jacoco with the gradle-plugin to calculate the coverage.
The problem is we put our classes which were generated from an XML in an extra project and resolved it as a dependency. We want the codecoverage of these model classes too to analyze it. To check if we used all setter methods of the datacontainers in our mapper classes. Do the code coverage in the model project is not an option.
Currently jacoco only shows in the report (html/xml/csv) only our classes which is in the main project but not the classes of external jars. The jacoco session contains the coverage data when I load it with eclipse or intellij.
buildscript {
...
dependencies {
...
classpath 'externalpackage:externalpackage-model'
...
}
}
compile('externalpackage:externalpackage-model:0.0.8')
testCompile('externalpackage:externalpackage-model:tests#jar')
testCompile('externalpackage:externalpackage-model:0.0.8:sources#jar')
jacoco {
toolVersion = "0.7.6.201602180812"
reportsDir = file("$buildDir/customJacocoReportDir")
}
jacocoTestReport {
reports {
xml.enabled true
csv.enabled true
html.enabled true
html.destination "${buildDir}/jacocoHtml"
}
additionalSourceDirs files('externalpackage:externalpagage:0.0.8:sources#jar')
//Doesn't work either
//additionalSourceDirs files('C:/Users/sero/Downloads/test/externalpackage-0.0.8-sources')
//additionalSourceDirs = files('C:/Users/sero/Downloads/test/externalpackage-0.0.8-sources/de/mycompany/.../MyModelClasses.java')
}
The jar source package is like this:
(root)/de/mycompany/.../MyModelClasses.java
Maybe someone has an idea
Found it by myself. Problem was, you need to specify the path the the classes too.
I unzipped the jars into the build folder and added additionalClassDirs and additionalSourceDirs to the report job.
This is the buildfile.
configurations {
externalClasses
externalSources
}
dependencies {
externalClasses "externalpackage:externalpackage-model:0.0.8#jar"
externalSources "externalpackage:externalpackage-model:0.0.8:sources#jar"
...
}
buildscript {
...
dependencies {
...
classpath 'externalpackage:externalpackage-model'
...
}
}
compile('externalpackage:externalpackage-model:0.0.8')
testCompile('externalpackage:externalpackage-model:tests#jar')
testCompile('externalpackage:externalpackage-model:0.0.8:sources#jar')
jacoco {
toolVersion = "0.7.6.201602180812"
reportsDir = file("$buildDir/customJacocoReportDir")
}
task unzipExternalModel(type: Copy){
from zipTree(configurations.externalSources.files.first())
into "$buildDir/tmp/externalSources"
from zipTree(configurations.externalClasses.files.first())
into "$buildDir/tmp/externalClasses"
}
jacocoTestReport {
dependsOn unzipExternalModel
reports {
xml.enabled true
csv.enabled true
html.enabled true
html.destination "${buildDir}/jacocoHtml"
}
additionalSourceDirs = files("$buildDir/tmp/externalSources")
additionalClassDirs = files("$buildDir/tmp/externalClasses")
}
Just for the record, using 7.1.1, this code (inpired on the Vincent's) works:
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
jacoco {
includes = ["com.mycompany.**"]
includeNoLocationClasses true
}
}
task unzipLibrariesJar(type: Copy){
into "$buildDir/tmp/libClasses"
from {
configurations.runtimeClasspath
.filter { it.path.contains("mycompany") }
.collect {zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
task unzipLibrariesSrc(type: Copy){
into "$buildDir/tmp/libSources"
from {
configurations.runtimeClasspath
.filter { it.path.contains("mycompany") && new File(it.getPath().replace(".jar", "-sources.jar")).exists() }
.collect { new File(it.getPath().replace(".jar", "-sources.jar")) }
.collect { zipTree(it) }
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
jacocoTestReport {
dependsOn test
dependsOn unzipLibrariesJar
dependsOn unzipLibrariesSrc
reports {
html.enabled true
}
additionalClassDirs.from = files("$buildDir/tmp/libClasses")
additionalSourceDirs.from = files("$buildDir/tmp/libSources")
}
Note that, I am only interested on the logs from all classes of "com.mycompany" package. You should change this according to your requirements.