diff --git a/docs/tests.md b/docs/tests.md index b583401f..8f644ca7 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -92,11 +92,11 @@ to add make sure that `LazyFuture.run()` gets called. ```scala mdoc import scala.concurrent.ExecutionContext.Implicits.global class TaskSuite extends munit.FunSuite { - override def munitTestValue(testValue: => Any): Future[Any] = - super.munitTestValue(testValue).flatMap { + override def munitValueTransforms = super.munitValueTransforms ++ List( + new ValueTransform("LazyFuture", { case LazyFuture(run) => run() - case value => Future.successful(value) - } + }) + ) implicit val ec = ExecutionContext.global test("ok-task") { LazyFuture { @@ -197,18 +197,22 @@ evaluate the body multiple times. ```scala mdoc case class Rerun(count: Int) extends munit.Tag("Rerun") -class MyWindowsSuite extends munit.FunSuite { - override def munitRunTest(options: munit.TestOptions, body: () => Future[Any]): Future[Any] = { - val rerunCount = options.tags.collectFirst { - case Rerun(n) => n - }.getOrElse(1) - val futures: Seq[Future[Any]] = 1.to(rerunCount).map(_ => - super.munitRunTest(options, body)) - val result: Future[Seq[Any]] = Future.sequence(futures) - result - } - test("files".tag(Rerun(10))) { - println("Hello") // will run 10 times +class MyRerunSuite extends munit.FunSuite { + override def munitTestTransforms = super.munitTestTransforms ++ List( + new TestTransform("Rerun", { test => + val rerunCount = test.tags + .collectFirst { case Rerun(n) => n } + .getOrElse(1) + if (rerunCount == 1) test + else { + test.withBody(() => { + Future.sequence(1.to(rerunCount).map(_ => test.body())) + }) + } + }) + ) + test("files".tag(Rerun(3))) { + println("Hello") // will run 3 times } test("files") { // will run once, like normal @@ -229,8 +233,11 @@ condition. ```scala mdoc class ScalaVersionSuite extends munit.FunSuite { val scalaVersion = scala.util.Properties.versionNumberString - override def munitNewTest(test: Test): Test = - test.withName(test.name + "-" + scalaVersion) + override def munitTestTransforms = super.munitTestTransforms ++ List( + new TestTransform("append Scala version", { test => + test.withName(test.name + "-" + scalaVersion) + }) + ) test("foo") { assert(!scalaVersion.startsWith("2.11")) } @@ -295,8 +302,7 @@ in your project. ```scala mdoc abstract class BaseSuite extends munit.FunSuite { override val munitTimeout = Duration(1, "min") - override def munitTestValue(value: => Any): Future[Any] = - ??? + override def munitTestTransforms = super.munitTestTransforms ++ List(???) // ... } class MyFirstSuite extends BaseSuite { /* ... */ } diff --git a/munit/shared/src/main/scala/munit/FunFixtures.scala b/munit/shared/src/main/scala/munit/FunFixtures.scala new file mode 100644 index 00000000..97f17c8c --- /dev/null +++ b/munit/shared/src/main/scala/munit/FunFixtures.scala @@ -0,0 +1,51 @@ +package munit + +trait FunFixtures { self: FunSuite => + + class FunFixture[T]( + val setup: TestOptions => T, + val teardown: T => Unit + ) { + def test(options: TestOptions)( + body: T => Any + )(implicit loc: Location): Unit = { + self.test(options) { + val argument = setup(options) + try body(argument) + finally teardown(argument) + }(loc) + } + } + object FunFixture { + def map2[A, B](a: FunFixture[A], b: FunFixture[B]): FunFixture[(A, B)] = + new FunFixture[(A, B)]( + setup = { options => + (a.setup(options), b.setup(options)) + }, + teardown = { + case (argumentA, argumentB) => + try a.teardown(argumentA) + finally b.teardown(argumentB) + } + ) + def map3[A, B, C]( + a: FunFixture[A], + b: FunFixture[B], + c: FunFixture[C] + ): FunFixture[(A, B, C)] = + new FunFixture[(A, B, C)]( + setup = { options => + (a.setup(options), b.setup(options), c.setup(options)) + }, + teardown = { + case (argumentA, argumentB, argumentC) => + try a.teardown(argumentA) + finally { + try b.teardown(argumentB) + finally c.teardown(argumentC) + } + } + ) + } + +} diff --git a/munit/shared/src/main/scala/munit/FunSuite.scala b/munit/shared/src/main/scala/munit/FunSuite.scala index 04f374d4..ce76f976 100644 --- a/munit/shared/src/main/scala/munit/FunSuite.scala +++ b/munit/shared/src/main/scala/munit/FunSuite.scala @@ -1,96 +1,39 @@ package munit -import munit.internal.console.StackTraces -import munit.internal.FutureCompat._ - import scala.collection.mutable -import scala.util.Failure -import scala.util.Success import scala.concurrent.Future import scala.util.control.NonFatal -import scala.util.Try -import scala.concurrent.duration.Duration -import munit.internal.PlatformCompat abstract class FunSuite extends Suite with Assertions - with TestOptionsConversions { self => + with FunFixtures + with TestOptionsConversions + with TestTransforms + with SuiteTransforms + with ValueTransforms { self => final type TestValue = Future[Any] - def isCI: Boolean = "true" == System.getenv("CI") - def munitIgnore: Boolean = false - def munitFlakyOK: Boolean = "true" == System.getenv("MUNIT_FLAKY_OK") - - private val defaultTimeout = Duration(30, "s") - def munitTimeout: Duration = defaultTimeout - val munitTestsBuffer: mutable.ArrayBuffer[Test] = - mutable.ArrayBuffer.empty[Test] + final val munitTestsBuffer: mutable.ListBuffer[Test] = + mutable.ListBuffer.empty[Test] def munitTests(): Seq[Test] = { - if (munitIgnore) { - Nil - } else { - val onlyTests = munitTestsBuffer.filter(_.tags(Only)) - if (onlyTests.nonEmpty) { - if (isCI) { - onlyTests.toSeq.map(t => - if (t.tags(Only)) { - t.withBody[TestValue](() => - fail("'Only' tag is not allowed when `isCI=true`")(t.location) - ) - } else { - t - } - ) - } else { - onlyTests.toSeq - } - } else { - munitTestsBuffer.toSeq - } - } + munitSuiteTransform(munitTestsBuffer.toList) } - def munitTestValue(testValue: => Any): Future[Any] = { - // Takes an arbitrarily nested future `Future[Future[Future[...]]]` and - // returns a `Future[T]` where `T` is not a `Future`. - def flattenFuture(future: Future[_]): Future[_] = { - val nested = future.map { - case f: Future[_] => flattenFuture(f) - case x => Future.successful(x) - }(munitExecutionContext) - nested.flattenCompat(munitExecutionContext) - } - val wrappedFuture = Future.fromTry(Try(StackTraces.dropOutside(testValue))) - val flatFuture = flattenFuture(wrappedFuture) - val awaitedFuture = PlatformCompat.waitAtMost(flatFuture, munitTimeout) - awaitedFuture - } - - def munitNewTest(test: Test): Test = - test - - def test(name: String)( - body: => Any - )(implicit loc: Location): Unit = { + def test(name: String)(body: => Any)(implicit loc: Location): Unit = { test(new TestOptions(name, Set.empty, loc))(body) } - - def test(options: TestOptions)( - body: => Any - )(implicit loc: Location): Unit = { - munitTestsBuffer += munitNewTest( + def test(options: TestOptions)(body: => Any)(implicit loc: Location): Unit = { + munitTestsBuffer += munitTestTransform( new Test( options.name, { () => - munitRunTest(options, () => { - try { - munitTestValue(body) - } catch { - case NonFatal(e) => - Future.failed(e) - } - }) + try { + munitValueTransform(body) + } catch { + case NonFatal(e) => + Future.failed(e) + } }, options.tags.toSet, loc @@ -98,98 +41,4 @@ abstract class FunSuite ) } - def munitRunTest( - options: TestOptions, - body: () => Future[Any] - ): Future[Any] = { - if (options.tags(Fail)) { - munitExpectFailure(options, body) - } else if (options.tags(Flaky)) { - munitFlaky(options, body) - } else { - body() - } - } - - def munitFlaky( - options: TestOptions, - body: () => Future[Any] - ): Future[Any] = { - body().transformCompat { - case Success(value) => Success(value) - case Failure(exception) => - if (munitFlakyOK) { - Success(new TestValues.FlakyFailure(exception)) - } else { - throw exception - } - }(munitExecutionContext) - } - - def munitExpectFailure( - options: TestOptions, - body: () => Future[Any] - ): Future[Any] = { - body().transformCompat { - case Success(value) => - Failure( - throw new FailException( - munitLines.formatLine( - options.location, - "expected failure but test passed" - ), - options.location - ) - ) - case Failure(exception) => - Success(()) - }(munitExecutionContext) - } - - class FunFixture[T]( - val setup: TestOptions => T, - val teardown: T => Unit - ) { - def test(options: TestOptions)( - body: T => Any - )(implicit loc: Location): Unit = { - self.test(options) { - val argument = setup(options) - try body(argument) - finally teardown(argument) - }(loc) - } - } - object FunFixture { - def map2[A, B](a: FunFixture[A], b: FunFixture[B]): FunFixture[(A, B)] = - new FunFixture[(A, B)]( - setup = { options => - (a.setup(options), b.setup(options)) - }, - teardown = { - case (argumentA, argumentB) => - try a.teardown(argumentA) - finally b.teardown(argumentB) - } - ) - def map3[A, B, C]( - a: FunFixture[A], - b: FunFixture[B], - c: FunFixture[C] - ): FunFixture[(A, B, C)] = - new FunFixture[(A, B, C)]( - setup = { options => - (a.setup(options), b.setup(options), c.setup(options)) - }, - teardown = { - case (argumentA, argumentB, argumentC) => - try a.teardown(argumentA) - finally { - try b.teardown(argumentB) - finally c.teardown(argumentC) - } - } - ) - } - } diff --git a/munit/shared/src/main/scala/munit/GenericTest.scala b/munit/shared/src/main/scala/munit/GenericTest.scala index 2212f4bc..6ec3b904 100644 --- a/munit/shared/src/main/scala/munit/GenericTest.scala +++ b/munit/shared/src/main/scala/munit/GenericTest.scala @@ -29,6 +29,10 @@ class GenericTest[T]( withTags(tags + newTag) def withLocation(newLocation: Location): GenericTest[T] = copy(location = newLocation) + + def withBodyMap[A](newBody: T => A): GenericTest[A] = + withBody[A](() => newBody(body())) + private[this] def copy[A]( name: String = this.name, body: () => A = this.body, diff --git a/munit/shared/src/main/scala/munit/MUnitRunner.scala b/munit/shared/src/main/scala/munit/MUnitRunner.scala index 3bea013d..93e59986 100644 --- a/munit/shared/src/main/scala/munit/MUnitRunner.scala +++ b/munit/shared/src/main/scala/munit/MUnitRunner.scala @@ -21,6 +21,7 @@ import scala.concurrent.Await import scala.concurrent.duration.Duration import scala.concurrent.Future import scala.concurrent.ExecutionContext +import java.util.concurrent.ExecutionException class MUnitRunner(val cls: Class[_ <: Suite], newInstance: () => Suite) extends Runner @@ -222,7 +223,14 @@ class MUnitRunner(val cls: Class[_ <: Suite], newInstance: () => Suite) Future.successful(()) case NonFatal(ex) => StackTraces.trimStackTrace(ex) - val failure = new Failure(description, ex) + val cause = ex match { + case e: ExecutionException + if "Boxed Exception" == e.getMessage() && + e.getCause() != null => + e.getCause() + case e => e + } + val failure = new Failure(description, cause) ex match { case _: AssumptionViolatedException => notifier.fireTestAssumptionFailed(failure) diff --git a/munit/shared/src/main/scala/munit/SuiteTransforms.scala b/munit/shared/src/main/scala/munit/SuiteTransforms.scala new file mode 100644 index 00000000..21c27acc --- /dev/null +++ b/munit/shared/src/main/scala/munit/SuiteTransforms.scala @@ -0,0 +1,64 @@ +package munit + +import scala.concurrent.Future +import scala.util.control.NonFatal + +trait SuiteTransforms { this: FunSuite => + + final class SuiteTransform(val name: String, fn: List[Test] => List[Test]) + extends Function1[List[Test], List[Test]] { + def apply(v1: List[Test]): List[Test] = fn(v1) + } + + def munitSuiteTransforms: List[SuiteTransform] = + List( + munitIgnoreSuiteTransform, + munitOnlySuiteTransform + ) + + final def munitSuiteTransform(tests: List[Test]): List[Test] = { + try { + munitSuiteTransforms.foldLeft(tests) { + case (ts, fn) => fn(ts) + } + } catch { + case NonFatal(e) => + List( + new Test( + "munitSuiteTransform", + () => Future.failed(e) + )(Location.empty) + ) + } + } + + def munitIgnore: Boolean = false + final def munitIgnoreSuiteTransform: SuiteTransform = + new SuiteTransform("munitIgnore", { tests => + if (munitIgnore) Nil + else tests + }) + + def isCI: Boolean = "true" == System.getenv("CI") + final def munitOnlySuiteTransform: SuiteTransform = + new SuiteTransform("only", { tests => + val onlySuite = tests.filter(_.tags(Only)) + if (onlySuite.nonEmpty) { + if (!isCI) { + onlySuite + } else { + onlySuite.map(t => + if (t.tags(Only)) { + t.withBody[TestValue](() => + fail("'Only' tag is not allowed when `isCI=true`")(t.location) + ) + } else { + t + } + ) + } + } else { + tests + } + }) +} diff --git a/munit/shared/src/main/scala/munit/TestTransforms.scala b/munit/shared/src/main/scala/munit/TestTransforms.scala new file mode 100644 index 00000000..af8e4ecd --- /dev/null +++ b/munit/shared/src/main/scala/munit/TestTransforms.scala @@ -0,0 +1,75 @@ +package munit + +import munit.internal.FutureCompat._ +import scala.util.Success +import scala.util.Failure +import scala.concurrent.Future +import scala.util.control.NonFatal + +trait TestTransforms { this: FunSuite => + + final class TestTransform(val name: String, fn: Test => Test) + extends Function1[Test, Test] { + def apply(v1: Test): Test = fn(v1) + } + + def munitTestTransforms: List[TestTransform] = + List( + munitFailTransform, + munitFlakyTransform + ) + + final def munitTestTransform(test: Test): Test = { + try { + munitTestTransforms.foldLeft(test) { + case (t, fn) => fn(t) + } + } catch { + case NonFatal(e) => + test.withBody[TestValue](() => Future.failed(e)) + } + } + + final def munitFailTransform: TestTransform = + new TestTransform("fail", { t => + if (t.tags(Fail)) { + t.withBodyMap[TestValue]( + _.transformCompat { + case Success(value) => + Failure( + throw new FailException( + munitLines.formatLine( + t.location, + "expected failure but test passed" + ), + t.location + ) + ) + case Failure(exception) => + Success(()) + }(munitExecutionContext) + ) + } else { + t + } + }) + + def munitFlakyOK: Boolean = "true" == System.getenv("MUNIT_FLAKY_OK") + final def munitFlakyTransform: TestTransform = + new TestTransform("flaky", { t => + if (t.tags(Flaky)) { + t.withBodyMap(_.transformCompat { + case Success(value) => Success(value) + case Failure(exception) => + if (munitFlakyOK) { + Success(new TestValues.FlakyFailure(exception)) + } else { + throw exception + } + }(munitExecutionContext)) + } else { + t + } + }) + +} diff --git a/munit/shared/src/main/scala/munit/ValueTransforms.scala b/munit/shared/src/main/scala/munit/ValueTransforms.scala new file mode 100644 index 00000000..4c2ff54a --- /dev/null +++ b/munit/shared/src/main/scala/munit/ValueTransforms.scala @@ -0,0 +1,50 @@ +package munit + +import scala.concurrent.Future +import munit.internal.FutureCompat._ +import scala.util.Try +import munit.internal.console.StackTraces +import munit.internal.PlatformCompat +import scala.concurrent.duration.Duration +import scala.concurrent.duration.FiniteDuration +import java.util.concurrent.TimeUnit + +trait ValueTransforms { this: FunSuite => + + final class ValueTransform( + val name: String, + fn: PartialFunction[Any, Future[Any]] + ) extends Function1[Any, Option[Future[Any]]] { + def apply(v1: Any): Option[Future[Any]] = fn.lift(v1) + } + + def munitValueTransforms: List[ValueTransform] = + List( + munitFutureTransform + ) + + def munitTimeout: Duration = new FiniteDuration(30, TimeUnit.SECONDS) + final def munitValueTransform(testValue: => Any): Future[Any] = { + // Takes an arbitrarily nested future `Future[Future[Future[...]]]` and + // returns a `Future[T]` where `T` is not a `Future`. + def flattenFuture(future: Future[_]): Future[_] = { + val nested: Future[Future[Any]] = future.map { value => + val transformed = munitValueTransforms.iterator + .map(fn => fn(value)) + .collectFirst { case Some(future) => future } + transformed match { + case Some(f) => flattenFuture(f) + case None => Future.successful(value) + } + }(munitExecutionContext) + nested.flattenCompat(munitExecutionContext) + } + val wrappedFuture = Future.fromTry(Try(StackTraces.dropOutside(testValue))) + val flatFuture = flattenFuture(wrappedFuture) + val awaitedFuture = PlatformCompat.waitAtMost(flatFuture, munitTimeout) + awaitedFuture + } + + final def munitFutureTransform: ValueTransform = + new ValueTransform("Future", { case e: Future[_] => e }) +} diff --git a/tests/shared/src/main/scala/munit/ScalaVersionFrameworkSuite.scala b/tests/shared/src/main/scala/munit/ScalaVersionFrameworkSuite.scala index a62f012d..1d18c8ee 100644 --- a/tests/shared/src/main/scala/munit/ScalaVersionFrameworkSuite.scala +++ b/tests/shared/src/main/scala/munit/ScalaVersionFrameworkSuite.scala @@ -2,8 +2,14 @@ package munit class ScalaVersionFrameworkSuite extends munit.FunSuite { val scalaVersion = "2.12.100" - override def munitNewTest(test: Test): Test = - test.withName(test.name + "-" + scalaVersion) + + override def munitTestTransforms: List[TestTransform] = + super.munitTestTransforms ++ List( + new TestTransform("append scala version", { test => + test.withName(test.name + "-" + scalaVersion) + }) + ) + test("foo") { assertEquals(List(1).head, 1) } diff --git a/tests/shared/src/main/scala/munit/SuiteTransformCrashFrameworkSuite.scala b/tests/shared/src/main/scala/munit/SuiteTransformCrashFrameworkSuite.scala new file mode 100644 index 00000000..d25baec1 --- /dev/null +++ b/tests/shared/src/main/scala/munit/SuiteTransformCrashFrameworkSuite.scala @@ -0,0 +1,15 @@ +package munit + +class SuiteTransformCrashFrameworkSuite extends munit.FunSuite { + override val munitSuiteTransforms: List[SuiteTransform] = List( + new SuiteTransform("boom", tests => ???) + ) + + test("hello") {} +} +object SuiteTransformCrashFrameworkSuite + extends FrameworkTest( + classOf[SuiteTransformCrashFrameworkSuite], + """|==> failure munit.SuiteTransformCrashFrameworkSuite.munitSuiteTransform - an implementation is missing + |""".stripMargin + ) diff --git a/tests/shared/src/main/scala/munit/SuiteTransformFrameworkSuite.scala b/tests/shared/src/main/scala/munit/SuiteTransformFrameworkSuite.scala new file mode 100644 index 00000000..a7102d0b --- /dev/null +++ b/tests/shared/src/main/scala/munit/SuiteTransformFrameworkSuite.scala @@ -0,0 +1,22 @@ +package munit + +class SuiteTransformFrameworkSuite extends munit.FunSuite { + override val munitSuiteTransforms: List[SuiteTransform] = List( + new SuiteTransform( + "hello", + tests => tests.filter(_.name.startsWith("hello")) + ) + ) + + test("hello") {} + test("hello-yes") {} + test("goodbye") {} + test("goodbye-yes") {} +} +object SuiteTransformFrameworkSuite + extends FrameworkTest( + classOf[SuiteTransformFrameworkSuite], + """|==> success munit.SuiteTransformFrameworkSuite.hello + |==> success munit.SuiteTransformFrameworkSuite.hello-yes + |""".stripMargin + ) diff --git a/tests/shared/src/main/scala/munit/TestTransformCrashFrameworkSuite.scala b/tests/shared/src/main/scala/munit/TestTransformCrashFrameworkSuite.scala new file mode 100644 index 00000000..52b7b3f5 --- /dev/null +++ b/tests/shared/src/main/scala/munit/TestTransformCrashFrameworkSuite.scala @@ -0,0 +1,15 @@ +package munit + +class TestTransformCrashFrameworkSuite extends munit.FunSuite { + override val munitTestTransforms: List[TestTransform] = List( + new TestTransform("boom", test => ???) + ) + + test("hello") {} +} +object TestTransformCrashFrameworkSuite + extends FrameworkTest( + classOf[TestTransformCrashFrameworkSuite], + """|==> failure munit.TestTransformCrashFrameworkSuite.hello - an implementation is missing + |""".stripMargin + ) diff --git a/tests/shared/src/main/scala/munit/TestTransformFrameworkSuite.scala b/tests/shared/src/main/scala/munit/TestTransformFrameworkSuite.scala new file mode 100644 index 00000000..0cd7ac25 --- /dev/null +++ b/tests/shared/src/main/scala/munit/TestTransformFrameworkSuite.scala @@ -0,0 +1,15 @@ +package munit + +class TestTransformFrameworkSuite extends munit.FunSuite { + override val munitTestTransforms: List[TestTransform] = List( + new TestTransform("ok", test => test.withName(test.name + "-ok")) + ) + + test("hello") {} +} +object TestTransformFrameworkSuite + extends FrameworkTest( + classOf[TestTransformFrameworkSuite], + """|==> success munit.TestTransformFrameworkSuite.hello-ok + |""".stripMargin + ) diff --git a/tests/shared/src/main/scala/munit/ValueTransformCrashFrameworkSuite.scala b/tests/shared/src/main/scala/munit/ValueTransformCrashFrameworkSuite.scala new file mode 100644 index 00000000..6eaf843e --- /dev/null +++ b/tests/shared/src/main/scala/munit/ValueTransformCrashFrameworkSuite.scala @@ -0,0 +1,15 @@ +package munit + +class ValueTransformCrashFrameworkSuite extends munit.FunSuite { + override val munitValueTransforms: List[ValueTransform] = List( + new ValueTransform("boom", { case test => ??? }) + ) + + test("hello") {} +} +object ValueTransformCrashFrameworkSuite + extends FrameworkTest( + classOf[ValueTransformCrashFrameworkSuite], + """|==> failure munit.ValueTransformCrashFrameworkSuite.hello - an implementation is missing + |""".stripMargin + ) diff --git a/tests/shared/src/main/scala/munit/ValueTransformFrameworkSuite.scala b/tests/shared/src/main/scala/munit/ValueTransformFrameworkSuite.scala new file mode 100644 index 00000000..703655cf --- /dev/null +++ b/tests/shared/src/main/scala/munit/ValueTransformFrameworkSuite.scala @@ -0,0 +1,21 @@ +package munit + +import scala.concurrent.Future + +class ValueTransformFrameworkSuite extends munit.FunSuite { + override val munitValueTransforms: List[ValueTransform] = List( + new ValueTransform("number", { + case 42 => Future.failed(new Exception("boom")) + }) + ) + + test("explode") { 42 } + test("ok") { 41 } +} +object ValueTransformFrameworkSuite + extends FrameworkTest( + classOf[ValueTransformFrameworkSuite], + """|==> failure munit.ValueTransformFrameworkSuite.explode - boom + |==> success munit.ValueTransformFrameworkSuite.ok + |""".stripMargin + ) diff --git a/tests/shared/src/test/scala/munit/BaseSuite.scala b/tests/shared/src/test/scala/munit/BaseSuite.scala index 4dc90316..a16030f3 100644 --- a/tests/shared/src/test/scala/munit/BaseSuite.scala +++ b/tests/shared/src/test/scala/munit/BaseSuite.scala @@ -1,25 +1,24 @@ package munit import munit.internal.PlatformCompat -import scala.concurrent.Future class BaseSuite extends FunSuite { - override def munitRunTest( - options: TestOptions, - body: () => Future[Any] - ): Future[Any] = { - def isDotty: Boolean = - BuildInfo.scalaVersion.startsWith("0.") - def is213: Boolean = - BuildInfo.scalaVersion.startsWith("2.13") || isDotty - if (options.tags(NoDotty) && isDotty) { - Future.successful(Ignore) - } else if (options.tags(Only213) && !is213) { - Future.successful(Ignore) - } else if (options.tags(OnlyJVM) && !PlatformCompat.isJVM) { - Future.successful(Ignore) - } else { - super.munitRunTest(options, body) - } - } + override def munitTestTransforms: List[TestTransform] = + super.munitTestTransforms ++ List( + new TestTransform("BaseSuite", { test => + def isDotty: Boolean = + BuildInfo.scalaVersion.startsWith("0.") + def is213: Boolean = + BuildInfo.scalaVersion.startsWith("2.13") || isDotty + if (test.tags(NoDotty) && isDotty) { + test.tag(Ignore) + } else if (test.tags(Only213) && !is213) { + test.tag(Ignore) + } else if (test.tags(OnlyJVM) && !PlatformCompat.isJVM) { + test.tag(Ignore) + } else { + test + } + }) + ) } diff --git a/tests/shared/src/test/scala/munit/FrameworkSuite.scala b/tests/shared/src/test/scala/munit/FrameworkSuite.scala index 6e8e2631..dd810a4a 100644 --- a/tests/shared/src/test/scala/munit/FrameworkSuite.scala +++ b/tests/shared/src/test/scala/munit/FrameworkSuite.scala @@ -13,7 +13,13 @@ class FrameworkSuite extends BaseFrameworkSuite { FixtureFrameworkSuite, TagsIncludeFramweworkSuite, TagsIncludeExcludeFramweworkSuite, - TagsExcludeFramweworkSuite + TagsExcludeFramweworkSuite, + SuiteTransformCrashFrameworkSuite, + SuiteTransformFrameworkSuite, + TestTransformCrashFrameworkSuite, + TestTransformFrameworkSuite, + ValueTransformCrashFrameworkSuite, + ValueTransformFrameworkSuite ) tests.foreach { t => check(t) diff --git a/tests/shared/src/test/scala/munit/LazyFutureSuite.scala b/tests/shared/src/test/scala/munit/LazyFutureSuite.scala index b695af96..ffbdf4f6 100644 --- a/tests/shared/src/test/scala/munit/LazyFutureSuite.scala +++ b/tests/shared/src/test/scala/munit/LazyFutureSuite.scala @@ -10,11 +10,13 @@ class LazyFutureSuite extends FunSuite { def apply[T](thunk: => T)(implicit ec: ExecutionContext): LazyFuture[T] = LazyFuture(() => Future(thunk)) } - override def munitTestValue(testValue: => Any): Future[Any] = - super.munitTestValue(testValue).flatMap { - case LazyFuture(run) => run() - case value => Future.successful(value) - } + + override def munitValueTransforms: List[ValueTransform] = + super.munitValueTransforms ++ List( + new ValueTransform("LazyFuture", { + case LazyFuture(run) => run() + }) + ) test("ok-task".fail) { LazyFuture { @@ -22,4 +24,13 @@ class LazyFutureSuite extends FunSuite { throw new RuntimeException("BOOM!") } } + + test("nested".fail) { + LazyFuture { + LazyFuture { + // Test will fail because LazyFuture.run()` is automatically called + throw new RuntimeException("BOOM!") + } + } + } } diff --git a/website/blog/2020-02-01-hello-world.md b/website/blog/2020-02-01-hello-world.md index fd72201c..fe31ccd6 100644 --- a/website/blog/2020-02-01-hello-world.md +++ b/website/blog/2020-02-01-hello-world.md @@ -127,16 +127,17 @@ import scala.util.Properties import munit._ object Windows213 extends Tag("Windows213") class MySuite extends FunSuite { - // reminder: type Test = GenericTest[Future[Any]] - override def munitNewTest(test: Test): Test = { - val isIgnored = - test.tags(Windows213) && !( - Properties.isWin && - Properties.versionNumberString.startsWith("2.13") - ) - if (isIgnored) test.tag(Ignore) - else test - } + override def munitTestTransforms = super.munitTestTransforms ++ List( + new TestTransform("Windows213", { test => + val isIgnored = + test.tags(Windows213) && !( + Properties.isWin && + Properties.versionNumberString.startsWith("2.13") + ) + if (isIgnored) test.tag(Ignore) + else test + }) + ) test("windows-213".tag(Windows213)) { // Only runs when operating system is Windows and Scala version is 2.13 @@ -145,6 +146,7 @@ class MySuite extends FunSuite { // Always runs like a normal test. } } + ``` By encoding the environment requirements in the test implementation, we prevent