Create assembly jar that contains all tests in SBT project+subprojects - scala

I have an interesting problem where I basically need to create a .jar (plus all of the classpath dependencies) that contains all of the tests of an SBT project (plus any of its subprojects). The idea is that I can just run the jar using java -jar and all of the tests will execute.
I heard that this is possible to do with sbt-assembly but you would have to manually run assembly for each sbt sub-project that you have (each with their own .jars) where as ideally I would just want to run one command that generates a giant .jar for every test in every sbt root+sub project that you happen to have (in the same way if you run test in an sbt project with sub projects it will run tests for everything).
The current testing framework that we are using is specs2 although I am not sure if this makes a difference.
Does anyone know if this is possible?

Exporting test runner is not supported
sbt 1.3.x does not have this feature. Defined tests are executed in tandem with the runner provided by test frameworks (like Specs2) and sbt's build that also reflectively discovers your defined tests (e.g. which class extends Spec2's test traits?). In theory, we already have a good chunk of what you'd need because Test / fork := true creates a program called ForkMain and runs your tests in another JVM. What's missing from that is dispatching of your defined tests.
Using specs2.run runner
Thankfully Specs2 provides a runner out of the box called specs2.run (See In the shell):
scala -cp ... specs2.run com.company.SpecName [argument1 argument2 ...]
So basically all you need to know is:
your classpath
list of fully qualified name for your defined tests
Here's how to get them using sbt:
> print Test/fullClasspath
* Attributed(/private/tmp/specs-runner/target/scala-2.13/test-classes)
* Attributed(/private/tmp/specs-runner/target/scala-2.13/classes)
* Attributed(/Users/eed3si9n/.coursier/cache/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.13/1.2.0/scala-xml_2.13-1.2.0.jar)
...
> print Test/definedTests
* Test foo.HelloWorldSpec : subclass(false, org.specs2.specification.core.SpecificationStructure)
We can exercise specs2.run runner from sbt shell as follows:
> Test/runMain specs2.run foo.HelloWorldSpec
Aggregating across subprojects
Aggregating tests across subprojects requires some thinking. Instead of creating a giant ball of assembly, I would recommend the following. Create a dummy subproject testAgg, and then collect all the Test/externalDependencyClasspath and Test/packageBin into its target/dist. You can then grab all the JAR and run java -jar ... as you wanted.
How would one go about that programmatically? See Getting values from multiple scopes.
lazy val collectJars = taskKey[Seq[File]]("")
lazy val collectDefinedTests = taskKey[Seq[String]]("")
lazy val testFilter = ScopeFilter(inAnyProject, inConfigurations(Test))
lazy val testAgg = (project in file("testAgg"))
.settings(
name := "testAgg",
publish / skip := true,
collectJars := {
val cps = externalDependencyClasspath.all(testFilter).value.flatten.distinct
val pkgs = packageBin.all(testFilter).value
cps.map(_.data) ++ pkgs
},
collectDefinedTests := {
val dts = definedTests.all(testFilter).value.flatten
dts.map(_.name)
},
Test / test := {
val jars = collectJars.value
val tests = collectDefinedTests.value
sys.process.Process(s"""java -cp ${jars.mkString(":")} specs2.run ${tests.mkString(" ")}""").!
}
)
This runs like this:
> testAgg/test
[info] HelloWorldSpec
[info]
[info] The 'Hello world' string should
[info] + contain 11 characters
[info] + start with 'Hello'
[info] + end with 'world'
[info]
[info]
[info] Total for specification HelloWorldSpec
[info] Finished in 124 ms
3 examples, 0 failure, 0 error
[info] testAgg / Test / test 1s
If you really want to you probably could generate source from the collectDefinedTests make testAgg depend on the Test configurations of all subprojects, and try to make a giant ball of assembly, but I'll leave as an exercise to the reader :)

Related

Get full classpath without compilation in SBT

I need a piece of code using SBT, possibly internals, which acquires the full classpath of an SBT project without invoking a compilation. Normally I would use "Runtime / fullClasspath", but that needs the project to be compiled first. Is there any way to get the fullClasspath without triggering a compile? I thought the build.sbt alone determined the classpath, and compilation (in theory) isn't necessary.
I asked on the SBT gitter channel, and there it was also mentioned to use dependencyClasspath. dependencyClasspath doesn't require compilation of the root project, but it does require compilation of all the dependents. So that doesn't solve it yet for me. I'm looking for the complete classpath required to running the root project, without compiling its constituents.
I'm interested in any way to work around this, so if there are any farfetched workarounds, those are welcome too.
An example of what I have now:
Global / printMainClasspath := {
val paths = (rootProject / fullClasspath).value
val joinedPaths = paths
.map(_.data)
.mkString(pathSeparator)
println(joinedPaths)
}
This works partially, it creates a task "printMainClasspath" which prints the full classpath. But if I call it in the sbt shell, it compiles the code first. I'd like the classpath to be printed without invoking a full compile of the project. Ideally, only all build.sbt's of all constituent projects are compiled. Is there a way?
val dryClasspath = taskKey[Seq[File]]("dryClasspath")
dryClasspath := {
val data = settingsData.value
val thisProj = thisProjectRef.value
val allProjects = thisProj +: buildDependencies.value.classpathTransitiveRefs(thisProj)
val classDirs = allProjects.flatMap(p => (p / Runtime / classDirectory).get(data))
val externalJars = (Runtime / externalDependencyClasspath).value.map(_.data)
classDirs ++ externalJars
}

Passing keys to integration test

