Skip to content

Commit

Permalink
Improve test module detection on Mill 0.11.7+ (#74)
Browse files Browse the repository at this point in the history
This prepared mill-jacoco to better catch exactly the correct module
sources. It especially detects any custom test module and excludes it's
coverage data from the reports. Perviously, we only detected test
modules by naming conventions, which was rather sloppy.

Fix #3

This feature only works when used with Mill 0.11.7 or newer. This is a
soft requirement and will checked at runtime.

Pull request: #74
  • Loading branch information
lefou authored Feb 6, 2024
1 parent dfc6c1b commit a028742
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.7.13"
version = "3.7.15"

align.preset = none
align.openParenCallSite = false
Expand Down
23 changes: 13 additions & 10 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ trait Deps {
object Deps_0_11 extends Deps {
override def millPlatform = "0.11" // only valid for exact milestone versions
override def millVersion = "0.11.0" // scala-steward:off
override def testWithMill = Seq(millVersion)
override def testWithMill = Seq("0.11.7", "0.11.6", millVersion)
}
object Deps_0_10 extends Deps {
override def millPlatform = "0.10"
override def millVersion = "0.10.0" // scala-steward:off
override def testWithMill = Seq(millVersion, "0.10.11")
override def testWithMill = Seq("0.10.13", millVersion)
}
object Deps_0_9 extends Deps {
override def millPlatform = "0.9"
override def millVersion = "0.9.7" // scala-steward:off
override def testWithMill = Seq(millVersion, "0.9.12")
override def testWithMill = Seq("0.9.12", millVersion)
}

val crossDeps = Seq(Deps_0_11, Deps_0_10, Deps_0_9)
Expand Down Expand Up @@ -77,8 +77,6 @@ trait BaseModule extends ScalaModule with PublishModule with ScoverageModule wit

override def scoverageVersion = deps.scoverageVersion

trait Tests extends ScoverageTests

}

object core extends Cross[CoreCross](millApiVersions.map(_._1))
Expand All @@ -101,10 +99,6 @@ trait CoreCross extends BaseModule {
super.sources() ++
versions.map(v => PathRef(millSourcePath / s"src-${v}"))
}

// object test extends Tests with TestModule.ScalaTest {
// override def ivyDeps = Agg(deps.scalaTest)
// }
}

