Integrating scala code coverage tool jacoco into a play 2.2.x project - scala

My goal is to integrate jacoco into my play 2.2.0 project.
Different guides on the web I tried to follow mostly added to confusion not to closing in on the goal.
Adding to confusion
Most guides assume the existance of an build.sbt
which as it seems as been superseded by an build.scala with a different
There is a jacoco4sbt and a regular jacoco
which one is most appropiate for use with scala play framework 2
Current state
in plugins.sbt added
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
in build.scala added
import de.johoop.jacoco4sbt.JacocoPlugin._
lazy val jacoco_settings = Defaults.defaultSettings ++ Seq(jacoco.settings: _*)
With these changes i don't get an "jacoco" task in sbt nor in the play console.
What are the appropriate steps to get this working?
Update
As requested the content of the build.scala
import com.typesafe.sbt.SbtNativePackager._
import com.typesafe.sbt.SbtScalariform._
import play.Project._
import sbt.Keys._
import sbt._
import sbtbuildinfo.Plugin._
import de.johoop.jacoco4sbt.JacocoPlugin._
object BuildSettings {
val buildOrganization = "XXXXX"
val buildVersion = "0.1"
val buildScalaVersion = "2.10.2"
val envConfig = "-Dsbt.log.format=false -Dconfig.file=" + Option(System.getProperty("env.config")).getOrElse("local.application")
scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked", "-feature")
javaOptions += envConfig
val buildSettings = Defaults.defaultSettings ++ Seq (
organization := buildOrganization,
version := buildVersion,
scalaVersion := buildScalaVersion
)
}
object Resolvers {
val remoteRepoUrl = "XXXXXXXXXXXX" at "http://nexus.cXXXXX/content/repositories/snapshots/"
val publishRepoUrl = "XXXXXXXXXXXX" at "http://nexus.ciXXXXXXXXXXXXXXXX/content/repositories/snapshots/"
}
object Dependencies {
val ods = "XXXXXXXXX" % "XXXXXX-ws" % "2.2.1-SNAPSHOT"
val scalatest = "org.scalatest" %% "scalatest" % "2.0.M8" % "test"
val mockito = "org.mockito" % "mockito-all" % "1.9.5" % "test"
}
object ApplicationBuild extends Build {
import BuildSettings._
import Dependencies._
import Resolvers._
// Sub-project specific dependencies
val commonDeps = Seq(
ods,
scalatest,
mockito
)
//val bN = settingKey[Int]("current build Number")
val gitHeadCommitSha = settingKey[String]("current git commit SHA")
val release = settingKey[Boolean]("Release")
lazy val jacoco_settings = Defaults.defaultSettings ++ Seq(jacoco.settings: _*)
lazy val nemo = play.Project(
"nemo",
path = file("."),
settings = Defaults.defaultSettings ++ buildSettings ++
Seq(libraryDependencies ++= commonDeps) ++
Seq(scalariformSettings: _*) ++
Seq(playScalaSettings: _*) ++
buildInfoSettings ++
jacoco_settings ++
Seq(
sourceGenerators in Compile <+= buildInfo,
buildInfoKeys ++= Seq[BuildInfoKey](
resolvers,
libraryDependencies in Test,
buildInfoBuildNumber,
BuildInfoKey.map(name) { case (k, v) => "project" + k.capitalize -> v.capitalize },
"envConfig" -> envConfig, // computed at project load time
BuildInfoKey.action("buildTime") {
System.currentTimeMillis
} // re-computed each time at compile
),
buildInfoPackage := "com.springer.nemo"
) ++
Seq(resolvers += remoteRepoUrl) ++
Seq(mappings in Universal ++= Seq(
file("ops/rpm/start-server.sh") -> "start-server.sh",
file("ops/rpm/stop-server.sh") -> "stop-server.sh"
))
).settings(version <<= version in ThisBuild)
lazy val nemoPackaging = Project(
"nemoPackaging",
file("nemoPackaging"),
settings = Defaults.defaultSettings ++Seq(Packaging.settings:_*)
)
def publishSettings =
Seq(
publishTo := Option(publishRepoUrl),
credentials += Credentials(
"Repo", "http://mycompany.com/repo", "admin", "admin123"))
}
Note: jacoco is running with this but does not pick up our tests. Output:
jacoco:cover
[info] Compiling 1 Scala source to /home/schl14/work/nemo/target/scala-2.10/classes...
[info] ScalaTest
[info] Run completed in 13 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for nemo/jacoco:test

