Skip to content

Commit

Permalink
Isolate scoverage modules from their parent modules (com-lihaoyi#3118)
Browse files Browse the repository at this point in the history
## Context

I encountered an issue with the way scoverage plugin alters the dependency tree of the test module by changing its module dependency from the `outer` module to the `outer.scoverage` module.
This shadow modification of the dependency tree produces a side effect that impacts the IDEA configuration generation.
When the IDEA configuration generation resolves the dependencies instead of using the outer module as a dependency for the test project it depends on the `outer.scoverage` module which should not be generated as it does not exist factually but more virtually.

## Solution

Instead of modifying the dependency tree at compile-time I change it at runtime by removing the `outer.localRunClasspath()` from the `test.runClasspath()` and appending, instead, the `outer.scoverage.localRunClasspath()`

This resolves the issue of modifying the dependency tree but introduces one more compilation as when the `outer.localRunClasspath()` is resolved it will force the compilation of the outer module. In the previous version only the `outer.scoverage` was compiled and not both of them.

Pull request: com-lihaoyi#3118
  • Loading branch information
romain-gilles-ultra committed Apr 12, 2024
1 parent a46018a commit 43c192a
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 19 deletions.
34 changes: 15 additions & 19 deletions contrib/scoverage/src/mill/contrib/scoverage/ScoverageModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package mill.contrib.scoverage
import coursier.Repository
import mill._
import mill.api.{Loose, PathRef, Result}
import mill.main.BuildInfo
import mill.contrib.scoverage.api.ScoverageReportWorkerApi.ReportType
import mill.main.BuildInfo
import mill.scalalib.api.ZincWorkerUtil
import mill.scalalib.{Dep, DepSyntax, JavaModule, ScalaModule}
import mill.util.Util.millProjectModule
Expand Down Expand Up @@ -188,6 +188,7 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
PathRef(T.dest)
}

override def compileResources: T[Seq[PathRef]] = outer.compileResources
override def generatedSources: Target[Seq[PathRef]] = T { outer.generatedSources() }
override def allSources: Target[Seq[PathRef]] = T { outer.allSources() }
override def moduleDeps: Seq[JavaModule] = outer.moduleDeps
Expand Down Expand Up @@ -225,30 +226,25 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
def xmlCoberturaReport(): Command[Unit] = T.command { doReport(ReportType.XmlCobertura) }
def consoleReport(): Command[Unit] = T.command { doReport(ReportType.Console) }

override def skipIdea = outer.skipIdea
override def skipIdea = true
}

trait ScoverageTests extends ScalaTests {
override def upstreamAssemblyClasspath = T {
super.upstreamAssemblyClasspath() ++
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}
override def compileClasspath = T {
super.compileClasspath() ++
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}
override def runClasspath = T {
super.runClasspath() ++

/**
* Alter classfiles and resources from upstream modules and dependencies
* by removing the ones from outer.localRunClasspath() and replacing them
* with outer.scoverage.localRunClasspath()
*/
override def runClasspath: T[Seq[PathRef]] = T {
val outerLocalRunClasspath = outer.localRunClasspath().toSet
super.runClasspath().filterNot(
outerLocalRunClasspath
) ++
outer.scoverage.localRunClasspath() ++
resolveDeps(T.task {
outer.scoverageRuntimeDeps().map(bindDependency())
})()
}

// Need the sources compiled with scoverage instrumentation to run.
override def moduleDeps: Seq[JavaModule] = Seq(outer.scoverage)
}
}
60 changes: 60 additions & 0 deletions example/misc/7-contrib-scoverage/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import mill._, scalalib._
import $ivy.`com.lihaoyi::mill-contrib-scoverage:`

import mill.contrib.scoverage._

object foo extends RootModule with ScoverageModule {
def scoverageVersion = "2.1.0"
def scalaVersion = "2.13.11"
def ivyDeps = Agg(
ivy"com.lihaoyi::scalatags:0.12.0",
ivy"com.lihaoyi::mainargs:0.6.2"
)

object test extends ScoverageTests /*with TestModule.Utest */{
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.7.11")
def testFramework = "utest.runner.Framework"
}
}

// This is a basic Mill build for a single `ScalaModule`, enhanced with
// Scoverage plugin. The root module extends the `ScoverageModule` and
// specifies the version of scoverage version to use here: `2.1.0`. This
// version can be changed if there is a newer one. Now you can call the
// scoverage targets to produce coverage reports.
// The sub test module extends `ScoverageTests` to transform the
// execution of the various testXXX targets to use scoverage and produce
// coverage data.
// This lets us perform the coverage operations but before that you
// must first run the test.
// `./mill test` then `./mill scoverage.consoleReport` and get your
// coverage into your console output.
//
// You can download this example project using the *download* link above
// if you want to try out the commands below yourself. The only requirement is
// that you have some version of the JVM installed; the `./mill` script takes
// care of any further dependencies that need to be downloaded.

/** Usage
> ./mill test # Run the tests and produce the coverage data
...
+ foo.FooTests.simple ... <h1>hello</h1>
+ foo.FooTests.escaping ... <h1>&lt;hello&gt;</h1>
> ./mill resolve scoverage._ # List what tasks are available to run from scoverage
...
scoverage.consoleReport
...
scoverage.htmlReport
...
scoverage.xmlCoberturaReport
...
scoverage.xmlReport
...
> ./mill scoverage.consoleReport
...
Statement coverage.: 16.67%
Branch coverage....: 100.00%
*/
16 changes: 16 additions & 0 deletions example/misc/7-contrib-scoverage/src/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package foo
import scalatags.Text.all._
import mainargs.{main, ParserForMethods}

object Foo {
def generateHtml(text: String) = {
h1(text).toString
}

@main
def main(text: String) = {
println(generateHtml(text))
}

def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}
16 changes: 16 additions & 0 deletions example/misc/7-contrib-scoverage/test/src/FooTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package foo
import utest._
object FooTests extends TestSuite {
def tests = Tests {
test("simple") {
val result = Foo.generateHtml("hello")
assert(result == "<h1>hello</h1>")
result
}
test("escaping") {
val result = Foo.generateHtml("<hello>")
assert(result == "<h1>&lt;hello&gt;</h1>")
result
}
}
}

0 comments on commit 43c192a

Please sign in to comment.