Skip to content

Commit

Permalink
Add initial coveralls plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
stringbean committed Oct 26, 2017
1 parent 753d965 commit a752950
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 18 deletions.
20 changes: 14 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
name := "sbt-jacoco"
organization := "com.github.sbt"

version in ThisBuild := "3.0.3"
version in ThisBuild := "3.1.0-M1"

sbtPlugin := true
crossSbtVersions := Seq("0.13.16", "1.0.2")

val jacocoVersion = "0.7.9"
val circeVersion = "0.8.0"

libraryDependencies ++= Seq(
"org.jacoco" % "org.jacoco.core" % jacocoVersion,
"org.jacoco" % "org.jacoco.report" % jacocoVersion,
"com.jsuereth" %% "scala-arm" % "2.0",
"org.scalatest" %% "scalatest" % "3.0.4" % Test,
"org.mockito" % "mockito-all" % "1.10.19" % Test
"org.jacoco" % "org.jacoco.core" % jacocoVersion,
"org.jacoco" % "org.jacoco.report" % jacocoVersion,
"com.jsuereth" %% "scala-arm" % "2.0",
"com.fasterxml.jackson.core" % "jackson-core" % "2.9.2",
"org.scalaj" %% "scalaj-http" % "2.3.0",
"commons-codec" % "commons-codec" % "1.11",
"org.scalatest" %% "scalatest" % "3.0.4" % Test,
"org.mockito" % "mockito-all" % "1.10.19" % Test
)