I solved it by doing this.
Add the following to plugins.sbt
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
In build.scala i added a new import
import de.johoop.jacoco4sbt.JacocoPlugin._
and added jacoco to the config section like this
lazy val xyz = play.Project(
"xyz",
path = file("."),
settings = Defaults.defaultSettings
jacoco.settings ++ //this is the important part.
).settings(parallelExecution in jacoco.Config := false) //not mandatory but needed in `most cases as most test can not be run in parallel`
After these steps jacoco:cover was available in the sbt and play console and also discovers our tests.

A better option for Scala code coverage is Scoverage which gives statement line coverage.
https://github.com/scoverage/scalac-scoverage-plugin
Add to project/plugins.sbt:
addSbtPlugin("com.sksamuel.scoverage" % "sbt-scoverage" % "1.0.1")
Then run SBT with
sbt clean coverage test

To measure the code coverage of Java code jacoco4sbt is the best fit.
Add to project/plugins.sbt:
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
Add at the end of build.sbt:
jacoco.settings
Then run in the terminal:
//or just the sbt command and then use your browser
sbt jacoco:cover && /usr/bin/x-www-browser target/scala-2.10/jacoco/html/index.html
Scala code coverage can also be determined by SCCT.
Add to project/plugins.sbt:
addSbtPlugin("com.github.scct" % "sbt-scct" % "0.2.1")
Add at the end of build.sbt:
ScctPlugin.instrumentSettings
And then to see the coverage:
sbt scct:test && /usr/bin/x-www-browser target/scala_2.10/coverage-report/index.html
Maybe you get the error
Please either restart the browser with --allow-file-access-from-files
or use a different browser.
Maybe you use chrome and the security settings forbid dynamic actions on local files. You can open the page with firefox or use python -m SimpleHTTPServer 8000 to bind it to the http protokoll and open http://localhost:8000/target/scala-2.10/coverage-report/ .
Inspiration which generated classes should be excluded from the report can you find on the mailing list.
My test projects are on GitHub.

Related

Pre-test SBT task: Unable to instantiate JDBC driver

