diff --git a/src/main/kotlin/SourceReportParser.kt b/src/main/kotlin/SourceReportParser.kt index a930661..be7a6fc 100644 --- a/src/main/kotlin/SourceReportParser.kt +++ b/src/main/kotlin/SourceReportParser.kt @@ -14,10 +14,12 @@ import java.io.File import java.math.BigInteger import java.security.MessageDigest -data class SourceReport(val name: String, val source_digest: String, val coverage: List) +data class SourceReport(val name: String, val source_digest: String, val coverage: List, val branches: List) data class Key(val pkg: String, val file: String) +data class Info(val hits: Int, val branchesMissed: Int, val branchesCovered: Int) + object SourceReportParser { private val logger: Logger by lazy { LogManager.getLogger(CoverallsReporter::class.java) } @@ -31,14 +33,14 @@ object SourceReportParser { } } - private fun read(reportPath: String): Map> { + private fun read(reportPath: String): Map> { val reader = SAXReader() reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) val document = reader.read(File(reportPath)) val root = document.rootElement - val fullCoverage = mutableMapOf>() + val fullCoverage = mutableMapOf>() root.elements("package").forEach { pkg -> val pkgName = pkg.attributeValue("name") @@ -52,9 +54,13 @@ object SourceReportParser { sf.elements("line").forEach { line -> val lineIndex = line.attributeValue("nr").toInt() - 1 + val branchesMissed = line.attributeValue("mb").toInt() + val branchesCovered = line.attributeValue("cb").toInt() // jacoco doesn't count hits - fullCoverage.getValue(key)[lineIndex] = if (line.attributeValue("ci").toInt() > 0) 1 else 0 + val hits = if (line.attributeValue("ci").toInt() > 0) 1 else 0 + + fullCoverage.getValue(key)[lineIndex] = Info(hits, branchesMissed, branchesCovered) } } } @@ -79,28 +85,36 @@ object SourceReportParser { project.extensions.findByType(BaseAppModuleExtension::class.java)!!.sourceSets .getByName("main").java.srcDirs.filterNotNull() } else // jacocoAggregation plugin - // Gradle 7 + // Gradle 7 project.configurations.findByName("allCodeCoverageReportSourceDirectories") - ?.incoming?.artifactView { view -> view - .componentFilter { it is ProjectComponentIdentifier } - .lenient(true) + ?.incoming?.artifactView { view -> + view + .componentFilter { it is ProjectComponentIdentifier } + .lenient(true) }?.files?.files // Gradle 8+ ?: project.configurations.findByName("aggregateCodeCoverageReportResults") ?.incoming?.artifactView { view -> val objects = project.objects view.withVariantReselection() - view.componentFilter { it is ProjectComponentIdentifier} - view.attributes { attributes -> attributes - .attribute(Bundling.BUNDLING_ATTRIBUTE, - objects.named(Bundling::class.java, Bundling.EXTERNAL)) - .attribute(Category.CATEGORY_ATTRIBUTE, - objects.named(Category::class.java, Category.VERIFICATION)) - .attribute(VerificationType.VERIFICATION_TYPE_ATTRIBUTE, - objects.named(VerificationType::class.java, VerificationType.MAIN_SOURCES)) + view.componentFilter { it is ProjectComponentIdentifier } + view.attributes { attributes -> + attributes + .attribute( + Bundling.BUNDLING_ATTRIBUTE, + objects.named(Bundling::class.java, Bundling.EXTERNAL) + ) + .attribute( + Category.CATEGORY_ATTRIBUTE, + objects.named(Category::class.java, Category.VERIFICATION) + ) + .attribute( + VerificationType.VERIFICATION_TYPE_ATTRIBUTE, + objects.named(VerificationType::class.java, VerificationType.MAIN_SOURCES) + ) } }?.files?.files - // main source set + // main source set ?: project.extensions.getByType(SourceSetContainer::class.java) .getByName("main").allJava.srcDirs.filterNotNull() } else { @@ -121,17 +135,26 @@ object SourceReportParser { val lines = f.readLines() val lineHits = arrayOfNulls(lines.size) - - cov.forEach { (line, hits) -> - if (line >=0 && line < lines.size) { - lineHits[line] = hits + val branches = mutableListOf() + + cov.forEach { (line, info) -> + if (line >= 0 && line < lines.size) { + lineHits[line] = info.hits + + val totalBranches = info.branchesCovered + info.branchesMissed + if (totalBranches > 0) { + for (branchNumber in 1..totalBranches) { + val covered = if (branchNumber <= info.branchesCovered) 1 else 0 + branches.addAll(listOf(line + 1, 0, branchNumber, covered)) + } + } } else { logger.debug("skipping invalid line $line, (total ${lines.size})") } } val relPath = File(project.rootDir.absolutePath).toURI().relativize(f.toURI()).toString() - SourceReport(relPath, f.md5(), lineHits.toList()) + SourceReport(relPath, f.md5(), lineHits.toList(), branches.toList()) }.also { it ?: logger.info("${key.file} could not be found in any of the source directories, skipping") diff --git a/src/test/kotlin/CoverallsReporterTest.kt b/src/test/kotlin/CoverallsReporterTest.kt index d04d970..a933186 100644 --- a/src/test/kotlin/CoverallsReporterTest.kt +++ b/src/test/kotlin/CoverallsReporterTest.kt @@ -106,7 +106,7 @@ internal class CoverallsReporterTest { val reporter = spyk(CoverallsReporter(envGetter), recordPrivateCalls = true) reporter.report(project) - assertEquals(546, coverallsRequest.length()) + assertEquals(577, coverallsRequest.length()) } @Test @@ -175,7 +175,7 @@ internal class CoverallsReporterTest { Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: binary -{"repo_token":"test-token","service_name":"github","git":{"head":{"id":"4cd72eadcc34861139b338dd859344d419244e0b","author_name":"John Doe","author_email":"test@example.com","committer_name":"John Doe","committer_email":"test@example.com","message":"test commit"},"branch":"master","remotes":[{"name":"origin","url":"git@github.com:test/testrepo.git"}]},"source_files":[{"name":"javaStyleSrc/main/kotlin/foo/bar/baz/Main.kt","source_digest":"36083cd4c2ac736f9210fd3ed23504b5","coverage":[null,null,null,null,1,1,1,1,null,1,1,0,0,1,1,null,1,1,1]}]} +{"repo_token":"test-token","service_name":"github","git":{"head":{"id":"4cd72eadcc34861139b338dd859344d419244e0b","author_name":"John Doe","author_email":"test@example.com","committer_name":"John Doe","committer_email":"test@example.com","message":"test commit"},"branch":"master","remotes":[{"name":"origin","url":"git@github.com:test/testrepo.git"}]},"source_files":[{"name":"javaStyleSrc/main/kotlin/foo/bar/baz/Main.kt","source_digest":"36083cd4c2ac736f9210fd3ed23504b5","coverage":[null,null,null,null,1,1,1,1,null,1,1,0,0,1,1,null,1,1,1],"branches":[10,0,1,1,10,0,2,0]}]} """.trimIndent() assertEquals(expected, actual) @@ -216,7 +216,7 @@ Content-Transfer-Encoding: binary Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: binary -{"repo_token":"test-token","service_name":"other","git":{"head":{"id":"4cd72eadcc34861139b338dd859344d419244e0b","author_name":"John Doe","author_email":"test@example.com","committer_name":"John Doe","committer_email":"test@example.com","message":"test commit"},"branch":"master","remotes":[{"name":"origin","url":"git@github.com:test/testrepo.git"}]},"source_files":[{"name":"javaStyleSrc/main/kotlin/foo/bar/baz/Main.kt","source_digest":"36083cd4c2ac736f9210fd3ed23504b5","coverage":[null,null,null,null,1,1,1,1,null,1,1,0,0,1,1,null,1,1,1]}]} +{"repo_token":"test-token","service_name":"other","git":{"head":{"id":"4cd72eadcc34861139b338dd859344d419244e0b","author_name":"John Doe","author_email":"test@example.com","committer_name":"John Doe","committer_email":"test@example.com","message":"test commit"},"branch":"master","remotes":[{"name":"origin","url":"git@github.com:test/testrepo.git"}]},"source_files":[{"name":"javaStyleSrc/main/kotlin/foo/bar/baz/Main.kt","source_digest":"36083cd4c2ac736f9210fd3ed23504b5","coverage":[null,null,null,null,1,1,1,1,null,1,1,0,0,1,1,null,1,1,1],"branches":[10,0,1,1,10,0,2,0]}]} """.trimIndent() assertEquals(expected, actual) diff --git a/src/test/kotlin/DataClassTest.kt b/src/test/kotlin/DataClassTest.kt index c41205e..ea7b206 100644 --- a/src/test/kotlin/DataClassTest.kt +++ b/src/test/kotlin/DataClassTest.kt @@ -51,10 +51,12 @@ internal class DataClassTest { @Test fun `data class SourceReport`() { val cov = arrayListOf(1, null) - val srcReport = SourceReport("1", "2", cov) + val branches = arrayListOf(1, 0, 0, 1) + val srcReport = SourceReport("1", "2", cov, branches) assertEquals("1", srcReport.name) assertEquals("2", srcReport.source_digest) assertEquals(cov, srcReport.coverage) + assertEquals(branches, srcReport.branches) } @Test diff --git a/src/test/kotlin/SourceReportParserTest.kt b/src/test/kotlin/SourceReportParserTest.kt index a75e450..174c711 100644 --- a/src/test/kotlin/SourceReportParserTest.kt +++ b/src/test/kotlin/SourceReportParserTest.kt @@ -63,7 +63,8 @@ internal class SourceReportParserTest { SourceReport( "javaStyleSrc/main/kotlin/foo/bar/baz/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ) ) assertEquals(expected, actual) @@ -88,12 +89,14 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -118,12 +121,14 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -148,12 +153,14 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -178,17 +185,20 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ), SourceReport( "src/anotherMain/kotlin/Lib.kt", "8b5c1c773cf81996efc19a08f0ac3648", - listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -211,8 +221,10 @@ internal class SourceReportParserTest { }) mockk { every { files } returns mockk { - every { files } returns setOf( testKotlinStyleSourceDir, - testKotlinStyleSourceDirAdditional) + every { files } returns setOf( + testKotlinStyleSourceDir, + testKotlinStyleSourceDirAdditional + ) } } } @@ -229,17 +241,20 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ), SourceReport( "src/anotherMain/kotlin/Lib.kt", "8b5c1c773cf81996efc19a08f0ac3648", - listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -265,7 +280,7 @@ internal class SourceReportParserTest { every { artifactView(capture(artifactViewConfigAction)) } answers { - artifactViewConfigAction.captured.execute(mockk ArtifactViewConfig@ { + artifactViewConfigAction.captured.execute(mockk ArtifactViewConfig@{ every { withVariantReselection() } returns this every { componentFilter(any()) } returns this val attributeContainerAction = slot>() @@ -284,8 +299,10 @@ internal class SourceReportParserTest { }) mockk { every { files } returns mockk { - every { files } returns setOf( testKotlinStyleSourceDir, - testKotlinStyleSourceDirAdditional) + every { files } returns setOf( + testKotlinStyleSourceDir, + testKotlinStyleSourceDirAdditional + ) } } } @@ -302,17 +319,20 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ), SourceReport( "src/anotherMain/kotlin/Lib.kt", "8b5c1c773cf81996efc19a08f0ac3648", - listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null), + emptyList() ) ) assertEquals(expected, actual) @@ -337,17 +357,20 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0) ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList() ), SourceReport( "src/anotherMain/kotlin/Lib.kt", "8b5c1c773cf81996efc19a08f0ac3648", - listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null, null, null, null, null), + emptyList() ) ) assertEquals(expected, actual) diff --git a/src/test/resources/testreports/jacocoTestReport.xml b/src/test/resources/testreports/jacocoTestReport.xml index 0091db8..237cfe5 100644 --- a/src/test/resources/testreports/jacocoTestReport.xml +++ b/src/test/resources/testreports/jacocoTestReport.xml @@ -8,7 +8,7 @@ - + diff --git a/src/test/resources/testreports/jacocoTestReportMissingLines.xml b/src/test/resources/testreports/jacocoTestReportMissingLines.xml index 4a9b4d4..e8a9d34 100644 --- a/src/test/resources/testreports/jacocoTestReportMissingLines.xml +++ b/src/test/resources/testreports/jacocoTestReportMissingLines.xml @@ -8,7 +8,7 @@ - + diff --git a/src/testAndroid/kotlin/SourceReportParserTest.kt b/src/testAndroid/kotlin/SourceReportParserTest.kt index ddc7496..0adcfab 100644 --- a/src/testAndroid/kotlin/SourceReportParserTest.kt +++ b/src/testAndroid/kotlin/SourceReportParserTest.kt @@ -33,12 +33,14 @@ internal class SourceReportParserTest { SourceReport( "src/main/kotlin/Main.kt", "36083cd4c2ac736f9210fd3ed23504b5", - listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1) + listOf(null, null, null, null, 1, 1, 1, 1, null, 1, 1, 0, 0, 1, 1, null, 1, 1, 1), + listOf(10, 0, 1, 1, 10, 0, 2, 0), ), SourceReport( "src/main/kotlin/internal/Util.kt", "805ee340f4d661be591b4eb42f6164d2", - listOf(null, null, null, null, 1, 1, 1, null, null) + listOf(null, null, null, null, 1, 1, 1, null, null), + emptyList(), ) ) assertEquals(expected, actual)