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?
Create a jar file using 'sbt assembly' command. If you run 'sbt assembly' command before adding plugin it shows errors. 2. Distribute all the JAR files necessary with a script that builds the classpath and executes the JAR file with the scala commands.
Typically, if a key has no associated value in a more-specific scope, sbt will try to get a value from a more general scope, such as the ThisBuild scope. This feature allows you to set a value once in a more general scope, allowing multiple more-specific scopes to inherit the value.
All new SBT versions (after 0.7. x ) by default put the downloaded JARS into the . ivy2 directory in your home directory. If you are using Linux, this is usually /home/<username>/.
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.
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:
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 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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With