I'm having trouble getting an SBT task to run migrations with Flyway; I get an exception when I run the task. Any ideas how I could fix it?
org.flywaydb.core.api.FlywayException: Unable to instantiate JDBC driver: org.postgresql.Driver => Check whether the jar file is present
The following code works, when I run it in BeforeAll, in my tests (ScalaTest), but does not work when I move it into an SBT task.
val flyway = Flyway
.configure()
.locations("filesystem:./**/resources/db/migrations/")
.dataSource("jdbc:postgresql://localhost:5432/my_database", "my_user", "secret")
.load()
flyway.clean()
flyway.migrate()
My /build.sbt file looks like this:
import org.flywaydb.core.Flyway
lazy val migrate = taskKey[Unit]("Migrate database")
lazy val migrateTask = Def.task {
println("Migrate")
val flyway = Flyway
.configure()
.locations("filesystem:./**/resources/db/migrations/")
.dataSource("jdbc:postgresql://localhost:5432/my_database", "my_user", "secret")
.load()
flyway.clean()
flyway.migrate()
}
val IntegrationTest = config("integration") extend Test
lazy val integrationTestSettings = inConfig(IntegrationTest)(Defaults.testSettings) ++ List(
IntegrationTest / fork := false,
IntegrationTest / parallelExecution := false,
IntegrationTest / sourceDirectory := baseDirectory.value / "src/test/integration",
IntegrationTest / test := {
(IntegrationTest / test) dependsOn migrateTask
}.value
)
lazy val root = Project(id = "hello", base = file("."))
.configs(Configs.all: _*)
.settings(
integrationTestSettings,
libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.4",
)
And my /project/build.sbt looks like this:
libraryDependencies ++= List(
"org.flywaydb" % "flyway-core" % "7.6.0",
"org.postgresql" % "postgresql" % "42.2.19",
)
The versions I'm using are:
SBT: 1.4.5
Scala: 2.13.4
Flyway: 7.6.0
Does anyone have any ideas why I'm getting that error, and how I can fix it?
Any help would be greatly appreciated. Thanks :)
Searching on the Flyway repo, the error message is coming from here - https://github.com/flyway/flyway/blob/9033185ab8bfa56b0dae9136c04763cdccc50081/flyway-core/src/main/java/org/flywaydb/core/internal/jdbc/DriverDataSource.java#L165-L182 where it's trying load the database driver from the classloader. These ClassLoader techniques sometimes clash with the sbt set up layered ClassLoader to run sbt itself. That's my speculation on what's happening.
How do we work around this?
You said that running it as part the test worked, so maybe you could create a subproject for this purpose?
ThisBuild / scalaVersion := "2.13.4"
lazy val migrate = taskKey[Unit]("Migrate database")
lazy val root = (project in file("."))
.settings(
name := "hello",
migrate := (migrateProj / run).toTask("").value
)
// utility project to run database migration
lazy val migrateProj = (project in file("migrate"))
.settings(
libraryDependencies ++= List(
"org.flywaydb" % "flyway-core" % "7.6.0",
"org.postgresql" % "postgresql" % "42.2.19",
),
Compile / run / fork := true,
publish / skip := true,
)
migrate/Migrate.scala
object Migrate extends App {
println("migrate")
// rest of the code here...
}
Now you can run
sbt:flyway> migrate
[info] running (fork) Migrate
[info] migrate
[success] Total time: 4 s, completed Mar 6, 2021 9:03:07 PM
Details about layered ClassLoader
ClassLoader techniques sometimes clash with the sbt set up layered ClassLoader to run sbt itself. sbt-the-Bash-script allows users to choose the sbt version using project/build.properties, and Scala version using build.sbt. Both of these make sbt build declarative and repeatable, and generally a good thing. But how can sbt launcher written using Scala 2.10 launch sbt 1.4.x written using Scala 2.12, which then launch your Scala 2.13 application? Each of these boundary cross is done by creating a layered ClassLoader, like the movie Inception.

sbt cross project, shared dependencies for test example

