Skip to content

Commit

Permalink
Replace specs2 with munit
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Dec 27, 2024
1 parent 1307e6d commit e671306
Show file tree
Hide file tree
Showing 94 changed files with 7,527 additions and 7,725 deletions.
7 changes: 1 addition & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ ThisBuild / autoAPIMappings := true

val CatsVersion = "2.11.0"
val CatsMtlVersion = "1.3.1"
val Specs2Version = "4.20.5"
val ScalaCheckVersion = "1.17.1"
val CoopVersion = "1.2.0"
val MUnitVersion = "1.0.0-M11"
Expand Down Expand Up @@ -922,10 +921,6 @@ lazy val tests: CrossProject = crossProject(JSPlatform, JVMPlatform, NativePlatf
name := "cats-effect-tests",
libraryDependencies ++= Seq(
"org.scalacheck" %%% "scalacheck" % ScalaCheckVersion,

"org.specs2" %%% "specs2-scalacheck" % Specs2Version % Test,
"org.typelevel" %%% "discipline-specs2" % "1.4.0" % Test,

"org.scalameta" %%% "munit" % MUnitVersion % Test,
"org.scalameta" %%% "munit-scalacheck" % MUnitScalaCheckVersion % Test,
"org.typelevel" %%% "discipline-munit" % DisciplineMUnitVersion % Test,
Expand All @@ -952,7 +947,7 @@ def configureIOAppTests(p: Project): Project =
p.enablePlugins(NoPublishPlugin, BuildInfoPlugin)
.settings(
Test / unmanagedSourceDirectories += (LocalRootProject / baseDirectory).value / "ioapp-tests" / "src" / "test" / "scala",
libraryDependencies += "org.specs2" %%% "specs2-core" % Specs2Version % Test,
libraryDependencies += "org.scalameta" %%% "munit" % MUnitVersion % Test,
buildInfoPackage := "cats.effect",
buildInfoKeys ++= Seq(
"jsRunner" -> (tests.js / Compile / fastOptJS / artifactPath).value,
Expand Down
187 changes: 94 additions & 93 deletions ioapp-tests/src/test/scala/IOAppSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package cats.effect

import org.specs2.mutable.Specification

import scala.io.Source
import scala.sys.process.{BasicIO, Process, ProcessBuilder}

import java.io.File

class IOAppSpec extends Specification {
import munit.FunSuite

class IOAppSpec extends FunSuite {

abstract class Platform(val id: String) { outer =>
def builder(proto: String, args: List[String]): ProcessBuilder
Expand Down Expand Up @@ -145,118 +145,123 @@ class IOAppSpec extends Specification {
platform == JVM && sys.props.get("java.version").filter(_.startsWith("1.8")).isDefined
lazy val isWindows = System.getProperty("os.name").toLowerCase.contains("windows")

s"IOApp (${platform.id})" should {
{

if (!isWindows) { // these tests have all been emperically flaky on Windows CI builds, so they're disabled

"evaluate and print hello world" in {
test(s"IOApp (${platform.id}) - evaluate and print hello world") {
val h = platform("HelloWorld", Nil)
h.awaitStatus() mustEqual 0
h.stdout() mustEqual s"Hello, World!${System.lineSeparator()}"
assertEquals(h.awaitStatus(), 0)
assertEquals(h.stdout(), s"Hello, World!${System.lineSeparator()}")
}

"pass all arguments to child" in {
test(s"IOApp (${platform.id}) - pass all arguments to child") {
val expected = List("the", "quick", "brown", "fox jumped", "over")
val h = platform("Arguments", expected)
h.awaitStatus() mustEqual 0
h.stdout() mustEqual expected.mkString(
"",
System.lineSeparator(),
System.lineSeparator())
assertEquals(h.awaitStatus(), 0)
assertEquals(
h.stdout(),
expected.mkString("", System.lineSeparator(), System.lineSeparator())
)
}

"exit on non-fatal error" in {
test(s"IOApp (${platform.id}) - exit on non-fatal error") {
val h = platform("NonFatalError", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
}

"exit with leaked fibers" in {
test(s"IOApp (${platform.id}) - exit with leaked fibers") {
val h = platform("LeakedFiber", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}

"exit on fatal error" in {
test(s"IOApp (${platform.id}) - exit on fatal error") {
val h = platform("FatalError", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
h.stdout() must not(contain("sadness"))
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
assert(!h.stdout().contains("sadness"))
}

"exit on fatal error with other unsafe runs" in {
test(s"IOApp (${platform.id}) - exit on fatal error with other unsafe runs") {
val h = platform("FatalErrorUnsafeRun", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
}

"exit on raising a fatal error with attempt" in {
test(s"IOApp (${platform.id}) - exit on raising a fatal error with attempt") {
val h = platform("RaiseFatalErrorAttempt", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
h.stdout() must not(contain("sadness"))
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
assert(!h.stdout().contains("sadness"))
}

"exit on raising a fatal error with handleError" in {
test(s"IOApp (${platform.id}) - exit on raising a fatal error with handleError") {
val h = platform("RaiseFatalErrorHandle", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
h.stdout() must not(contain("sadness"))
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
assert(!h.stdout().contains("sadness"))
}

"exit on raising a fatal error inside a map" in {
test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a map") {
val h = platform("RaiseFatalErrorMap", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
h.stdout() must not(contain("sadness"))
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
assert(!h.stdout().contains("sadness"))
}

"exit on raising a fatal error inside a flatMap" in {
test(s"IOApp (${platform.id}) - exit on raising a fatal error inside a flatMap") {
val h = platform("RaiseFatalErrorFlatMap", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("Boom!")
h.stdout() must not(contain("sadness"))
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("Boom!"))
assert(!h.stdout().contains("sadness"))
}

"warn on global runtime collision" in {
test(s"IOApp (${platform.id}) - warn on global runtime collision") {
val h = platform("GlobalRacingInit", List.empty)
h.awaitStatus() mustEqual 0
h.stderr() must contain(
"Cats Effect global runtime already initialized; custom configurations will be ignored")
h.stderr() must not(contain("boom"))
assertEquals(h.awaitStatus(), 0)
assert(
h.stderr()
.contains(
"Cats Effect global runtime already initialized; custom configurations will be ignored"))
assert(!h.stderr().contains("boom"))
}

"reset global runtime on shutdown" in {
test(s"IOApp (${platform.id}) - reset global runtime on shutdown") {
val h = platform("GlobalShutdown", List.empty)
h.awaitStatus() mustEqual 0
h.stderr() must not contain
"Cats Effect global runtime already initialized; custom configurations will be ignored"
h.stderr() must not(contain("boom"))
assertEquals(h.awaitStatus(), 0)
assert(
!h.stderr()
.contains(
"Cats Effect global runtime already initialized; custom configurations will be ignored"))
assert(!h.stderr().contains("boom"))
}

// TODO reenable this test (#3919)
"warn on cpu starvation" in skipped {
test(s"IOApp (${platform.id}) - warn on cpu starvation".ignore) {
val h = platform("CpuStarvation", List.empty)
h.awaitStatus()
val err = h.stderr()
err must not(contain("[WARNING] Failed to register Cats Effect CPU"))
err must contain("[WARNING] Your app's responsiveness")
assert(!err.contains("[WARNING] Failed to register Cats Effect CPU"))
assert(err.contains("[WARNING] Your app's responsiveness"))
// we use a regex because time has too many corner cases - a test run at just the wrong
// moment on new year's eve, etc
err must beMatching(
// (?s) allows matching across line breaks
"""(?s)^\d{4}-[01]\d-[0-3]\dT[012]\d:[0-6]\d:[0-6]\d(?:\.\d{1,3})?Z \[WARNING\] Your app's responsiveness.*"""
)
assert(
err.matches(
// (?s) allows matching across line breaks
"""(?s)^\d{4}-[01]\d-[0-3]\dT[012]\d:[0-6]\d:[0-6]\d(?:\.\d{1,3})?Z \[WARNING\] Your app's responsiveness.*"""
))
}

"custom runtime installed as global" in {
test(s"IOApp (${platform.id}) - custom runtime installed as global") {
val h = platform("CustomRuntime", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}

if (platform != Native) {
"abort awaiting shutdown hooks" in {
test(s"IOApp (${platform.id}) - abort awaiting shutdown hooks") {
val h = platform("ShutdownHookImmediateTimeout", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}
()
}
Expand All @@ -266,7 +271,7 @@ class IOAppSpec extends Specification {
// The jvm cannot gracefully terminate processes on Windows, so this
// test cannot be carried out properly. Same for testing IOApp in sbt.

"run finalizers on TERM" in {
test(s"IOApp (${platform.id}) - run finalizers on TERM") {
import _root_.java.io.{BufferedReader, FileReader}

// we have to resort to this convoluted approach because Process#destroy kills listeners before killing the process
Expand All @@ -293,32 +298,32 @@ class IOAppSpec extends Specification {
) // give thread scheduling just a sec to catch up and get us into the latch.await()

h.term()
h.awaitStatus() mustEqual 143
assertEquals(h.awaitStatus(), 143)

i = 0
while (readTest() == null && i < 100) {
i += 1
}
readTest() must contain("canceled")
assert(readTest().contains("canceled"))
}
} else ()

"exit on fatal error without IOApp" in {
test(s"IOApp (${platform.id}) - exit on fatal error without IOApp") {
val h = platform("FatalErrorRaw", List.empty)
h.awaitStatus()
h.stdout() must not(contain("sadness"))
h.stderr() must not(contain("Promise already completed"))
assert(!h.stdout().contains("sadness"))
assert(!h.stderr().contains("Promise already completed"))
}

"exit on canceled" in {
test(s"IOApp (${platform.id}) - exit on canceled") {
val h = platform("Canceled", List.empty)
h.awaitStatus() mustEqual 1
assertEquals(h.awaitStatus(), 1)
}

if (!isJava8 && !isWindows && platform != Native) {
// JDK 8 does not have free signals for live fiber snapshots
// cannot observe signals sent to process termination on Windows
"live fiber snapshot" in {
test(s"IOApp (${platform.id}) - live fiber snapshot") {
val h = platform("LiveFiberSnapshot", List.empty)

// wait for the application to fully start before trying to send the signal
Expand All @@ -327,60 +332,56 @@ class IOAppSpec extends Specification {
}

val pid = h.pid()
pid must beSome
assert(pid.isDefined)
pid.foreach(platform.sendSignal)
h.awaitStatus()
val stderr = h.stderr()
stderr must contain("cats.effect.IOFiber")
assert(stderr.contains("cats.effect.IOFiber"))
}
()
}

if (platform == JVM) {
"shutdown on worker thread interruption" in {
test(s"IOApp (${platform.id}) - shutdown on worker thread interruption") {
val h = platform("WorkerThreadInterrupt", List.empty)
h.awaitStatus() mustEqual 1
h.stderr() must contain("java.lang.InterruptedException")
ok
assertEquals(h.awaitStatus(), 1)
assert(h.stderr().contains("java.lang.InterruptedException"))
}

"support main thread evaluation" in {
test(s"IOApp (${platform.id}) - support main thread evaluation") {
val h = platform("EvalOnMainThread", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}

"use configurable reportFailure for MainThread" in {
test(s"IOApp (${platform.id}) - use configurable reportFailure for MainThread") {
val h = platform("MainThreadReportFailure", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}

"warn on blocked threads" in {
test(s"IOApp (${platform.id}) - warn on blocked threads") {
val h = platform("BlockedThreads", List.empty)
h.awaitStatus()
val err = h.stderr()
err must contain(
"[WARNING] A Cats Effect worker thread was detected to be in a blocked state")
assert(
err.contains(
"[WARNING] A Cats Effect worker thread was detected to be in a blocked state"))
}

"shut down WSTP on fatal error without IOApp" in {
test(s"IOApp (${platform.id}) - shut down WSTP on fatal error without IOApp") {
val h = platform("FatalErrorShutsDownRt", List.empty)
h.awaitStatus()
h.stdout() must not(contain("sadness"))
h.stdout() must contain("done")
assert(!h.stdout().contains("sadness"))
assert(h.stdout().contains("done"))
}

()
}

if (platform == Node) {
"gracefully ignore undefined process.exit" in {
test(s"IOApp (${platform.id}) - gracefully ignore undefined process.exit") {
val h = platform("UndefinedProcessExit", List.empty)
h.awaitStatus() mustEqual 0
assertEquals(h.awaitStatus(), 0)
}
()
}

"make specs2 happy" in ok
}

trait Handle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import cats.effect.kernel.testkit.TimeT
import cats.effect.kernel.testkit.pure._
import cats.syntax.all._

import scala.concurrent.TimeoutException
import scala.concurrent.duration._
import munit.FunSuite

import scala.concurrent.TimeoutException
import munit.FunSuite

class GenTemporalSuite extends FunSuite {
outer =>
Expand Down
1 change: 1 addition & 0 deletions std/shared/src/test/scala/cats/effect/std/SyntaxSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package cats.effect.std

import cats.effect.kernel.{Async, Concurrent, Deferred, GenConcurrent, Ref, Sync}

import munit.FunSuite

class SyntaxSpec extends FunSuite {
Expand Down
Loading

0 comments on commit e671306

Please sign in to comment.