diff --git a/build.sc b/build.sc index 079c07f40c3..898b5561903 100644 --- a/build.sc +++ b/build.sc @@ -397,6 +397,7 @@ trait MillBaseTestsModule extends MillJavaModule with TestModule { s"-DTEST_SCALANATIVE_VERSION=${Deps.Scalanative_0_4.scalanativeVersion}", s"-DTEST_UTEST_VERSION=${Deps.utest.dep.version}", s"-DTEST_SCALATEST_VERSION=${Deps.TestDeps.scalaTest.dep.version}", + s"-DTEST_TEST_INTERFACE_VERSION=${Deps.sbtTestInterface.dep.version}", s"-DTEST_ZIOTEST_VERSION=${Deps.TestDeps.zioTest.dep.version}", s"-DTEST_ZINC_VERSION=${Deps.zinc.dep.version}" ) diff --git a/scalalib/test/resources/testrunner/doneMessageFailure/src/DoneMessageFailureFramework.scala b/scalalib/test/resources/testrunner/doneMessageFailure/src/DoneMessageFailureFramework.scala new file mode 100644 index 00000000000..d28504e70f9 --- /dev/null +++ b/scalalib/test/resources/testrunner/doneMessageFailure/src/DoneMessageFailureFramework.scala @@ -0,0 +1,42 @@ +package mill.scalalib + +import sbt.testing._ + +class DoneMessageFailureFramework extends Framework { + def fingerprints() = Array.empty + def name() = "DoneMessageFailureFramework" + def runner( + args: Array[String], + remoteArgs: Array[String], + testClassLoader: ClassLoader + ): Runner = new Runner { + def args() = Array.empty + def done() = "test failure done message" + def remoteArgs() = Array.empty + def tasks(taskDefs: Array[TaskDef]) = Array(new Task { + def taskDef(): TaskDef = null + def execute( + eventHandler: EventHandler, + loggers: Array[Logger] + ): Array[Task] = { + eventHandler.handle(new Event { + + override def fullyQualifiedName(): String = "foo.bar" + + override def fingerprint(): Fingerprint = new Fingerprint {} + + override def selector(): Selector = new TestSelector("foo.bar") + + override def status(): Status = Status.Failure + + override def throwable(): OptionalThrowable = new OptionalThrowable() + + override def duration(): Long = 0L + + }) + Array.empty + } + def tags = Array.empty + }) + } +} diff --git a/scalalib/test/resources/testrunner/doneMessageNull/src/DoneMessageNullFramework.scala b/scalalib/test/resources/testrunner/doneMessageNull/src/DoneMessageNullFramework.scala new file mode 100644 index 00000000000..6e5c656eae2 --- /dev/null +++ b/scalalib/test/resources/testrunner/doneMessageNull/src/DoneMessageNullFramework.scala @@ -0,0 +1,18 @@ +package mill.scalalib + +import sbt.testing._ + +class DoneMessageNullFramework extends Framework { + def fingerprints() = Array.empty + def name() = "DoneMessageNullFramework" + def runner( + args: Array[String], + remoteArgs: Array[String], + testClassLoader: ClassLoader + ): Runner = new Runner { + def args() = Array.empty + def done() = null + def remoteArgs() = Array.empty + def tasks(taskDefs: Array[TaskDef]) = Array.empty + } +} diff --git a/scalalib/test/resources/testrunner/doneMessageSuccess/src/DoneMessageSuccessFramework.scala b/scalalib/test/resources/testrunner/doneMessageSuccess/src/DoneMessageSuccessFramework.scala new file mode 100644 index 00000000000..575bdf6367e --- /dev/null +++ b/scalalib/test/resources/testrunner/doneMessageSuccess/src/DoneMessageSuccessFramework.scala @@ -0,0 +1,18 @@ +package mill.scalalib + +import sbt.testing._ + +class DoneMessageSuccessFramework extends Framework { + def fingerprints() = Array.empty + def name() = "DoneMessageSuccessFramework" + def runner( + args: Array[String], + remoteArgs: Array[String], + testClassLoader: ClassLoader + ): Runner = new Runner { + def args() = Array.empty + def done() = "test success done message" + def remoteArgs() = Array.empty + def tasks(taskDefs: Array[TaskDef]) = Array.empty + } +} diff --git a/scalalib/test/src/mill/scalalib/TestRunnerTests.scala b/scalalib/test/src/mill/scalalib/TestRunnerTests.scala index 16e9e0682eb..14c82dfb9ce 100644 --- a/scalalib/test/src/mill/scalalib/TestRunnerTests.scala +++ b/scalalib/test/src/mill/scalalib/TestRunnerTests.scala @@ -2,10 +2,13 @@ package mill.scalalib import mill.{Agg, T} +import mill.api.Result import mill.util.{TestEvaluator, TestUtil} import utest._ import utest.framework.TestPath +import java.io.{ByteArrayOutputStream, PrintStream} + object TestRunnerTests extends TestSuite { object testrunner extends TestUtil.BaseModule with ScalaModule { override def millSourcePath = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') @@ -28,6 +31,23 @@ object TestRunnerTests extends TestSuite { } } + trait DoneMessage extends ScalaTests { + override def ivyDeps = T { + super.ivyDeps() ++ Agg( + ivy"org.scala-sbt:test-interface:${sys.props.getOrElse("TEST_TEST_INTERFACE_VERSION", ???)}" + ) + } + } + object doneMessageSuccess extends DoneMessage { + def testFramework = "mill.scalalib.DoneMessageSuccessFramework" + } + object doneMessageFailure extends DoneMessage { + def testFramework = "mill.scalalib.DoneMessageFailureFramework" + } + object doneMessageNull extends DoneMessage { + def testFramework = "mill.scalalib.DoneMessageNullFramework" + } + object ziotest extends ScalaTests with TestModule.ZioTest { override def ivyDeps = T { super.ivyDeps() ++ Agg( @@ -42,11 +62,12 @@ object TestRunnerTests extends TestSuite { def workspaceTest[T]( m: TestUtil.BaseModule, + outStream: PrintStream = System.out, resourcePath: os.Path = resourcePath )(t: TestEvaluator => T)( implicit tp: TestPath ): T = { - val eval = new TestEvaluator(m) + val eval = new TestEvaluator(m, outStream = outStream) os.remove.all(m.millSourcePath) os.remove.all(eval.outPath) os.makeDir.all(m.millSourcePath / os.up) @@ -88,6 +109,29 @@ object TestRunnerTests extends TestSuite { } } + "doneMessage" - { + test("failure") { + val outStream = new ByteArrayOutputStream() + workspaceTest(testrunner, outStream = new PrintStream(outStream, true)) { eval => + val Left(Result.Failure(msg, _)) = eval(testrunner.doneMessageFailure.test()) + val stdout = new String(outStream.toByteArray) + assert(stdout.contains("test failure done message")) + } + } + test("success") { + val outStream = new ByteArrayOutputStream() + workspaceTest(testrunner, outStream = new PrintStream(outStream, true)) { eval => + val Right(_) = eval(testrunner.doneMessageSuccess.test()) + val stdout = new String(outStream.toByteArray) + assert(stdout.contains("test success done message")) + } + } + test("null") { + workspaceTest(testrunner) { eval => + val Right(_) = eval(testrunner.doneMessageNull.test()) + } + } + } "ScalaTest" - { test("scalatest.test") { workspaceTest(testrunner) { eval => diff --git a/testrunner/src/mill/testrunner/TestRunnerUtils.scala b/testrunner/src/mill/testrunner/TestRunnerUtils.scala index 10f32e466de..3cc60d14d47 100644 --- a/testrunner/src/mill/testrunner/TestRunnerUtils.scala +++ b/testrunner/src/mill/testrunner/TestRunnerUtils.scala @@ -156,6 +156,13 @@ import scala.jdk.CollectionConverters.IteratorHasAsScala runner.done() } + if (doneMessage != null && doneMessage.nonEmpty) { + if (doneMessage.endsWith("\n")) + ctx.log.outputStream.print(doneMessage) + else + ctx.log.outputStream.println(doneMessage) + } + val results = for (e <- events.iterator().asScala) yield { val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None