scalacOptions ++= Seq(
Expand Down Expand Up @@ -51,3 +55,7 @@ headerLicense := Some(HeaderLicense.Custom(
enablePlugins(ParadoxSitePlugin, GhpagesPlugin)
paradoxNavigationDepth in Paradox := 3
git.remoteRepo := "[email protected]:sbt/sbt-jacoco.git"

addCompilerPlugin(
"org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full
)
4 changes: 0 additions & 4 deletions src/main/scala/com/github/sbt/jacoco/BaseJacocoPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,6 @@ private[jacoco] abstract class BaseJacocoPlugin extends AutoPlugin with JacocoKe
private def toClassName(entry: String): String =
entry.stripSuffix(".class").replace('/', '.')

private def projectData(project: ResolvedProject): ProjectData = {
ProjectData(project.id)
}

protected lazy val submoduleSettingsTask: Def.Initialize[Task[(Seq[File], Option[File], Option[File])]] = Def.task {
(classesToCover.value, (sourceDirectory in Compile).?.value, (jacocoDataFile in srcConfig).?.value)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.sbt.jacoco.coveralls

import java.io.{File, FileInputStream}

import sbt.Keys.TaskStreams

import scalaj.http.{Http, MultiPart}

object CoverallsClient {
private val jobsUrl = "https://coveralls.io/api/v1/jobs"

def sendReport(reportFile: File, streams: TaskStreams): Unit = {
val response = Http(jobsUrl)
.postMulti(
MultiPart(
"json_file",
"json_file.json",
"application/json",
new FileInputStream(reportFile),
reportFile.length(),
_ => ())
).asString

if (response.isSuccess) {
streams.log.info("Upload complete")
} else {
streams.log.error(s"Unexpected response from coveralls: ${response.code}")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.sbt.jacoco.coveralls

import java.io.File

import com.github.sbt.jacoco.report.formats.JacocoReportFormat
import org.jacoco.report.IReportVisitor
import sbt._

class CoverallsReportFormat(sourceDirs: Seq[File], projectRootDir: File, jobId: String, repoToken: Option[String])
extends JacocoReportFormat {

override def createVisitor(directory: File, encoding: String): IReportVisitor = {
IO.createDirectory(directory)

new CoverallsReportVisitor(directory / "coveralls.json", sourceDirs, projectRootDir, jobId, repoToken)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.github.sbt.jacoco.coveralls

import java.io.File
import java.{util => ju}

import com.fasterxml.jackson.core.{JsonEncoding, JsonFactory}
import org.apache.commons.codec.digest.DigestUtils
import org.jacoco.core.analysis.{IBundleCoverage, ILine, IPackageCoverage, ISourceFileCoverage}
import org.jacoco.core.data.{ExecutionData, SessionInfo}
import org.jacoco.report.{IReportGroupVisitor, IReportVisitor, ISourceFileLocator}
import sbt._

import scala.collection.JavaConverters._

class CoverallsReportVisitor(
output: File,
sourceDirs: Seq[File],
projectRootDir: File,
jobId: String,
repoToken: Option[String])
extends IReportVisitor
with IReportGroupVisitor {

private val digest = new DigestUtils("MD5")

private val jsonFactory = new JsonFactory()
private val json = jsonFactory.createGenerator(output, JsonEncoding.UTF8)

json.writeStartObject()

repoToken foreach { token =>
json.writeStringField("repo_token", token)
}

json.writeStringField("service_job_id", jobId)
json.writeStringField("service_name", "travis-ci")

json.writeArrayFieldStart("source_files")

override def visitInfo(sessionInfos: ju.List[SessionInfo], executionData: ju.Collection[ExecutionData]): Unit = {}

override def visitGroup(name: String): IReportGroupVisitor = this

override def visitBundle(bundle: IBundleCoverage, locator: ISourceFileLocator): Unit = {
bundle.getPackages.asScala foreach { pkg: IPackageCoverage =>
pkg.getSourceFiles.asScala foreach { source: ISourceFileCoverage =>
json.writeStartObject()

//noinspection ScalaStyle
val (filename, md5) = findFile(pkg.getName, source.getName) match {
case Some(file) =>
(IO.relativize(projectRootDir, file).getOrElse(file.getName), digest.digestAsHex(file))

case None =>
(source.getName, "")
}

json.writeStringField("name", filename)
json.writeStringField("source_digest", md5)

json.writeArrayFieldStart("coverage")

(0 to source.getLastLine) foreach { l =>
val line: ILine = source.getLine(l)

if (line.getInstructionCounter.getTotalCount == 0) {
// non-code line
json.writeNull()
} else {
json.writeNumber(line.getInstructionCounter.getCoveredCount)
}
}

json.writeEndArray()

json.writeEndObject()
}
}
}

override def visitEnd(): Unit = {
json.writeEndArray()
json.writeEndObject()
json.close()
}

private def findFile(packageName: String, fileName: String): Option[File] = {
// TODO make common with source file locator
sourceDirs.map(d => d / packageName / fileName).find(_.exists())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.github.sbt.jacoco.coveralls

import java.io.File

import com.github.sbt.jacoco.{JacocoPlugin, _}
import com.github.sbt.jacoco.report.ReportUtils
import sbt.Keys._
import sbt._

object JacocoCoverallsPlugin extends BaseJacocoPlugin {
override def requires: Plugins = JacocoPlugin
override def trigger: PluginTrigger = noTrigger

override protected def srcConfig = Test

object autoImport {
val jacocoCoveralls: TaskKey[Unit] = taskKey("Upload JaCoCo reports to Coveralls")

val jacocoCoverallsJobId: SettingKey[String] = settingKey("todo")
val jacocoCoverallsGenerateReport: TaskKey[Unit] = taskKey("TODO")
val jacocoCoverallsOutput: SettingKey[File] = settingKey("File to store Coveralls coverage")

val jacocoCoverallsRepoToken: SettingKey[Option[String]] = settingKey("todo")
}

import autoImport._ // scalastyle:ignore import.grouping

override def projectSettings: Seq[Setting[_]] = Seq(
jacocoCoverallsOutput := jacocoReportDirectory.value,
jacocoCoveralls := Def.task {
CoverallsClient.sendReport(jacocoCoverallsOutput.value / "coveralls.json", streams.value)
}.value,
jacocoCoverallsGenerateReport := Def.task {
val coverallsFormat =
new CoverallsReportFormat(
coveredSources.value,
baseDirectory.value,
jacocoCoverallsJobId.value,
jacocoCoverallsRepoToken.value)

ReportUtils.generateReport(
jacocoCoverallsOutput.value,
jacocoDataFile.value,
jacocoReportSettings.value.withFormats(coverallsFormat),
coveredSources.value,
classesToCover.value,
jacocoSourceSettings.value,
streams.value,
checkCoverage = false
)
}.value,
jacocoCoveralls := (jacocoCoveralls dependsOn jacocoCoverallsGenerateReport).value,
// TODO fail if no job id
// TODO manual job id
jacocoCoverallsJobId := sys.env.getOrElse("TRAVIS_JOB_ID", "unknown"),
jacocoCoverallsRepoToken := None
)
}
10 changes: 10 additions & 0 deletions src/main/scala/com/github/sbt/jacoco/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.sbt

import com.github.sbt.jacoco.data.ProjectData
import sbt.ResolvedProject

package object jacoco {
private[jacoco] def projectData(project: ResolvedProject): ProjectData = {
ProjectData(project.id)
}
}
5 changes: 3 additions & 2 deletions src/main/scala/com/github/sbt/jacoco/report/Report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class Report(
sourceSettings: JacocoSourceSettings,
reportSettings: JacocoReportSettings,
reportDirectory: File,
streams: TaskStreams) {
streams: TaskStreams,
checkCoverage: Boolean) {

private val percentageFormat = new DecimalFormat("#.##")

Expand All @@ -39,7 +40,7 @@ class Report(

reportSettings.formats.foreach(createReport(_, bundleCoverage, executionDataStore, sessionInfoStore))

if (!checkCoverage(bundleCoverage)) {
if (checkCoverage && !checkCoverage(bundleCoverage)) {
streams.log error "Required coverage is not met"
// is there a better way to fail build?
sys.exit(1)
Expand Down
12 changes: 8 additions & 4 deletions src/main/scala/com/github/sbt/jacoco/report/ReportUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ object ReportUtils {
sourceDirectories: Seq[File],
classDirectories: Seq[File],
sourceSettings: JacocoSourceSettings,
streams: TaskStreams): Unit = {
streams: TaskStreams,
checkCoverage: Boolean = true): Unit = {

val report = new Report(
reportDirectory = destinationDirectory,
Expand All @@ -32,7 +33,8 @@ object ReportUtils {
sourceDirectories = sourceDirectories,
sourceSettings = sourceSettings,
reportSettings = reportSettings,
streams = streams
streams = streams,
checkCoverage = checkCoverage
)

report.generate()
Expand All @@ -45,7 +47,8 @@ object ReportUtils {
sourceDirectories: Seq[File],
classDirectories: Seq[File],
sourceSettings: JacocoSourceSettings,
streams: TaskStreams): Unit = {
streams: TaskStreams,
checkCoverage: Boolean = true): Unit = {

val report = new Report(
reportDirectory = destinationDirectory,
Expand All @@ -54,7 +57,8 @@ object ReportUtils {
sourceDirectories = sourceDirectories,
sourceSettings = sourceSettings,
reportSettings = reportSettings,
streams = streams
streams = streams,
checkCoverage = checkCoverage
)

report.generate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class TestCounters {
reportDirectory = new File("."),
executionDataFiles = Nil,
classDirectories = Nil,
sourceDirectories = Nil
sourceDirectories = Nil,
checkCoverage = true
)

when[Logger](mockStreams.log).thenReturn(mockLog)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class TestCounters {
reportDirectory = new File("."),
executionDataFiles = Nil,
classDirectories = Nil,
sourceDirectories = Nil
sourceDirectories = Nil,
checkCoverage = true
)

when[ManagedLogger](mockStreams.log).thenReturn(mockLog)
Expand Down

0 comments on commit a752950

Please sign in to comment.