I have an sbt project for which I am writing integration tests. The integration test deploys the API defined in the project. I need the version of the project from sbt in order to deploy the corresponding version of the API which has been published to a remote repository (x.x.x-SNAPSHOT). After it is deployed, I'll run integration tests against it.
Is there a way to pass Keys from sbt to a unit test class? I'm using Scalatest and sbt 1.2.7
If you run your unit/integration tests in a forked JVM, you can pass the version through a system property to that JVM:
Test / fork := true
Test / javaOptions += s"-Dproject.version=${version.value}"
Change the scope according to how you set up your unit tests (you might need to use a different configuration or even a specific task).
If you don't want to run your tests in a forked JVM, you could use the following setting to set up the system property before running your tests:
Test / testOptions += Tests.Setup(() => sys.props += "project.version" -> version.value)
In either of these cases, you then should access the project.version system property in your tests to get the version number:
val version = sys.props("project.version")
Alternatively, you can generate a file and put it into your generated resources directory, and load the version number from there:
// build.sbt
Test / resourceGenerators += Def.task {
val versionFile = (Test / resourceManaged).value / "version.txt"
IO.write(versionFile, version.value)
Vector(versionFile)
}
// your test
val is = getClass.getClassLoader.getResourceAsStream("version.txt")
val version = try {
scala.io.Source.fromInputStream(is).mkString
} finally {
is.close()
}

Run a single test suite from build.sbt

I have a multi-module project and currently run tests during packaging by a task which reads -
val testALL = taskKey[Unit]("Test ALL Modules")
testALL := {
(test in Test in module_A).value
(test in Test in module_B).value
(test in Test in module_C).value
}
Now, I have consolidated all tests in each module into a single top-level ScalaTest Suite. So for each module want to only run this single top-level suite (named say "blah.moduleA.TestSuite" and so on). Have been trying to use testOnly and testFilter in my build.sbt to run just this single suite in each module but cant get the syntax right. Can someone please tell me how to do this?
testOnly is an InputKey[Unit]. You want to turn it in a Task[Unit] to be able to run it directly for a given test suite.
You can achieve this this way:
lazy val foo = taskKey[Unit]("...")
foo := (testOnly in Test).fullInput("hello").value
In sbt's documentation: Preapplying input in sbt

Disable single test in Play 2.4 scala

I have some long running tests in my project. These these are sitting in parallel to my integration and unit-tests in
/test/manual/*
Is there in Play 2.4 for Scala a way to disable/mark these test classes. So they are not run automaticly when
$ activator test
but only run when using the test-only command.
Problem is that I do not want to run these longer tests on my CI server.
Having similar problems for long-running integration tests, I created an It configuration derived from the standard test config (in <projectHome>/build.sbt):
lazy val It = config("it").extend(Test)
Then I add the sources and test sources to this config
scalaSource in It <<= (scalaSource in Test)
and you needd to enable to config and corresponding tasks available in the current project
lazy val root = (project in file(".")).configs(It)
.settings(inConfig(It)(Defaults.testTasks): _*)
I then disable long running tests in the Test config :
testOptions in Test := Seq(Tests.Argument("exclude", "LongRunning"))
And include only these long running tests in the It config:
testOptions in It := Seq(Tests.Argument("include", "LongRunning"))
These last 2 configs are kinda dependent on the test framework you use (specs2 in my case, scala test would probably use -n and -l in addition to tags to achieve the same)
Then sbt test will exclude all LongRunning tests and you can run it:test or it:testOnly your.long.running.TestCaseHere in an interactive sbt session if need be.

Cannot see jacoco task when executing sbt tasks command (-v is tried as well)

I was trying to use jacoco to integrate test report to my sbt project. https://github.com/sbt/jacoco4sbt
I added jacoco.settings into build.sbt
I also added addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.6") into plugins.sbt
When I run sbt jacoco:check, it is working fine. However, when I try to look at how many tasks for jacoco, sbt tasks doesn't show anything related to jacoco.
I have to go to source code to look at it.
https://github.com/sbt/jacoco4sbt/blob/master/src/main/scala/de/johoop/jacoco4sbt/Keys.scala
May I know why jacoco is not shown for sbt tasks command and what is preferable way to look at all the available tasks for the plugins
Edit:
I suspect that the statement lazy val Config = config("jacoco") extend(Test) hide means jacoco extends Test task, so it wont show it in the sbt tasks, but I am not sure.
By running
> tasks -v
Edit: If that doesn't work consider adding more "v"s, such as tasks -vvv, or even tasks -V to see all the tasks.
I see, for instance, cover:
This is a list of tasks defined for the current project.
It does not list the scopes the tasks are defined in; use the 'inspect' command for that.
Tasks produce values. Use the 'show' command to run the task and print the resulting value.
check Executes the tests and saves the execution data in 'jacoco.exec'.
classesToCover compiled classes (filtered by includes and excludes) that will be covered
clean Cleaning JaCoCo's output-directory.
compile Compiles sources.
console Starts the Scala interpreter with the project classes on the classpath.
consoleProject Starts the Scala interpreter with the sbt and the build definition on the classpath and useful imports.
consoleQuick Starts the Scala interpreter with the project dependencies on the classpath.
copyResources Copies resources to the output directory.
cover Executes the tests and creates a JaCoCo coverage report.
coveredSources Covered Sources.
Note also what it says at the beginning (wrapped for clarity):
It does not list the scopes the tasks are defined in;
use the 'inspect' command for that.
which leads to
> inspect cover
[info] No entry for key.
[info] Description:
[info] Executes the tests and creates a JaCoCo coverage report.
[info] Delegates:
[info] *:cover
[info] {.}/*:cover
[info] */*:cover
[info] Related:
[info] jacoco:cover
So you know to run jacoco:cover