From 2cbc8d403715bc909325c9323ef58ffb47b22e1f Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov <5min4eq.unity@gmail.com> Date: Thu, 22 Feb 2018 11:18:45 +0300 Subject: support multiple test frameworks (#148) support multiple test frameworks --- build.sc | 2 +- docs/pages/2 - Configuring Mill.md | 10 ++-- docs/pages/6 - Extending Mill.md | 2 +- integration/test/resources/acyclic/build.sc | 4 +- integration/test/resources/ammonite/build.sc | 6 +-- integration/test/resources/better-files/build.sc | 2 +- integration/test/resources/jawn/build.sc | 4 +- integration/test/resources/upickle/build.sc | 2 +- main/test/src/mill/define/DiscoverTests.scala | 2 +- main/test/src/mill/util/TestGraphs.scala | 2 +- scalajslib/src/mill/scalajslib/ScalaJSModule.scala | 4 +- .../src/mill/scalajslib/HelloJSWorldTests.scala | 4 +- scalalib/src/mill/scalalib/ScalaModule.scala | 6 +-- scalalib/src/mill/scalalib/ScalaWorkerApi.scala | 2 +- scalalib/src/mill/scalalib/TestRunner.scala | 8 +-- scalaworker/src/mill/scalaworker/ScalaWorker.scala | 60 ++++++++++++---------- 16 files changed, 63 insertions(+), 57 deletions(-) diff --git a/build.sc b/build.sc index 02f3b323..792cf908 100755 --- a/build.sc +++ b/build.sc @@ -55,7 +55,7 @@ trait MillModule extends MillPublishModule{ outer => if (this == main.test) Seq(main) else Seq(outer, main.test) def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0") - def testFramework = "mill.UTestFramework" + def testFrameworks = Seq("mill.UTestFramework") def scalacPluginClasspath = super.scalacPluginClasspath() ++ Seq(moduledefs.jar()) } } diff --git a/docs/pages/2 - Configuring Mill.md b/docs/pages/2 - Configuring Mill.md index d976aa57..04d75f1a 100644 --- a/docs/pages/2 - Configuring Mill.md +++ b/docs/pages/2 - Configuring Mill.md @@ -81,7 +81,7 @@ object foo extends ScalaModule { object test extends Tests{ def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0") - def testFramework = "mill.UTestFramework" + def testFrameworks = Seq("mill.UTestFramework") } } ``` @@ -143,11 +143,11 @@ object foo extends ScalaModule { object test extends Tests{ def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0") - def testFramework = "mill.UTestFramework" + def testFrameworks = Seq("mill.UTestFramework") } object integration extends Tests{ def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0") - def testFramework = "mill.UTestFramework" + def testFrameworks = Seq("mill.UTestFramework") } } ``` @@ -167,12 +167,12 @@ import mill._, scalalib._ object foo extends ScalaModule { def scalaVersion = "2.12.4" def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4") - def testFramework = "org.scalatest.tools.Framework" + def testFrameworks = Seq("org.scalatest.tools.Framework") } ``` Integrating with test frameworks like Scalatest is simply a matter of adding it -to `ivyDeps` and specifying the `testFramework` you want to use. After that you +to `ivyDeps` and specifying the `testFrameworks` you want to use. After that you can [add a test suite](#adding-a-test-suite) and `mill foo.test` as usual, passing args to the test suite via `mill foo.test arg1 arg2 arg3` diff --git a/docs/pages/6 - Extending Mill.md b/docs/pages/6 - Extending Mill.md index e5b655f5..a6c096a6 100644 --- a/docs/pages/6 - Extending Mill.md +++ b/docs/pages/6 - Extending Mill.md @@ -104,7 +104,7 @@ trait FooModule extends ScalaModule{ def scalaVersion = "2.11.11" object test extends Tests{ def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4") - def testFramework = "org.scalatest.tools.Framework" + def testFrameworks = Seq("org.scalatest.tools.Framework") } } ``` diff --git a/integration/test/resources/acyclic/build.sc b/integration/test/resources/acyclic/build.sc index 5328db24..373be20a 100644 --- a/integration/test/resources/acyclic/build.sc +++ b/integration/test/resources/acyclic/build.sc @@ -32,6 +32,6 @@ class AcyclicModule(val crossScalaVersion: String) extends CrossSbtModule with P def ivyDeps = Agg( ivy"com.lihaoyi::utest:0.6.0" ) - def testFramework = "utest.runner.Framework" + def testFrameworks = Seq("utest.runner.Framework") } -} \ No newline at end of file +} diff --git a/integration/test/resources/ammonite/build.sc b/integration/test/resources/ammonite/build.sc index 4594cd9f..39134e3d 100644 --- a/integration/test/resources/ammonite/build.sc +++ b/integration/test/resources/ammonite/build.sc @@ -5,13 +5,13 @@ val fullCrossScalaVersions = Seq( "2.12.0", "2.12.1", "2.12.2", "2.12.3", "2.12.4" ) trait AmmModule extends mill.scalalib.CrossSbtModule{ - def testFramework = "utest.runner.Framework" + def testFrameworks = Seq("utest.runner.Framework") def scalacOptions = Seq("-P:acyclic:force", "-target:jvm-1.7") def compileIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") def scalacPluginIvyDeps = Agg(ivy"com.lihaoyi::acyclic:0.1.7") trait Tests extends super.Tests{ def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.6.0") - def testFramework = "utest.runner.Framework" + def testFrameworks = Seq("utest.runner.Framework") } def allIvyDeps = T{transitiveIvyDeps() ++ scalaLibraryIvyDeps()} def externalSources = T{ @@ -181,4 +181,4 @@ class SshdModule(val crossScalaVersion: String) extends AmmModule{ ivy"org.scalacheck::scalacheck:1.12.6" ) } -} \ No newline at end of file +} diff --git a/integration/test/resources/better-files/build.sc b/integration/test/resources/better-files/build.sc index f9747343..6196fa65 100644 --- a/integration/test/resources/better-files/build.sc +++ b/integration/test/resources/better-files/build.sc @@ -55,7 +55,7 @@ trait BetterFilesModule extends SbtModule{ if (this == core.test) super.moduleDeps else super.moduleDeps ++ Seq(core.test) def ivyDeps = Agg(ivy"org.scalatest::scalatest:3.0.4") - def testFramework = "org.scalatest.tools.Framework" + def testFrameworks = Seq("org.scalatest.tools.Framework") } } diff --git a/integration/test/resources/jawn/build.sc b/integration/test/resources/jawn/build.sc index b83eac17..deb9731a 100644 --- a/integration/test/resources/jawn/build.sc +++ b/integration/test/resources/jawn/build.sc @@ -20,7 +20,7 @@ class JawnModule(crossVersion: String) extends mill.Module{ ivy"org.scalatest::scalatest:3.0.3", ivy"org.scalacheck::scalacheck:1.13.5" ) - def testFramework = "org.scalatest.tools.Framework" + def testFrameworks = Seq("org.scalatest.tools.Framework") } } object parser extends JawnModule @@ -57,4 +57,4 @@ class JawnModule(crossVersion: String) extends mill.Module{ } object spray extends Support(ivy"io.spray::spray-json:1.3.3") } -} \ No newline at end of file +} diff --git a/integration/test/resources/upickle/build.sc b/integration/test/resources/upickle/build.sc index 39742562..007ab9a2 100644 --- a/integration/test/resources/upickle/build.sc +++ b/integration/test/resources/upickle/build.sc @@ -107,7 +107,7 @@ trait UpickleTestModule extends TestModule{ millSourcePath / platformSegment / "src" / "test", millSourcePath / "shared" / "src" / "test" ) - def testFramework = "utest.runner.Framework" + def testFrameworks = Seq("utest.runner.Framework") } object upickleJvm extends Cross[UpickleJvmModule]("2.11.11", "2.12.4") diff --git a/main/test/src/mill/define/DiscoverTests.scala b/main/test/src/mill/define/DiscoverTests.scala index 7621169a..cd9939f0 100644 --- a/main/test/src/mill/define/DiscoverTests.scala +++ b/main/test/src/mill/define/DiscoverTests.scala @@ -18,7 +18,7 @@ object DiscoverTests extends TestSuite{ check(TestGraphs.triangleTask)(_.left, _.right) } 'TraitWithModuleObject - { - check(TestGraphs.TraitWithModuleObject)(_.TraitModule.testFramework) + check(TestGraphs.TraitWithModuleObject)(_.TraitModule.testFrameworks) } 'nestedModule - { check(TestGraphs.nestedModule)(_.single, _.nested.single, _.classInstance.single) diff --git a/main/test/src/mill/util/TestGraphs.scala b/main/test/src/mill/util/TestGraphs.scala index 20f5c9ef..750ef015 100644 --- a/main/test/src/mill/util/TestGraphs.scala +++ b/main/test/src/mill/util/TestGraphs.scala @@ -185,7 +185,7 @@ object TestGraphs{ trait TraitWithModule extends Module{ outer => object TraitModule extends Module{ - def testFramework = T{ "mill.UTestFramework" } + def testFrameworks = T{ Seq("mill.UTestFramework") } def test() = T.command{ ()/*donothing*/ } } } diff --git a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala index a94b287d..ab67cd95 100644 --- a/scalajslib/src/mill/scalajslib/ScalaJSModule.scala +++ b/scalajslib/src/mill/scalajslib/ScalaJSModule.scala @@ -194,14 +194,14 @@ trait TestScalaJSModule extends ScalaJSModule with TestModule { override def test(args: String*) = T.command { val framework = mill.scalajslib.ScalaJSBridge.scalaJSBridge().getFramework( toolsClasspath().map(_.path), - testFramework(), + testFrameworks().head, fastOptTest().path.toIO ) val (doneMsg, results) = mill.scalalib.ScalaWorkerApi .scalaWorker() .runTests( - _ => framework, + _ => Seq(framework), runClasspath().map(_.path), Agg(compile().classes.path), args diff --git a/scalajslib/test/src/mill/scalajslib/HelloJSWorldTests.scala b/scalajslib/test/src/mill/scalajslib/HelloJSWorldTests.scala index f3dd7def..d616d1b0 100644 --- a/scalajslib/test/src/mill/scalajslib/HelloJSWorldTests.scala +++ b/scalajslib/test/src/mill/scalajslib/HelloJSWorldTests.scala @@ -57,7 +57,7 @@ object HelloJSWorldTests extends TestSuite { extends BuildModule(crossScalaVersion, sjsVersion0) { object test extends super.Tests { override def sources = T.sources{ millSourcePath / 'src / 'utest } - def testFramework: T[String] = "utest.runner.Framework" + def testFrameworks = Seq("utest.runner.Framework") override def ivyDeps = Agg( ivy"com.lihaoyi:utest_sjs${scalaJSBinaryVersion()}_${Lib.scalaBinaryVersion(scalaVersion())}:0.6.3" ) @@ -69,7 +69,7 @@ object HelloJSWorldTests extends TestSuite { extends BuildModule(crossScalaVersion, sjsVersion0) { object test extends super.Tests { override def sources = T.sources{ millSourcePath / 'src / 'scalatest } - def testFramework: T[String] = "org.scalatest.tools.Framework" + def testFrameworks = Seq("org.scalatest.tools.Framework") override def ivyDeps = Agg( ivy"org.scalatest:scalatest_sjs${scalaJSBinaryVersion()}_${Lib.scalaBinaryVersion(scalaVersion())}:3.0.4" ) diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala index dd9fa91e..ffaaeac8 100644 --- a/scalalib/src/mill/scalalib/ScalaModule.scala +++ b/scalalib/src/mill/scalalib/ScalaModule.scala @@ -294,7 +294,7 @@ object TestModule{ } trait TestModule extends ScalaModule with TaskModule { override def defaultCommandName() = "test" - def testFramework: T[String] + def testFrameworks: T[Seq[String]] def forkWorkingDir = ammonite.ops.pwd @@ -307,7 +307,7 @@ trait TestModule extends ScalaModule with TaskModule { jvmArgs = forkArgs(), envArgs = forkEnv(), mainArgs = Seq( - testFramework(), + testFrameworks().mkString(" "), runClasspath().map(_.path).mkString(" "), Seq(compile().classes.path).mkString(" "), args.mkString(" "), @@ -326,7 +326,7 @@ trait TestModule extends ScalaModule with TaskModule { val outputPath = T.ctx().dest/"out.json" mill.scalalib.ScalaWorkerApi.scalaWorker().runTests( - TestRunner.framework(testFramework()), + TestRunner.frameworks(testFrameworks()), runClasspath().map(_.path), Agg(compile().classes.path), args diff --git a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala index 4cec0696..964179a9 100644 --- a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala +++ b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala @@ -57,7 +57,7 @@ trait ScalaWorkerApi { upstreamCompileOutput: Seq[CompilationResult]) (implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] - def runTests(frameworkInstance: ClassLoader => sbt.testing.Framework, + def runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], entireClasspath: Agg[Path], testClassfilePath: Agg[Path], args: Seq[String]) diff --git a/scalalib/src/mill/scalalib/TestRunner.scala b/scalalib/src/mill/scalalib/TestRunner.scala index 0654e5ef..7bee681f 100644 --- a/scalalib/src/mill/scalalib/TestRunner.scala +++ b/scalalib/src/mill/scalalib/TestRunner.scala @@ -2,10 +2,10 @@ package mill.scalalib import mill.util.JsonFormatters._ object TestRunner { - def framework(frameworkName: String)(cl: ClassLoader): sbt.testing.Framework = { - cl.loadClass(frameworkName) - .newInstance() - .asInstanceOf[sbt.testing.Framework] + def frameworks(frameworkNames: Seq[String])(cl: ClassLoader): Seq[sbt.testing.Framework] = { + frameworkNames.map { name => + cl.loadClass(name).newInstance().asInstanceOf[sbt.testing.Framework] + } } case class Result(fullyQualifiedName: String, diff --git a/scalaworker/src/mill/scalaworker/ScalaWorker.scala b/scalaworker/src/mill/scalaworker/ScalaWorker.scala index 7929d7c0..a98d327b 100644 --- a/scalaworker/src/mill/scalaworker/ScalaWorker.scala +++ b/scalaworker/src/mill/scalaworker/ScalaWorker.scala @@ -37,7 +37,7 @@ object ScalaWorker{ def main(args: Array[String]): Unit = { try{ val result = new ScalaWorker(null, null).runTests( - frameworkInstance = TestRunner.framework(args(0)), + frameworkInstances = TestRunner.frameworks(args(0).split(" ")), entireClasspath = Agg.from(args(1).split(" ").map(Path(_))), testClassfilePath = Agg.from(args(2).split(" ").map(Path(_))), args = args(3) match{ case "" => Nil case x => x.split(" ").toList } @@ -240,46 +240,51 @@ class ScalaWorker(ctx0: mill.util.Ctx, }catch{case e: CompileFailed => mill.eval.Result.Failure(e.toString)} } - def runTests(frameworkInstance: ClassLoader => sbt.testing.Framework, + def runTests(frameworkInstances: ClassLoader => Seq[sbt.testing.Framework], entireClasspath: Agg[Path], testClassfilePath: Agg[Path], args: Seq[String]) (implicit ctx: mill.util.Ctx.Log): (String, Seq[Result]) = { Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, cl => { - val framework = frameworkInstance(cl) + val frameworks = frameworkInstances(cl) - val testClasses = discoverTests(cl, framework, testClassfilePath) + val events = mutable.Buffer.empty[Event] - val runner = framework.runner(args.toArray, args.toArray, cl) + val doneMessages = frameworks.map { framework => + val runner = framework.runner(args.toArray, args.toArray, cl) - val tasks = runner.tasks( - for ((cls, fingerprint) <- testClasses.toArray) - yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) - ) - val events = mutable.Buffer.empty[Event] - for (t <- tasks) { - t.execute( - new EventHandler { - def handle(event: Event) = events.append(event) - }, - Array( - new Logger { - def debug(msg: String) = ctx.log.info(msg) + val testClasses = discoverTests(cl, framework, testClassfilePath) + + val tasks = runner.tasks( + for ((cls, fingerprint) <- testClasses.toArray) + yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) + ) - def error(msg: String) = ctx.log.error(msg) + for (t <- tasks) { + t.execute( + new EventHandler { + def handle(event: Event) = events.append(event) + }, + Array( + new Logger { + def debug(msg: String) = ctx.log.info(msg) - def ansiCodesSupported() = true + def error(msg: String) = ctx.log.error(msg) - def warn(msg: String) = ctx.log.info(msg) + def ansiCodesSupported() = true - def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) + def warn(msg: String) = ctx.log.info(msg) - def info(msg: String) = ctx.log.info(msg) - }) - ) + def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) + + def info(msg: String) = ctx.log.info(msg) + }) + ) + } + runner.done() } - val doneMsg = runner.done() + val results = for(e <- events) yield { val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None Result( @@ -298,7 +303,8 @@ class ScalaWorker(ctx0: mill.util.Ctx, ex.map(_.getStackTrace) ) } - (doneMsg, results) + + (doneMessages.mkString("\n"), results) }) } -- cgit v1.2.3