I have a small project.
Where I have the following problem:
scalaTest needs to be added to all three dependency project (client, server, shared), otherwise the scalatest library is not accessible from all projects.
In other words, if I write
val jvmDependencies = Def.setting(Seq(
"org.scalaz" %% "scalaz-core" % "7.2.8"
)++scalaTest)
then things work fine.
But if I don't write ++scalaTest into each three dependencies then it fails like this:
> test
[info] Compiling 1 Scala source to /Users/joco/tmp3/server/target/scala-2.11/test-classes...
[error] /Users/joco/tmp3/server/src/test/scala/Test.scala:1: object specs2 is not a member of package org
[error] import org.specs2.mutable.Specification
[error] ^
[error] /Users/joco/tmp3/server/src/test/scala/Test.scala:3: not found: type Specification
[error] class Test extends Specification {
[error] ^
[error] /Users/joco/tmp3/server/src/test/scala/Test.scala:5: value should is not a member of String
[error] "Test" should {
[error] ^
[error] /Users/joco/tmp3/server/src/test/scala/Test.scala:6: value in is not a member of String
[error] "one is one" in {
[error] ^
[error] /Users/joco/tmp3/server/src/test/scala/Test.scala:8: value === is not a member of Int
[error] 1===one
[error] ^
[error] 5 errors found
[error] (server/test:compileIncremental) Compilation failed
[error] Total time: 4 s, completed Mar 18, 2017 1:56:54 PM
However for production(not test) code everything works just fine: I don't have to add 3 times the same dependencies (in this example autowire) to all three projects if I want to use a library in all three projects, it is enough to add it to only the shared project and then I can use that library from all three projects.
For test code, however, as I mentioned above, currently I have to add the same library dependency (scalaTest - below) to all three projects.
Question: Is there a way to avoid this ?
Settings.scala:
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import sbt.Keys._
import sbt._
object Settings {
val scalacOptions = Seq(
"-Xlint",
"-unchecked",
"-deprecation",
"-feature",
"-Yrangepos"
)
object versions {
val scala = "2.11.8"
}
val scalaTest=Seq(
"org.scalatest" %% "scalatest" % "3.0.1" % "test",
"org.specs2" %% "specs2" % "3.7" % "test")
val sharedDependencies = Def.setting(Seq(
"com.lihaoyi" %%% "autowire" % "0.2.6"
)++scalaTest)
val jvmDependencies = Def.setting(Seq(
"org.scalaz" %% "scalaz-core" % "7.2.8"
))
/** Dependencies only used by the JS project (note the use of %%% instead of %%) */
val scalajsDependencies = Def.setting(Seq(
"org.scala-js" %%% "scalajs-dom" % "0.9.1"
)++scalaTest)
}
build.sbt:
import sbt.Keys._
import sbt.Project.projectToRef
import webscalajs.SourceMappings
lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared")) .settings(
scalaVersion := Settings.versions.scala,
libraryDependencies ++= Settings.sharedDependencies.value,
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
) .jsConfigure(_ enablePlugins ScalaJSWeb)
lazy val sharedJVM = shared.jvm.settings(name := "sharedJVM")
lazy val sharedJS = shared.js.settings(name := "sharedJS")
lazy val elideOptions = settingKey[Seq[String]]("Set limit for elidable functions")
lazy val client: Project = (project in file("client"))
.settings(
scalaVersion := Settings.versions.scala,
scalacOptions ++= Settings.scalacOptions,
libraryDependencies ++= Settings.scalajsDependencies.value,
testFrameworks += new TestFramework("utest.runner.Framework")
)
.enablePlugins(ScalaJSPlugin)
.disablePlugins(RevolverPlugin)
.dependsOn(sharedJS)
lazy val clients = Seq(client)
lazy val server = (project in file("server")) .settings(
scalaVersion := Settings.versions.scala,
scalacOptions ++= Settings.scalacOptions,
libraryDependencies ++= Settings.jvmDependencies.value
)
.enablePlugins(SbtLess,SbtWeb)
.aggregate(clients.map(projectToRef): _*)
.dependsOn(sharedJVM)
onLoad in Global := (Command.process("project server", _: State)) compose (onLoad in Global).value
fork in run := true
cancelable in Global := true
For test code, however, as I mentioned above, currently I have to add the same library dependency (scalaTest - below) to all three projects.
That is expected. test dependencies are not inherited along dependency chains. That makes sense, because you don't want to depend on JUnit just because you depend on a library that happens to be tested using JUnit.
Although yes, that calls for a bit of duplication when you have several projects in the same build, all using the same testing framework. This is why we often find some commonSettings that are added to all projects of an sbt build. This is also where we typically put things like organization, scalaVersion, and many other settings that usually apply to all projects inside one build.

Adding module dependency information in sbt's build.sbt file

I have a multi module project in IntelliJ, as in this screen capture shows, contexProcessor module depends on contextSummary module.
IntelliJ takes care of everything once I setup the dependencies in Project Structure.
However, when I run sbt test with the following setup in build.sbt, I got an error complaining that it can't find the packages in contextSummary module.
name := "contextProcessor"
version := "1.0"
scalaVersion := "2.11.7"
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
How to teach sbt that the missing modules are found?
I could use the build.sbt file in the main root directory.
lazy val root = (project in file(".")).aggregate(contextSummary, contextProcessor)
lazy val contextSummary = project
lazy val contextProcessor = project.dependsOn(contextSummary)
Reference: http://www.scala-sbt.org/0.13.5/docs/Getting-Started/Multi-Project.html
For testing only one project, I can use project command in sbt.
> sbt
[info] Set current project to root (in build file:/Users/smcho/Desktop/code/ContextSharingSimulation/)
> project contextProcessor
[info] Set current project to contextProcessor (in build file:/Users/smcho/Desktop/code/ContextSharingSimulation/)
> test
For batch mode as in How to pass command line args to program in SBT 0.13.1?
sbt "project contextProcessor" test
I think a simple build.sbt might not be enough for that.
You would need to create a more sophisticated project/Build.scala like that:
import sbt._
import sbt.Keys._
object Build extends Build {
lazy val root = Project(
id = "root",
base = file("."),
aggregate = Seq(module1, module2)
)
lazy val module1 = Project(
id = "module1",
base = file("module1-folder"),
settings = Seq(
name := "Module 1",
version := "1.0",
scalaVersion := "2.11.7",
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
lazy val module2 = Project(
id = "module2",
base = file("module2-folder"),
dependencies = Seq(module1),
settings = Seq(
name := "Module 2",
version := "1.0",
scalaVersion := "2.11.7",
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
}

Compile with different settings in different commands

I have a project defined as follows:
lazy val tests = Project(
id = "tests",
base = file("tests")
) settings(
commands += testScalalib
) settings (
sharedSettings ++ useShowRawPluginSettings ++ usePluginSettings: _*
) settings (
libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _),
libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-compiler" % _),
libraryDependencies += "org.tukaani" % "xz" % "1.5",
scalacOptions ++= Seq()
)
I would like to have three different commands which will compile only some files inside this project. The testScalalib command added above for instance is supposed to compile only some specific files.
My best attempt so far is:
lazy val testScalalib: Command = Command.command("testScalalib") { state =>
val extracted = Project extract state
import extracted._
val newState = append(Seq(
(sources in Compile) <<= (sources in Compile).map(_ filter(f => !f.getAbsolutePath.contains("scalalibrary/") && f.name != "Typers.scala"))),
state)
runTask(compile in Compile, newState)
state
}
Unfortunately when I use the command, it still compiles the whole project, not just the specified files...
Do you have any idea how I should do that?
I think your best bet would be to create different configurations like compile and test, and have the appropriate settings values that would suit your needs. Read Scopes in the official sbt documentation and/or How to define another compilation scope in SBT?
I would not create additional commands, I would create an extra configuration, as #JacekLaskowski suggested, and based on the answer he had cited.
This is how you can do it (using Sbt 0.13.2) and Build.scala (you could of course do the same in build.sbt, and older Sbt version with different syntax)
import sbt._
import Keys._
object MyBuild extends Build {
lazy val Api = config("api")
val root = Project(id="root", base = file(".")).configs(Api).settings(custom: _*)
lazy val custom: Seq[Setting[_]] = inConfig(Api)(Defaults.configSettings ++ Seq(
unmanagedSourceDirectories := (unmanagedSourceDirectories in Compile).value,
classDirectory := (classDirectory in Compile).value,
dependencyClasspath := (dependencyClasspath in Compile).value,
unmanagedSources := {
unmanagedSources.value.filter(f => !f.getAbsolutePath.contains("scalalibrary/") && f.name != "Typers.scala")
}
))
}
now when you call compile everything will get compiled, but when you call api:compile only the classes matching the filter predicate.
Btw. You may want to also look into the possibility of defining different unmanagedSourceDirectories and/or defining includeFilter.

How to get list of dependency jars from an sbt 0.10.0 project

I have a sbt 0.10.0 project that declares a few dependencies somewhat like:
object MyBuild extends Build {
val commonDeps = Seq("commons-httpclient" % "commons-httpclient" % "3.1",
"commons-lang" % "commons-lang" % "2.6")
val buildSettings = Defaults.defaultSettings ++ Seq ( organization := "org" )
lazy val proj = Project("proj", file("src"),
settings = buildSettings ++ Seq(
name := "projname",
libraryDependencies := commonDeps, ...)
...
}
I wish to creat a build rule to gather all the jar dependencies of "proj", so that I can symlink them to a single directory.
Thanks.
Example SBT task to print full runtime classpath
Below is roughly what I'm using. The "get-jars" task is executable from the SBT prompt.
import sbt._
import Keys._
object MyBuild extends Build {
// ...
val getJars = TaskKey[Unit]("get-jars")
val getJarsTask = getJars <<= (target, fullClasspath in Runtime) map { (target, cp) =>
println("Target path is: "+target)
println("Full classpath is: "+cp.map(_.data).mkString(":"))
}
lazy val project = Project (
"project",
file ("."),
settings = Defaults.defaultSettings ++ Seq(getJarsTask)
)
}
Other resources
Unofficial guide to sbt 0.10.
Keys.scala defines predefined keys. For example, you might want to replace fullClasspath with managedClasspath.
This plugin defines a simple command to generate an .ensime file, and may be a useful reference.