object itest extends Cross[ItestCross](millItestVersions.map(_._1))
Expand All @@ -114,6 +108,15 @@ trait ItestCross extends MillIntegrationTestModule with Cross.Module[String] {
override def millTestVersion = crossValue
override def pluginsUnderTest = Seq(core(millApiVersion))

override def sources = T.sources {
val versions =
ZincWorkerUtil.matchingVersions(millApiVersion) ++
ZincWorkerUtil.versionRanges(millApiVersion, millApiVersions.map(_._1))

super.sources() ++
versions.map(v => PathRef(millSourcePath / s"src-${v}"))
}

/** Replaces the plugin jar with a scoverage-enhanced version of it. */
override def pluginUnderTestDetails: Task[Seq[(PathRef, (PathRef, (PathRef, (PathRef, (PathRef, Artifact)))))]] =
Target.traverse(pluginsUnderTest) { p =>
Expand All @@ -129,7 +132,7 @@ trait ItestCross extends MillIntegrationTestModule with Cross.Module[String] {
pr -> Seq(
TestInvocation.Targets(Seq("-d", "__.test")),
TestInvocation.Targets(Seq("-d", "de.tobiasroeser.mill.jacoco.Jacoco/jacocoReportFull")),
TestInvocation.Targets(Seq("-d", "verify"))
TestInvocation.Targets(Seq("-d", "verify", millApiVersion))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,20 @@ trait JacocoReportModulePlatform extends CoursierModule {
jars.iterator.next()
}

protected[jacoco] def resolveTasks[T](tasks: String, evaluator: Evaluator): Seq[Task[T]] = RunScript.resolveTasks(
mill.main.ResolveTasks,
evaluator,
Seq(tasks),
multiSelect = true
) match {
case Left(err) => throw new Exception(err)
case Right(tasks) => tasks.asInstanceOf[Seq[Task[T]]]
protected[jacoco] def resolveTasks[T](tasks: String, evaluator: Evaluator): Seq[Task[T]] =
if (tasks.trim().isEmpty()) Seq()
else RunScript.resolveTasks(
mill.main.ResolveTasks,
evaluator,
Seq(tasks),
multiSelect = true
) match {
case Left(err) => throw new Exception(err)
case Right(tasks) => tasks.asInstanceOf[Seq[Task[T]]]
}

protected[jacoco] val (sourcesSelector, compileSelector, excludeSourcesSelector, excludeCompiledSelector) = {
("__.allSources", "__.compile", "__.test.allSources", "__.test.compile")
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package de.tobiasroeser.mill.jacoco

import mill.{Agg, T}
import mill.api.PathRef
import mill.define.{Input, Task}
import mill.define.Task
import mill.eval.Evaluator
import mill.main.RunScript
import mill.resolve.{Resolve, SelectMode}
import mill.scalalib.{CoursierModule, DepSyntax}
import mill.util.Version

trait JacocoReportModulePlatform extends CoursierModule {

/** The Jacoco Version. */
def jacocoVersion: Input[String]
def jacocoVersion: T[String]

/** The Jacoco Classpath contains the tools used to generate reports from collected coverage data. */
def jacocoClasspath: T[Agg[PathRef]] = T {
Expand All @@ -31,11 +31,23 @@ trait JacocoReportModulePlatform extends CoursierModule {
}

protected[jacoco] def resolveTasks[T](tasks: String, evaluator: Evaluator): Seq[Task[T]] = {
Resolve.Tasks.resolve(evaluator.rootModule, Seq(tasks), SelectMode.Multi) match {
if (tasks.trim().isEmpty()) Seq()
else Resolve.Tasks.resolve(evaluator.rootModule, Seq(tasks), SelectMode.Multi) match {
case Left(err) => throw new Exception(err)
case Right(tasks) => tasks.asInstanceOf[Seq[Task[T]]]
}
}

protected[jacoco] val (sourcesSelector, compileSelector, excludeSourcesSelector, excludeCompiledSelector) = {
// since version 0.11.7 Mill supports type selectors in target queries
// https://github.com/com-lihaoyi/mill/pull/2997
if (
Version.parse(mill.api.BuildInfo.millVersion).isAtLeast(Version.parse("0.11.7"))(
mill.util.Version.IgnoreQualifierOrdering
)
) ("__:JavaModule:^TestModule.allSources", "__:JavaModule:^TestModule.compile", "", "")
else
("__.allSources", "__.compile", "__.test.allSources", "__.test.compile")
}

}
8 changes: 4 additions & 4 deletions core/src/de/tobiasroeser/mill/jacoco/JacocoReportModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ trait JacocoReportModule extends JacocoReportModulePlatform { jacocoReportModule
def jacocoReportFull(evaluator: mill.eval.Evaluator): Command[PathRef] = T.command {
jacocoReportTask(
evaluator = evaluator,
sources = "__.allSources",
compiled = "__.compile",
excludeSources = "__.test.allSources",
excludeCompiled = "__.test.compile"
sources = sourcesSelector,
compiled = compileSelector,
excludeSources = excludeSourcesSelector,
excludeCompiled = excludeCompiledSelector
)()
}

Expand Down
35 changes: 35 additions & 0 deletions itest/src-0.11+/custom-test/build.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// mill plugins under test
import $file.shared

import de.tobiasroeser.mill.jacoco._

import mill._
import mill.scalalib._
import mill.define.Command

object main extends JavaModule {
// no tests in here
object test extends JavaModuleTests with JacocoTestModule with TestModule.Junit4
// but here
object customTest extends JavaModuleTests with JacocoTestModule with TestModule.Junit4 {
override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg(
ivy"com.novocode:junit-interface:0.11",
ivy"junit:junit:4.12"
)
}
}

def verify(millVersion: String): Command[Unit] = T.command {
val jacocoPath = os.pwd / "out" / "de" / "tobiasroeser" / "mill" / "jacoco" / "Jacoco" / "jacocoReportFull.dest"
val xml = jacocoPath / "jacoco.xml"
assert(os.exists(jacocoPath))
assert(os.exists(xml))
val contents = os.read(xml)
assert(contents.contains("""<class name="test/Main" sourcefilename="Main.java">"""))
assert(contents.contains("""<sourcefile name="Main.java">"""))
if (millVersion.startsWith("0.11.") && millVersion.drop(5).takeWhile(_.isDigit).toInt >= 7) {
// this file should be excluded from report, as it belongs to the test sources
assert(!contents.contains("""<class name="test/MainTest" sourcefilename="MainTest.java">"""))
}
()
}
10 changes: 10 additions & 0 deletions itest/src-0.11+/custom-test/main/customTest/src/test/MainTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test;

import org.junit.Test;

public class MainTest {
@Test
public void testMain() {
Main.main(new String[]{});
}
}
7 changes: 7 additions & 0 deletions itest/src-0.11+/custom-test/main/src/test/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test;

class Main {
public static void main(String[] args) {
System.out.println("Hello Main!");
}
}
15 changes: 13 additions & 2 deletions itest/src/simple-java/build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ object main extends JavaModule {
}
}

def verify(): Command[Unit] = T.command {
assert(os.exists(os.pwd / "out" / "de" / "tobiasroeser" / "mill"))
def verify(millVersion: String): Command[Unit] = T.command {
val destDir =
if (millVersion.startsWith("0.9")) "jacocoReportFull" :: "dest" :: Nil
else "jacocoReportFull.dest" :: Nil
val jacocoPath = os.pwd / "out" / "de" / "tobiasroeser" / "mill" / "jacoco" / "Jacoco" / destDir

val xml = jacocoPath / "jacoco.xml"
assert(os.exists(jacocoPath))
assert(os.exists(xml))
val contents = os.read(xml)
assert(contents.contains("""<class name="test/Main" sourcefilename="Main.java">"""))
assert(contents.contains("""<sourcefile name="Main.java">"""))
()
}
10 changes: 10 additions & 0 deletions itest/src/simple-java/main/test/src/test/MainTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test;

import org.junit.Test;

public class MainTest {
@Test
public void testMain() {
Main.main(new String[]{});
}
}

0 comments on commit a028742

Please sign in to comment.