Skip to content

Commit

Permalink
Merge pull request #7 from nbaztec/autodetect-root-package
Browse files Browse the repository at this point in the history
autodetect root package
  • Loading branch information
nbaztec authored Aug 18, 2020
2 parents 77585b1 + c868941 commit 3396e8a
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 59 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ A jacoco test coverage reporter gradle plugin for [coveralls.io](https://coveral
The plugin supports non-root packages in line with the recommended [Kotlin directory structure](https://kotlinlang.org/docs/reference/coding-conventions.html#directory-structure)
which was missing in many other plugins for the Kotlin ecosystem.

The plugin automatically detects the root package, if it conforms to Kotlin guidelines and has a `.kt` file on the root level.

## Usage

[Gradle Plugin page](https://plugins.gradle.org/plugin/com.github.nbaztec.coveralls-jacoco)
Expand All @@ -32,7 +34,6 @@ Set the value of `COVERALLS_REPO_TOKEN` from the project page on coveralls.io

coverallsJacoco {
reportPath = "" // default: "build/reports/jacoco/test/jacocoTestReport.xml"
rootPackage = "com.github.nbaztec.foo" // optional, leave out if project has a normal java styled directory structure
reportSourceSets = [ sourceSets.foo, sourceSets.bar ] // optional, default: main
apiEndpoint = "" // optional, default: https://coveralls.io/api/v1/jobs
}
Expand All @@ -50,6 +51,44 @@ jacocoTestReport {
}
```

## Multi-Project Support
To consolidate multiple JaCoCo coverage reports, the following code can be used to add a new task `codeCoverageReport`
```kotlin
tasks.register<JacocoReport>("codeCoverageReport") {
// If a subproject applies the 'jacoco' plugin, add the result it to the report
subprojects {
val subproject = this
subproject.plugins.withType<JacocoPlugin>().configureEach {
subproject.tasks.matching({ it.extensions.findByType<JacocoTaskExtension>() != null }).configureEach {
val testTask = this
sourceSets(subproject.sourceSets.main.get())
executionData(testTask)
}

// To automatically run `test` every time `./gradlew codeCoverageReport` is called,
// you may want to set up a task dependency between them as shown below.
// Note that this requires the `test` tasks to be resolved eagerly (see `forEach`) which
// may have a negative effect on the configuration time of your build.
subproject.tasks.matching({ it.extensions.findByType<JacocoTaskExtension>() != null }).forEach {
rootProject.tasks["codeCoverageReport"].dependsOn(it)
}
}
}


// enable the different report types (html, xml, csv)
reports {
// xml is usually used to integrate code coverage with
// other tools like SonarQube, Coveralls or Codecov
xml.isEnabled = true

// HTML reports can be used to see code coverage
// without any external tools
html.isEnabled = true
}
}
```

## CI Usage
The plugin can be used with the following CI providers:

Expand Down
4 changes: 1 addition & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

group = "com.github.nbaztec"
version = "1.0.5"
version = "1.1.0"

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
Expand Down Expand Up @@ -68,10 +68,8 @@ publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "coveralls-jacoco"

from(components["java"])
}

}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/CoverallsJacocoPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ class CoverallsJacocoPlugin : Plugin<Project> {
}
}
}
}
}
19 changes: 9 additions & 10 deletions src/main/kotlin/CoverallsReporter.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.gradle.plugin.coveralls.jacoco


import com.google.gson.Gson
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.methods.HttpPost
Expand All @@ -12,14 +11,13 @@ import org.apache.log4j.LogManager
import org.apache.log4j.Logger
import org.gradle.api.Project


data class Request(
val repo_token: String,
val service_name: String,
val service_job_id: String?,
val service_pull_request: String?,
val git: GitInfo?,
val source_files: List<SourceReport>
val repo_token: String,
val service_name: String,
val service_job_id: String?,
val service_pull_request: String?,
val git: GitInfo?,
val source_files: List<SourceReport>
)

private fun Request.json() = Gson().toJson(this)
Expand All @@ -31,6 +29,8 @@ class CoverallsReporter(val envGetter: EnvGetter) {
private val defaultHttpTimeoutMs = 10 * 1000

fun report(project: Project) {
val pluginExtension = project.extensions.getByType(CoverallsJacocoPluginExtension::class.java)

logger.info("retrieving git info")
val gitInfo = GitInfoParser.parse(project.projectDir)

Expand All @@ -56,7 +56,6 @@ class CoverallsReporter(val envGetter: EnvGetter) {
gitInfo,
sourceFiles
)
val pluginExtension = project.extensions.getByType(CoverallsJacocoPluginExtension::class.java)

send(pluginExtension.apiEndpoint, req)
}
Expand Down Expand Up @@ -90,4 +89,4 @@ class CoverallsReporter(val envGetter: EnvGetter) {
}
logger.info("OK")
}
}
}
47 changes: 47 additions & 0 deletions src/main/kotlin/FileFinder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.gradle.plugin.coveralls.jacoco

import java.io.File
import org.apache.log4j.LogManager
import org.apache.log4j.Logger

class FileFinder(private val dirs: Iterable<File>) {
private val logger: Logger by lazy { LogManager.getLogger(FileFinder::class.java) }
private val packageRegex = """package\s+(\S+)""".toRegex()

private val rootPackages = dirs.associateWith { dir ->
dir.listFiles()
?.firstOrNull { it.extension == "kt" }
?.let { f ->
f.bufferedReader().useLines { seq ->
seq.firstOrNull { packageRegex.matches(it) }
?.let {
packageRegex.find(it)!!.groupValues[1].replace(".", "/")
}
}
}
}.also {
it.forEach { (dir, pkg) ->
pkg?.let {
logger.info("mapped root package ${pkg.replace("/", ".")} for directory '$dir'")
}
}
}

fun find(file: File): File? {
dirs.any { dir ->
val adjustedFile = rootPackages[dir]?.let {
File(file.path.replace(Regex("^$it/"), ""))
} ?: file

val f = File(dir, adjustedFile.toString())
f.exists().also { exists ->
if (exists) {
return f
}
}
}

logger.info("could not find file '$file'")
return null
}
}
24 changes: 12 additions & 12 deletions src/main/kotlin/GitInfoParser.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package org.gradle.plugin.coveralls.jacoco

import java.io.File
import org.apache.log4j.LogManager
import org.apache.log4j.Logger
import org.eclipse.jgit.lib.RepositoryBuilder
import org.eclipse.jgit.revwalk.RevWalk
import java.io.File

data class Head(
val id: String,
val author_name: String,
val author_email: String,
val committer_name: String,
val committer_email: String,
val message: String
val id: String,
val author_name: String,
val author_email: String,
val committer_name: String,
val committer_email: String,
val message: String
)

data class Remote(
val name: String,
val url: String
val name: String,
val url: String
)

data class GitInfo(
val head: Head,
val branch: String,
val remotes: Collection<Remote>
val head: Head,
val branch: String,
val remotes: Collection<Remote>
)

object GitInfoParser {
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/ServiceInfoParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,3 @@ class ServiceInfoParser(val envGetter: EnvGetter) {
}
}
}

35 changes: 16 additions & 19 deletions src/main/kotlin/SourceReportParser.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
package org.gradle.plugin.coveralls.jacoco

import java.io.File
import java.math.BigInteger
import java.security.MessageDigest
import org.apache.log4j.LogManager
import org.apache.log4j.Logger
import org.dom4j.io.SAXReader
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSetContainer
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<Int?>)

data class Key(val pkg: String, val file: String)

object SourceReportParser {
private val logger: Logger by lazy { LogManager.getLogger(CoverallsReporter::class.java) }

private fun read(reportPath: String, rootPackage: String?): Map<String, Map<Int, Int>> {
private fun read(reportPath: String): Map<Key, Map<Int, Int>> {
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 rootPackagePath = rootPackage?.replace(".", "/")

val fullCoverage = mutableMapOf<String, MutableMap<Int, Int>>()
val fullCoverage = mutableMapOf<Key, MutableMap<Int, Int>>()
root.elements("package").forEach { pkg ->
val pkgName = pkg.attributeValue("name")
val path = rootPackagePath?.let {
pkgName.replaceFirst("^$it".toRegex(), "")
} ?: pkgName

pkg.elements("sourcefile").forEach { sf ->
val sfName = sf.attributeValue("name")
val key = "$path/$sfName"
val key = Key(pkgName, sfName)

if (fullCoverage[key] == null) {
fullCoverage[key] = mutableMapOf()
Expand Down Expand Up @@ -70,13 +67,12 @@ object SourceReportParser {
return emptyList()
}

val fileFinder = FileFinder(sourceDirs)

logger.info("using source directories: $sourceDirs")
return read(pluginExtension.reportPath, pluginExtension.rootPackage)
.mapNotNull { (filename, cov) ->
sourceDirs.find {
File(it, filename).exists()
}?.let { dir ->
val f = File(dir, filename)
return read(pluginExtension.reportPath)
.mapNotNull { (key, cov) ->
fileFinder.find(File(key.pkg, key.file))?.let { f ->
logger.debug("found file: $f")

val lines = f.readLines()
Expand All @@ -87,8 +83,9 @@ object SourceReportParser {
val relPath = File(project.projectDir.absolutePath).toURI().relativize(f.toURI()).toString()
SourceReport(relPath, f.md5(), lineHits.toList())
}.also {
it ?: logger.info("$filename could not be found in any of the source directories, skipping")
it
?: logger.info("${key.file} could not be found in any of the source directories, skipping")
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/test/kotlin/CoverallsJacocoPluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.gradle.plugin.coveralls.jacoco
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import java.io.File
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
Expand All @@ -11,7 +12,6 @@ import org.gradle.api.tasks.SourceSetContainer
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.io.File

internal class CoverallsJacocoPluginTest {
@Test
Expand Down
6 changes: 3 additions & 3 deletions src/test/kotlin/CoverallsReporterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verifyAll
import java.io.File
import java.io.PrintWriter
import java.net.InetSocketAddress
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
Expand All @@ -18,9 +21,6 @@ import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import java.io.File
import java.io.PrintWriter
import java.net.InetSocketAddress

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class CoverallsReporterTest {
Expand Down
10 changes: 9 additions & 1 deletion src/test/kotlin/DataClassTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ internal class DataClassTest {
assertEquals(cov, srcReport.coverage)
}

@Test
fun `data class Key`() {
val cov = arrayListOf(1, null)
val key = Key("1", "2")
assertEquals("1", key.pkg)
assertEquals("2", key.file)
}

@Test
fun `data class Request`() {
val gitInfo = mockk<GitInfo>()
Expand All @@ -65,4 +73,4 @@ internal class DataClassTest {
assertEquals(gitInfo, req.git)
assertEquals(sourceFiles, req.source_files)
}
}
}
Loading

0 comments on commit 3396e8a

Please sign in to comment.