diff --git a/modules/core/src/test/scala-2/doobie/util/QueryLogSuitePlatform.scala b/modules/core/src/test/scala-2/doobie/util/QueryLogSuitePlatform.scala index 539e82b4e..88bad441d 100644 --- a/modules/core/src/test/scala-2/doobie/util/QueryLogSuitePlatform.scala +++ b/modules/core/src/test/scala-2/doobie/util/QueryLogSuitePlatform.scala @@ -12,7 +12,7 @@ trait QueryLogSuitePlatform { self: QueryLogSuite => test("[Query] n-arg success") { val Sql = "select 1 where ? = ?" val Arg = 1 :: 1 :: HNil - eventForUniqueQuery(Sql, Arg) match { + eventForUniqueQuery(Sql, Arg).map { case Success(Sql, Parameters.NonBatch(List(1, 1)), _, _, _) => () case a => fail(s"no match: $a") } @@ -21,7 +21,7 @@ trait QueryLogSuitePlatform { self: QueryLogSuite => test("[Query] n-arg processing failure") { val Sql = "select 1 where ? = ?" val Arg = 1 :: 2 :: HNil - eventForUniqueQuery(Sql, Arg) match { + eventForUniqueQuery(Sql, Arg).map { case ProcessingFailure(Sql, Parameters.NonBatch(List(1, 2)), _, _, _, _) => () case a => fail(s"no match: $a") } diff --git a/modules/core/src/test/scala-3/doobie/util/QueryLogSuitePlatform.scala b/modules/core/src/test/scala-3/doobie/util/QueryLogSuitePlatform.scala index 81aa8532c..d90df27f1 100644 --- a/modules/core/src/test/scala-3/doobie/util/QueryLogSuitePlatform.scala +++ b/modules/core/src/test/scala-3/doobie/util/QueryLogSuitePlatform.scala @@ -11,7 +11,7 @@ trait QueryLogSuitePlatform { self: QueryLogSuite => test("[Query] n-arg success") { val Sql = "select 1 where ? = ?" val Arg = 1 *: 1 *: EmptyTuple - eventForUniqueQuery(Sql, Arg) match { + eventForUniqueQuery(Sql, Arg).map { case Success(Sql, Parameters.NonBatch(List(1, 1)), _, _, _) => () case a => fail(s"no match: $a") } @@ -20,7 +20,7 @@ trait QueryLogSuitePlatform { self: QueryLogSuite => test("[Query] n-arg processing failure") { val Sql = "select 1 where ? = ?" val Arg = 1 *: 2 *: EmptyTuple - eventForUniqueQuery(Sql, Arg) match { + eventForUniqueQuery(Sql, Arg).map { case ProcessingFailure(Sql, Parameters.NonBatch(List(1, 2)), _, _, _, _) => () case a => fail(s"no match: $a") } diff --git a/modules/core/src/test/scala/doobie/issue/262.scala b/modules/core/src/test/scala/doobie/issue/262.scala index 579cac1ab..599dfe53b 100644 --- a/modules/core/src/test/scala/doobie/issue/262.scala +++ b/modules/core/src/test/scala/doobie/issue/262.scala @@ -5,11 +5,11 @@ package doobie.issue import cats.effect.IO -import doobie.*, doobie.implicits.* +import doobie.* +import doobie.implicits.* +import munit.CatsEffectSuite -class `262` extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class `262` extends CatsEffectSuite { // an interpreter that returns null when we ask for statement metadata object Interp extends KleisliInterpreter[IO](LogHandler.noop) { @@ -34,7 +34,7 @@ class `262` extends munit.FunSuite { test("getColumnJdbcMeta should handle null metadata") { val prog = HC.prepareStatementPrimitive("select 1")(HPS.getColumnJdbcMeta) - assertEquals(prog.transact(xa).unsafeRunSync(), Nil) + prog.transact(xa).assertEquals(Nil) } } diff --git a/modules/core/src/test/scala/doobie/util/CatchSqlSuite.scala b/modules/core/src/test/scala/doobie/util/CatchSqlSuite.scala index 55c8f6a58..f4e408ab7 100644 --- a/modules/core/src/test/scala/doobie/util/CatchSqlSuite.scala +++ b/modules/core/src/test/scala/doobie/util/CatchSqlSuite.scala @@ -4,171 +4,158 @@ package doobie.util -import cats.effect.{IO} -import doobie.*, doobie.implicits.* -import java.sql.SQLException +import cats.effect.{IO, Ref} +import doobie.* +import doobie.implicits.* +import munit.CatsEffectSuite -class CatchSqlSuite extends munit.FunSuite { +import java.sql.SQLException - import cats.effect.unsafe.implicits.global +class CatchSqlSuite extends CatsEffectSuite { val SQLSTATE_FOO = SqlState("Foo") val SQLSTATE_BAR = SqlState("Bar") test("attemptSql should do nothing on success") { - assertEquals(IO.delay(3).attemptSql.unsafeRunSync(), Right(3)) + IO.delay(3).attemptSql.assertEquals(Right(3)) } test("attemptSql should catch SQLException") { val e = new SQLException - assertEquals(IO.raiseError(e).attemptSql.unsafeRunSync(), Left(e)) + IO.raiseError(e).attemptSql.assertEquals(Left(e)) } test("attemptSql should ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError(e).attemptSql.unsafeRunSync() - } + IO.raiseError(e).attemptSql.intercept[IllegalArgumentException] } test("attemptSqlState shuold do nothing on success") { - assertEquals(IO.delay(3).attemptSqlState.unsafeRunSync(), Right(3)) + IO.delay(3).attemptSqlState.assertEquals(Right(3)) } test("attemptSqlState shuold catch SQLException") { val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals(IO.raiseError(e).attemptSqlState.unsafeRunSync(), Left(SQLSTATE_FOO)) + IO.raiseError(e).attemptSqlState.assertEquals(Left(SQLSTATE_FOO)) } test("attemptSqlState shuold ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError(e).attemptSqlState.unsafeRunSync() - } + IO.raiseError(e).attemptSqlState.intercept[IllegalArgumentException] } test("attemptSomeSqlState should do nothing on success") { - assertEquals( - IO.delay(3).attemptSomeSqlState { - case SQLSTATE_FOO => 42 - case SQLSTATE_BAR => 66 - }.unsafeRunSync(), - Right(3)) + IO.delay(3).attemptSomeSqlState { + case SQLSTATE_FOO => 42 + case SQLSTATE_BAR => 66 + }.assertEquals(Right(3)) } test("attemptSomeSqlState should catch SQLException with matching state (1)") { val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals( - IO.raiseError(e).attemptSomeSqlState { - case SQLSTATE_FOO => 42 - case SQLSTATE_BAR => 66 - }.unsafeRunSync(), - Left(42)) + IO.raiseError(e).attemptSomeSqlState { + case SQLSTATE_FOO => 42 + case SQLSTATE_BAR => 66 + }.assertEquals(Left(42)) } test("attemptSomeSqlState should catch SQLException with matching state (2)") { val e = new SQLException("", SQLSTATE_BAR.value) - assertEquals( - IO.raiseError(e).attemptSomeSqlState { - case SQLSTATE_FOO => 42 - case SQLSTATE_BAR => 66 - }.unsafeRunSync(), - Left(66)) + IO.raiseError(e).attemptSomeSqlState { + case SQLSTATE_FOO => 42 + case SQLSTATE_BAR => 66 + }.assertEquals(Left(66)) } test("attemptSomeSqlState should ignore SQLException with non-matching state") { val e = new SQLException("", SQLSTATE_BAR.value) - intercept[SQLException] { - IO.raiseError(e).attemptSomeSqlState { - case SQLSTATE_FOO => 42 - }.unsafeRunSync() - } + IO.raiseError(e).attemptSomeSqlState { + case SQLSTATE_FOO => 42 + }.intercept[SQLException] } test("attemptSomeSqlState should ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError(e).attemptSomeSqlState { - case SQLSTATE_FOO => 42 - }.unsafeRunSync() - } + IO.raiseError(e).attemptSomeSqlState { + case SQLSTATE_FOO => 42 + }.intercept[IllegalArgumentException] } lazy val rescue = IO.delay(4) test("exceptSql should do nothing on success") { - assertEquals(IO.delay(3).exceptSql(_ => rescue).unsafeRunSync(), 3) + IO.delay(3).exceptSql(_ => rescue).assertEquals(3) } test("exceptSql should catch SQLException") { val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals(IO.raiseError[Int](e).exceptSql(_ => rescue).unsafeRunSync(), 4) + IO.raiseError[Int](e).exceptSql(_ => rescue).assertEquals(4) } test("exceptSql should ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError[Int](e).exceptSql(_ => rescue).unsafeRunSync() - } + IO.raiseError[Int](e).exceptSql(_ => rescue).intercept[IllegalArgumentException] } test("exceptSqlState should do nothing on success") { - assertEquals(IO.delay(3).exceptSqlState(_ => rescue).unsafeRunSync(), 3) + IO.delay(3).exceptSqlState(_ => rescue).assertEquals(3) } test("exceptSqlState should catch SQLException") { val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals(IO.raiseError[Int](e).exceptSqlState(_ => rescue).unsafeRunSync(), 4) + IO.raiseError[Int](e).exceptSqlState(_ => rescue).assertEquals(4) } test("exceptSqlState should ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError[Int](e).exceptSqlState(_ => rescue).unsafeRunSync() - } + IO.raiseError[Int](e).exceptSqlState(_ => rescue).intercept[IllegalArgumentException] } test("exceptSomeSqlState should do nothing on success") { - assertEquals(IO.delay(3).exceptSomeSqlState { case _ => rescue }.unsafeRunSync(), 3) + IO.delay(3).exceptSomeSqlState { case _ => rescue }.assertEquals(3) } test("exceptSomeSqlState should catch SQLException with some state") { val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals(IO.raiseError[Int](e).exceptSomeSqlState { case SQLSTATE_FOO => rescue }.unsafeRunSync(), 4) + IO.raiseError[Int](e).exceptSomeSqlState { case SQLSTATE_FOO => rescue }.assertEquals(4) } test("exceptSomeSqlState should ignore SQLException with other state") { val e = new SQLException("", SQLSTATE_FOO.value) - intercept[SQLException] { - IO.raiseError[Int](e).exceptSomeSqlState { case SQLSTATE_BAR => rescue }.unsafeRunSync() - } + IO.raiseError[Int](e).exceptSomeSqlState { case SQLSTATE_BAR => rescue }.intercept[SQLException] } test("exceptSomeSqlState should ignore non-SQLException") { val e = new IllegalArgumentException - intercept[IllegalArgumentException] { - IO.raiseError[Int](e).exceptSomeSqlState { case _ => rescue }.unsafeRunSync() - } + IO.raiseError[Int](e).exceptSomeSqlState { case _ => rescue }.intercept[IllegalArgumentException] } test("onSqlException should do nothing on success") { - var a = 1 - val _ = IO.delay(3).onSqlException(IO.delay(a += 1)).attempt.unsafeRunSync() - assertEquals(a, 1) + for { + a <- Ref.of[IO, Int](1) + _ <- IO.delay(3).onSqlException(IO.delay(a.set(2))).attempt + b <- a.get + } yield assertEquals(b, 1) } test("onSqlException should perform its effect on SQLException") { - var a = 1 - val e = new SQLException("", SQLSTATE_FOO.value) - assertEquals(IO.raiseError[Int](e).onSqlException(IO.delay(a += 1)).attempt.unsafeRunSync(), Left(e)) - assertEquals(a, 2) + for { + a <- Ref.of[IO, Int](1) + e = new SQLException("foo") + _ <- IO.raiseError[Int](e).onSqlException(a.set(2)).attempt.assertEquals(Left(e)) + b <- a.get + } yield assertEquals(b, 2) } test("onSqlException should ignore its effect on non-SQLException") { - var a = 1 - val e = new IllegalArgumentException - assertEquals(IO.raiseError[Int](e).onSqlException(IO.delay(a += 1)).attempt.unsafeRunSync(), Left(e)) - assertEquals(a, 1) + for { + a <- Ref.of[IO, Int](1) + e = new RuntimeException("foo") + _ <- IO.raiseError[Int](e).onSqlException(a.set(2)).attempt.assertEquals(Left(e)) + b <- a.get + } yield { + assertEquals(b, 1) + } } } diff --git a/modules/core/src/test/scala/doobie/util/ConnectionIOSuite.scala b/modules/core/src/test/scala/doobie/util/ConnectionIOSuite.scala index a824f38e4..31f70f0f5 100644 --- a/modules/core/src/test/scala/doobie/util/ConnectionIOSuite.scala +++ b/modules/core/src/test/scala/doobie/util/ConnectionIOSuite.scala @@ -11,9 +11,7 @@ import cats.kernel.Monoid import doobie.* import doobie.implicits.* -class ConnectionIOSuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class ConnectionIOSuite extends munit.CatsEffectSuite { val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -25,11 +23,11 @@ class ConnectionIOSuite extends munit.FunSuite { test("Semigroup ConnectionIO") { val prg = Applicative[ConnectionIO].pure(List(1, 2, 3)) `combine` Applicative[ConnectionIO].pure(List(4, 5, 6)) - assertEquals(prg.transact(xa).unsafeRunSync(), List(1, 2, 3, 4, 5, 6)) + prg.transact(xa).assertEquals(List(1, 2, 3, 4, 5, 6)) } test("Monoid ConnectionIO") { - assertEquals(Monoid[ConnectionIO[List[Int]]].empty.transact(xa).unsafeRunSync(), Nil) + Monoid[ConnectionIO[List[Int]]].empty.transact(xa).assertEquals(Nil) } } diff --git a/modules/core/src/test/scala/doobie/util/FragmentSuite.scala b/modules/core/src/test/scala/doobie/util/FragmentSuite.scala index 1a71f3621..deba20877 100644 --- a/modules/core/src/test/scala/doobie/util/FragmentSuite.scala +++ b/modules/core/src/test/scala/doobie/util/FragmentSuite.scala @@ -9,10 +9,9 @@ import cats.effect.IO import doobie.* import doobie.implicits.* import doobie.testutils.VoidExtensions +import munit.CatsEffectSuite -class FragmentSuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class FragmentSuite extends CatsEffectSuite { val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -51,22 +50,22 @@ class FragmentSuite extends munit.FunSuite { test("Fragment must maintain parameter indexing (in-order)") { val s = fr"select" ++ List(fra, frb, frc).intercalate(fr",") - assertEquals(s.query[(Int, String, Boolean)].unique.transact(xa).unsafeRunSync(), ((a, b, c))) + s.query[(Int, String, Boolean)].unique.transact(xa).assertEquals((a, b, c)) } test("Fragment must maintain parameter indexing (out-of-order)") { val s = fr"select" ++ List(frb, frc, fra).intercalate(fr",") - assertEquals(s.query[(String, Boolean, Int)].unique.transact(xa).unsafeRunSync(), ((b, c, a))) + s.query[(String, Boolean, Int)].unique.transact(xa).assertEquals((b, c, a)) } test("Fragment must maintain associativity (left)") { val s = fr"select" ++ List(fra, fr",", frb, fr",", frc).foldLeft(Fragment.empty)(_ ++ _) - assertEquals(s.query[(Int, String, Boolean)].unique.transact(xa).unsafeRunSync(), ((a, b, c))) + s.query[(Int, String, Boolean)].unique.transact(xa).assertEquals((a, b, c)) } test("Fragment must maintain associativity (right)") { val s = fr"select" ++ List(fra, fr",", frb, fr",", frc).foldRight(Fragment.empty)(_ ++ _) - assertEquals(s.query[(Int, String, Boolean)].unique.transact(xa).unsafeRunSync(), ((a, b, c))) + s.query[(Int, String, Boolean)].unique.transact(xa).assertEquals((a, b, c)) } test("Fragment must Add a trailing space when constructed with .const") { @@ -112,7 +111,7 @@ class FragmentSuite extends munit.FunSuite { fr0"SELECT 1 WHERE 1 IN (" ++ List.fill(STACK_UNSAFE_SIZE)(1).foldLeft(Fragment.empty)((f, n) => f ++ fr"$n,") ++ fr0"1)" - assertEquals(frag.query[Int].unique.transact(xa).unsafeRunSync(), 1) + frag.query[Int].unique.transact(xa).assertEquals(1) } test("Fragment must be stacksafe (right-associative)") { @@ -120,7 +119,7 @@ class FragmentSuite extends munit.FunSuite { fr0"SELECT 1 WHERE 1 IN (" ++ List.fill(STACK_UNSAFE_SIZE)(1).foldRight(Fragment.empty)((n, f) => f ++ fr"$n,") ++ fr0"1)" - assertEquals(frag.query[Int].unique.transact(xa).unsafeRunSync(), 1) + frag.query[Int].unique.transact(xa).assertEquals(1) } } diff --git a/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala b/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala index e3f2626b6..009ffe3b6 100644 --- a/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala +++ b/modules/core/src/test/scala/doobie/util/FragmentsSuite.scala @@ -9,9 +9,8 @@ import cats.effect.IO import doobie.* import doobie.implicits.* -class FragmentsSuite extends munit.FunSuite { +class FragmentsSuite extends munit.CatsEffectSuite { import Fragments.* - import cats.effect.unsafe.implicits.global val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -389,13 +388,13 @@ class FragmentsSuite extends munit.FunSuite { test("values (1)") { val c = Contact(Person("Bob", 42), Some("addr")) val f = sql"select" ++ Fragments.values(c) - assertEquals(f.query[Contact].unique.transact(xa).unsafeRunSync(), c) + f.query[Contact].unique.transact(xa).assertEquals(c) } test("values (2)") { val c = Contact(Person("Bob", 42), None) val f = sql"select" ++ Fragments.values(c) - assertEquals(f.query[Contact].unique.transact(xa).unsafeRunSync(), c) + f.query[Contact].unique.transact(xa).assertEquals(c) } } diff --git a/modules/core/src/test/scala/doobie/util/GetSuite.scala b/modules/core/src/test/scala/doobie/util/GetSuite.scala index 35b985b44..003e69668 100644 --- a/modules/core/src/test/scala/doobie/util/GetSuite.scala +++ b/modules/core/src/test/scala/doobie/util/GetSuite.scala @@ -9,7 +9,7 @@ import doobie.enumerated.JdbcType import doobie.testutils.VoidExtensions import doobie.util.transactor.Transactor -class GetSuite extends munit.FunSuite { +class GetSuite extends munit.CatsEffectSuite { case class X(x: Int) case class Q(x: String) @@ -27,8 +27,7 @@ class GetSuite extends munit.FunSuite { final case class Foo(s: String) final case class Bar(n: Int) -class GetDBSuite extends munit.FunSuite { - import cats.effect.unsafe.implicits.global +class GetDBSuite extends munit.CatsEffectSuite { import doobie.syntax.all.* lazy val xa = Transactor.fromDriverManager[IO]( @@ -45,23 +44,22 @@ class GetDBSuite extends munit.FunSuite { implicit val barMeta: Get[Bar] = Get[Int].temap(n => if (n == 0) Left("cannot be 0") else Right(Bar(n))) test("Get should not allow map to observe null on the read side (AnyRef)") { - val x = sql"select null".query[Option[Foo]].unique.transact(xa).unsafeRunSync() - assertEquals(x, None) + sql"select null".query[Option[Foo]].unique.transact(xa).assertEquals(None) } test("Get should read non-null value (AnyRef)") { - val x = sql"select 'abc'".query[Foo].unique.transact(xa).unsafeRunSync() - assertEquals(x, Foo("ABC")) + sql"select 'abc'".query[Foo].unique.transact(xa).assertEquals(Foo("ABC")) } test("Get should error when reading a NULL into an unlifted Scala type (AnyRef)") { - def x = sql"select null".query[Foo].unique.transact(xa).attempt.unsafeRunSync() - assertEquals(x, Left(doobie.util.invariant.NonNullableColumnRead(1, JdbcType.Char))) + sql"select null".query[Foo].unique.transact(xa).attempt.assertEquals(Left( + doobie.util.invariant.NonNullableColumnRead(1, JdbcType.Char))) } test("Get should error when reading an incorrect value") { - def x = sql"select 0".query[Bar].unique.transact(xa).attempt.unsafeRunSync() - assertEquals(x, Left(doobie.util.invariant.InvalidValue[Int, Bar](0, "cannot be 0"))) + sql"select 0".query[Bar].unique.transact(xa).attempt.assertEquals(Left(doobie.util.invariant.InvalidValue[Int, Bar]( + 0, + "cannot be 0"))) } } diff --git a/modules/core/src/test/scala/doobie/util/ProcessSuite.scala b/modules/core/src/test/scala/doobie/util/ProcessSuite.scala index a750afae4..abad32c67 100644 --- a/modules/core/src/test/scala/doobie/util/ProcessSuite.scala +++ b/modules/core/src/test/scala/doobie/util/ProcessSuite.scala @@ -7,16 +7,16 @@ package util import cats.effect.IO import doobie.util.stream.repeatEvalChunks -import org.scalacheck.Prop.forAll +import munit.CatsEffectAssertions.MUnitCatsAssertionsForIOOps +import org.scalacheck.effect.PropF + import scala.Predef.* import scala.util.Random class ProcessSuite extends munit.ScalaCheckSuite { - import cats.effect.unsafe.implicits.global - test("repeatEvalChunks must yield the same result irrespective of chunk size") { - forAll { (n0: Int) => + PropF.forAllF { (n0: Int) => val dataSize = 1000 val chunkSize = (n0 % dataSize).abs max 1 val data = Seq.fill(dataSize)(Random.nextInt()) @@ -28,8 +28,7 @@ class ProcessSuite extends munit.ScalaCheckSuite { h } } - val result = repeatEvalChunks(fa).compile.toVector.unsafeRunSync() - assertEquals(result, data.toVector) + repeatEvalChunks(fa).compile.toVector.assertEquals(data.toVector) } } diff --git a/modules/core/src/test/scala/doobie/util/QueryLogSuite.scala b/modules/core/src/test/scala/doobie/util/QueryLogSuite.scala index fe95d9b02..142bc2e67 100644 --- a/modules/core/src/test/scala/doobie/util/QueryLogSuite.scala +++ b/modules/core/src/test/scala/doobie/util/QueryLogSuite.scala @@ -14,9 +14,7 @@ import doobie.util.query.Query import doobie.util.transactor.Transactor import doobie.util.update.Update -class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { - - import cats.effect.unsafe.implicits.global +class QueryLogSuite extends munit.CatsEffectSuite with QueryLogSuitePlatform { val logEventRef: Ref[IO, LogEvent] = Ref.of[IO, LogEvent](null).unsafeRunSync() @@ -29,45 +27,45 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { logHandler = Some(ev => logEventRef.set(ev)) ) - def eventForCIO[A](cio: ConnectionIO[A]): LogEvent = { + def eventForCIO[A](cio: ConnectionIO[A]): IO[LogEvent] = { for { _ <- logEventRef.set(null) _ <- cio.transact(xa).attempt log <- logEventRef.get } yield log - }.unsafeRunSync() + } - def successEventForCIO[A](cio: ConnectionIO[A]): Success = - eventForCIO(cio) match { + def successEventForCIO[A](cio: ConnectionIO[A]): IO[Success] = + eventForCIO(cio).map { case s: Success => s case other => fail(s"Expected Success log event but got $other") } - def execFailureEventForCIO[A](cio: ConnectionIO[A]): ExecFailure = - eventForCIO(cio) match { + def execFailureEventForCIO[A](cio: ConnectionIO[A]): IO[ExecFailure] = + eventForCIO(cio).map { case ev: ExecFailure => ev case other => fail(s"Expected ExecFailure log event but got $other") } - def processFailureEventForCIO[A](cio: ConnectionIO[A]): ProcessingFailure = - eventForCIO(cio) match { + def processFailureEventForCIO[A](cio: ConnectionIO[A]): IO[ProcessingFailure] = + eventForCIO(cio).map { case ev: ProcessingFailure => ev case other => fail(s"Expected ProcessingFailure log event but got $other") } - def eventForUniqueQuery[A: Write](sql: String, arg: A): LogEvent = { + def eventForUniqueQuery[A: Write](sql: String, arg: A): IO[LogEvent] = { eventForCIO(Query[A, Unit](sql, None).unique(arg)) } - def eventForUpdate[A: Write](sql: String, arg: A): LogEvent = { + def eventForUpdate[A: Write](sql: String, arg: A): IO[LogEvent] = { val cio = sql"create table if not exists foo (bar integer)".update.run *> Update[A](sql, None).run(arg) eventForCIO(cio) } test("simple") { - val q = sql"select 1, 2".query[(Int, Int)] - val succEvents = List( + val q: query.Query0[(Int, Int)] = sql"select 1, 2".query[(Int, Int)] + val succEvents: List[IO[Success]] = List( successEventForCIO(q.to[List]), successEventForCIO(q.toMap[Int, Int]), successEventForCIO(q.accumulate[List]), @@ -75,11 +73,12 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { successEventForCIO(q.option), successEventForCIO(q.nel) ) - succEvents.foreach { succ => - assertEquals(succ.sql, "select 1, 2") - assertEquals(succ.params, NonBatch(Nil)) - assertEquals(succ.label, "unlabeled") - } + + succEvents.map { succ => + assertIO(succ.map(_.sql), "select 1, 2") *> + assertIO(succ.map(_.params), NonBatch(Nil)) *> + assertIO(succ.map(_.label), "unlabeled") + }.sequence_ } test("With params and label") { @@ -92,13 +91,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { successEventForCIO(q.option), successEventForCIO(q.nel) ) - succEvents.foreach { succ => - assertEquals(succ.sql, "select ?, ?") - assertEquals(succ.params, NonBatch(List(1, "2"))) - assertEquals(succ.label, "mylabel") - assert(succ.exec.toNanos > 0L) - assert(succ.processing.toNanos > 0L) - } + succEvents.map { succ => + assertIO(succ.map(_.sql), "select ?, ?") *> + assertIO(succ.map(_.params), NonBatch(List(1, "2"))) *> + assertIO(succ.map(_.label), "mylabel") *> + assertIOBoolean(succ.map(_.exec.toNanos > 0L)) *> + assertIOBoolean(succ.map(_.processing.toNanos > 0L)) + }.sequence_ } test("execution failure (Error during PreparedStatement construction)") { @@ -110,13 +109,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { execFailureEventForCIO(q.unique), execFailureEventForCIO(q.option), execFailureEventForCIO(q.nel) - ).foreach { ev => - assertEquals(ev.sql, "select bad_column") - assertEquals(ev.params, Parameters.nonBatchEmpty) - assertEquals(ev.label, "unlabeled") - assertEquals(ev.exec.toNanos, 0L) - assert(ev.failure.getMessage.contains("not found")) - } + ).map { ev => + assertIO(ev.map(_.sql), "select bad_column") *> + assertIO(ev.map(_.params), Parameters.nonBatchEmpty) *> + assertIO(ev.map(_.label), "unlabeled") *> + assertIO(ev.map(_.exec.toNanos), 0L) *> + assertIOBoolean(ev.map(_.failure.getMessage.contains("not found"))) + }.sequence } test("execution failure") { @@ -128,13 +127,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { execFailureEventForCIO(q.unique("not_int")), execFailureEventForCIO(q.option("not_int")), execFailureEventForCIO(q.nel("not_int")) - ).foreach { ev => - assertEquals(ev.sql, "select ? :: Int") - assertEquals(ev.params, NonBatch(List("not_int"))) - assertEquals(ev.label, "unlabeled") - assert(ev.exec.toNanos > 0L) - assert(ev.failure.getMessage.contains("Data conversion error")) - } + ).map { ev => + assertIO(ev.map(_.sql), "select ? :: Int") *> + assertIO(ev.map(_.params), NonBatch(List("not_int"))) *> + assertIO(ev.map(_.label), "unlabeled") *> + assertIOBoolean(ev.map(_.exec.toNanos > 0L)) *> + assertIOBoolean(ev.map(_.failure.getMessage.contains("Data conversion error"))) + }.sequence } test("processing failure") { @@ -146,13 +145,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { processFailureEventForCIO(q.unique), processFailureEventForCIO(q.option), processFailureEventForCIO(q.nel) - ).foreach { ev => - assertEquals(ev.sql, "select 'not_int'") - assertEquals(ev.params, Parameters.nonBatchEmpty) - assertEquals(ev.label, "unlabeled") - assert(ev.exec.toNanos > 0L) - assert(ev.failure.getMessage.contains("Data conversion error")) - } + ).map { ev => + assertIO(ev.map(_.sql), "select 'not_int'") *> + assertIO(ev.map(_.params), Parameters.nonBatchEmpty) *> + assertIO(ev.map(_.label), "unlabeled") *> + assertIOBoolean(ev.map(_.exec.toNanos > 0L)) *> + assertIOBoolean(ev.map(_.failure.getMessage.contains("Data conversion error"))) + }.sequence_ } test("stream") { @@ -160,11 +159,11 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { val succ = successEventForCIO( Query[Unit, Int](sql).stream(()).compile.toList ) - assertEquals(succ.sql, sql) - assertEquals(succ.params, NonBatch(Nil)) - assertEquals(succ.label, "unlabeled") - assert(succ.exec.toNanos > 0L) - assertEquals(succ.processing.toNanos, 0L) + assertIO(succ.map(_.sql), sql) *> + assertIO(succ.map(_.params), NonBatch(Nil)) *> + assertIO(succ.map(_.label), "unlabeled") *> + assertIOBoolean(succ.map(_.exec.toNanos > 0L)) *> + assertIO(succ.map(_.processing.toNanos), 0L) } test("streamWithChunkSize") { @@ -172,11 +171,11 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { val succ = successEventForCIO( Query[Unit, Int](sql).streamWithChunkSize((), 5).compile.toList ) - assertEquals(succ.sql, sql) - assertEquals(succ.params, NonBatch(Nil)) - assertEquals(succ.label, "unlabeled") - assert(succ.exec.toNanos > 0L) - assertEquals(succ.processing.toNanos, 0L) + assertIO(succ.map(_.sql), sql) *> + assertIO(succ.map(_.params), NonBatch(Nil)) *> + assertIO(succ.map(_.label), "unlabeled") *> + assertIOBoolean(succ.map(_.exec.toNanos > 0L)) *> + assertIO(succ.map(_.processing.toNanos), 0L) } test("stream: Log ExecFailure on failed PreparedStatement construction") { @@ -184,13 +183,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { List( execFailureEventForCIO(q0.stream.compile.toList), execFailureEventForCIO(q0.streamWithChunkSize(1).compile.toList) - ).foreach { ev => - assertEquals(ev.sql, "select bad_column") - assertEquals(ev.params, Parameters.nonBatchEmpty) - assertEquals(ev.label, "unlabeled") - assertEquals(ev.exec.toNanos, 0L) - assert(ev.failure.getMessage.contains("not found")) - } + ).map { ev => + assertIO(ev.map(_.sql), "select bad_column") *> + assertIO(ev.map(_.params), Parameters.nonBatchEmpty) *> + assertIO(ev.map(_.label), "unlabeled") *> + assertIO(ev.map(_.exec.toNanos), 0L) *> + assertIOBoolean(ev.map(_.failure.getMessage.contains("not found"))) + }.sequence_ } test("stream: Log ExecFailure on failed PreparedStatement execution") { @@ -198,13 +197,13 @@ class QueryLogSuite extends munit.FunSuite with QueryLogSuitePlatform { List( execFailureEventForCIO(q0.stream("not_int").compile.toList), execFailureEventForCIO(q0.streamWithChunkSize("not_int", 1).compile.toList) - ).foreach { ev => - assertEquals(ev.sql, "select ? :: Int") - assertEquals(ev.params, NonBatch(List("not_int"))) - assertEquals(ev.label, "unlabeled") - assert(ev.exec.toNanos > 0L) - assert(ev.failure.getMessage.contains("Data conversion error")) - } + ).map { ev => + assertIO(ev.map(_.sql), "select ? :: Int") *> + assertIO(ev.map(_.params), NonBatch(List("not_int"))) *> + assertIO(ev.map(_.label), "unlabeled") *> + assertIOBoolean(ev.map(_.exec.toNanos > 0L)) *> + assertIOBoolean(ev.map(_.failure.getMessage.contains("Data conversion error"))) + }.sequence_ } } diff --git a/modules/core/src/test/scala/doobie/util/QuerySuite.scala b/modules/core/src/test/scala/doobie/util/QuerySuite.scala index 242792b5c..3d42d6f82 100644 --- a/modules/core/src/test/scala/doobie/util/QuerySuite.scala +++ b/modules/core/src/test/scala/doobie/util/QuerySuite.scala @@ -8,9 +8,7 @@ import cats.effect.IO import doobie.*, doobie.implicits.* import scala.Predef.* -class QuerySuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class QuerySuite extends munit.CatsEffectSuite { val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -24,117 +22,117 @@ class QuerySuite extends munit.FunSuite { val pairQuery = Query[String, (String, Int)]("select 'xxx', 123 where ? = 'foo'", None) test("Query (non-empty) to") { - assertEquals(q.to[List]("foo").transact(xa).unsafeRunSync(), List(123)) + q.to[List]("foo").transact(xa).assertEquals(List(123)) } test("Query (non-empty) toMap") { - assertEquals(pairQuery.toMap[String, Int]("foo").transact(xa).unsafeRunSync(), Map("xxx" -> 123)) + pairQuery.toMap[String, Int]("foo").transact(xa).assertEquals(Map("xxx" -> 123)) } test("Query (non-empty) unique") { - assertEquals(q.unique("foo").transact(xa).unsafeRunSync(), 123) + q.unique("foo").transact(xa).assertEquals(123) } test("Query (non-empty) option") { - assertEquals(q.option("foo").transact(xa).unsafeRunSync(), Some(123)) + q.option("foo").transact(xa).assertEquals(Some(123)) } test("Query (non-empty) map") { - assertEquals(q.map("x" * _).to[List]("foo").transact(xa).unsafeRunSync(), List("x" * 123)) + q.map("x" * _).to[List]("foo").transact(xa).assertEquals(List("x" * 123)) } test("Query (non-empty) contramap") { - assertEquals(q.contramap[Int](n => "foo" * n).to[List](1).transact(xa).unsafeRunSync(), List(123)) + q.contramap[Int](n => "foo" * n).to[List](1).transact(xa).assertEquals(List(123)) } test("Query (empty) to") { - assertEquals(q.to[List]("bar").transact(xa).unsafeRunSync(), Nil) + q.to[List]("bar").transact(xa).assertEquals(Nil) } test("Query (empty) toMap") { - assertEquals(pairQuery.toMap[String, Int]("bar").transact(xa).unsafeRunSync(), Map.empty[String, Int]) + pairQuery.toMap[String, Int]("bar").transact(xa).assertEquals(Map.empty[String, Int]) } test("Query (empty) unique") { - assertEquals(q.unique("bar").transact(xa).attempt.unsafeRunSync(), Left(invariant.UnexpectedEnd)) + q.unique("bar").transact(xa).attempt.assertEquals(Left(invariant.UnexpectedEnd)) } test("Query (empty) option") { - assertEquals(q.option("bar").transact(xa).unsafeRunSync(), None) + q.option("bar").transact(xa).assertEquals(None) } test("Query (empty) map") { - assertEquals(q.map("x" * _).to[List]("bar").transact(xa).unsafeRunSync(), Nil) + q.map("x" * _).to[List]("bar").transact(xa).assertEquals(Nil) } test("Query (empty) contramap") { - assertEquals(q.contramap[Int](n => "bar" * n).to[List](1).transact(xa).unsafeRunSync(), Nil) + q.contramap[Int](n => "bar" * n).to[List](1).transact(xa).assertEquals(Nil) } test("Query0 from Query (non-empty) to") { - assertEquals(q.toQuery0("foo").to[List].transact(xa).unsafeRunSync(), List(123)) + q.toQuery0("foo").to[List].transact(xa).assertEquals(List(123)) } test("Query0 from Query (non-empty) toMap") { - assertEquals(pairQuery.toQuery0("foo").toMap[String, Int].transact(xa).unsafeRunSync(), Map("xxx" -> 123)) + pairQuery.toQuery0("foo").toMap[String, Int].transact(xa).assertEquals(Map("xxx" -> 123)) } test("Query0 from Query (non-empty) unique") { - assertEquals(q.toQuery0("foo").unique.transact(xa).unsafeRunSync(), 123) + q.toQuery0("foo").unique.transact(xa).assertEquals(123) } test("Query0 from Query (non-empty) option") { - assertEquals(q.toQuery0("foo").option.transact(xa).unsafeRunSync(), Some(123)) + q.toQuery0("foo").option.transact(xa).assertEquals(Some(123)) } test("Query0 from Query (non-empty) map") { - assertEquals(q.toQuery0("foo").map(_ * 2).to[List].transact(xa).unsafeRunSync(), List(246)) + q.toQuery0("foo").map(_ * 2).to[List].transact(xa).assertEquals(List(246)) } test("Query0 from Query (empty) to") { - assertEquals(q.toQuery0("bar").to[List].transact(xa).unsafeRunSync(), Nil) + q.toQuery0("bar").to[List].transact(xa).assertEquals(Nil) } test("Query0 from Query (empty) toMap") { - assertEquals(pairQuery.toQuery0("bar").toMap[String, Int].transact(xa).unsafeRunSync(), Map.empty[String, Int]) + pairQuery.toQuery0("bar").toMap[String, Int].transact(xa).assertEquals(Map.empty[String, Int]) } test("Query0 from Query (empty) unique") { - assertEquals(q.toQuery0("bar").unique.transact(xa).attempt.unsafeRunSync(), Left(invariant.UnexpectedEnd)) + q.toQuery0("bar").unique.transact(xa).attempt.assertEquals(Left(invariant.UnexpectedEnd)) } test("Query0 from Query (empty) option") { - assertEquals(q.toQuery0("bar").option.transact(xa).unsafeRunSync(), None) + q.toQuery0("bar").option.transact(xa).assertEquals(None) } test("Query0 from Query (empty) map") { - assertEquals(q.toQuery0("bar").map(_ * 2).to[List].transact(xa).unsafeRunSync(), Nil) + q.toQuery0("bar").map(_ * 2).to[List].transact(xa).assertEquals(Nil) } val q0n = Query0[Int]("select 123 where 'foo' = 'foo'", None) val pairQ0n = Query0[(String, Int)]("select 'xxx', 123 where 'foo' = 'foo'", None) test("Query0 via constructor (non-empty) to") { - assertEquals(q0n.to[List].transact(xa).unsafeRunSync(), List(123)) + q0n.to[List].transact(xa).assertEquals(List(123)) } test("Query0 via constructor (non-empty) toMap") { - assertEquals(pairQ0n.toMap[String, Int].transact(xa).unsafeRunSync(), Map("xxx" -> 123)) + pairQ0n.toMap[String, Int].transact(xa).assertEquals(Map("xxx" -> 123)) } test("Query0 via constructor (non-empty) unique") { - assertEquals(q0n.unique.transact(xa).unsafeRunSync(), 123) + q0n.unique.transact(xa).assertEquals(123) } test("Query0 via constructor (non-empty) option") { - assertEquals(q0n.option.transact(xa).unsafeRunSync(), Some(123)) + q0n.option.transact(xa).assertEquals(Some(123)) } test("Query0 via constructor (non-empty) map") { - assertEquals(q0n.map(_ * 2).to[List].transact(xa).unsafeRunSync(), List(246)) + q0n.map(_ * 2).to[List].transact(xa).assertEquals(List(246)) } val q0e = Query0[Int]("select 123 where 'bar' = 'foo'", None) val pairQ0e = Query0[(String, Int)]("select 'xxx', 123 where 'bar' = 'foo'", None) test("Query0 via constructor (empty) to") { - assertEquals(q0e.to[List].transact(xa).unsafeRunSync(), Nil) + q0e.to[List].transact(xa).assertEquals(Nil) } test("Query0 via constructor (empty) toMap") { - assertEquals(pairQ0e.toMap[String, Int].transact(xa).unsafeRunSync(), Map.empty[String, Int]) + pairQ0e.toMap[String, Int].transact(xa).assertEquals(Map.empty[String, Int]) } test("Query0 via constructor (empty) unique") { - assertEquals(q0e.unique.transact(xa).attempt.unsafeRunSync(), Left(invariant.UnexpectedEnd)) + q0e.unique.transact(xa).attempt.assertEquals(Left(invariant.UnexpectedEnd)) } test("Query0 via constructor (empty) option") { - assertEquals(q0e.option.transact(xa).unsafeRunSync(), None) + q0e.option.transact(xa).assertEquals(None) } test("Query0 via constructor (empty) map") { - assertEquals(q0e.map(_ * 2).to[List].transact(xa).unsafeRunSync(), Nil) + q0e.map(_ * 2).to[List].transact(xa).assertEquals(Nil) } val qf = sql"select 'foo', ${1: Int}, ${Option.empty[Int]}, ${Option(42)}".query[String] // wrong! test("Query to Fragment and back") { val qfʹ = qf.toFragment.query[(String, Int, Option[Int], Option[Int])] - assertEquals(qfʹ.unique.transact(xa).unsafeRunSync(), (("foo", 1, None, Some(42)))) + qfʹ.unique.transact(xa).assertEquals(("foo", 1, None, Some(42))) } } diff --git a/modules/core/src/test/scala/doobie/util/ReadSuite.scala b/modules/core/src/test/scala/doobie/util/ReadSuite.scala index 26e6b78ce..d07841d06 100644 --- a/modules/core/src/test/scala/doobie/util/ReadSuite.scala +++ b/modules/core/src/test/scala/doobie/util/ReadSuite.scala @@ -16,9 +16,7 @@ import munit.Location import scala.annotation.nowarn -class ReadSuite extends munit.FunSuite with ReadSuitePlatform { - - import cats.effect.unsafe.implicits.global +class ReadSuite extends munit.CatsEffectSuite with ReadSuitePlatform { val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -59,55 +57,51 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { test("Semiauto derivation selects custom Read instances when available") { implicit val i0: Read[HasCustomReadWrite0] = Read.derived[HasCustomReadWrite0] - assertEquals(i0.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite0(CustomReadWrite("x_R"), "y")) - implicit val i1: Read[HasCustomReadWrite1] = Read.derived[HasCustomReadWrite1] - assertEquals(i1.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite1("x", CustomReadWrite("y_R"))) - implicit val iOpt0: Read[HasOptCustomReadWrite0] = Read.derived[HasOptCustomReadWrite0] - assertEquals(iOpt0.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite0(Some(CustomReadWrite("x_R")), "y")) - implicit val iOpt1: Read[HasOptCustomReadWrite1] = Read.derived[HasOptCustomReadWrite1] - assertEquals(iOpt1.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite1("x", Some(CustomReadWrite("y_R")))) + + IO { assertEquals(i0.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite0(CustomReadWrite("x_R"), "y")) *> + IO { assertEquals(i1.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite1("x", CustomReadWrite("y_R"))) *> + IO { assertEquals(iOpt0.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite0(Some(CustomReadWrite("x_R")), "y")) *> + IO { assertEquals(iOpt1.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite1("x", Some(CustomReadWrite("y_R")))) } test("Semiauto derivation selects custom Get instances to use for Read when available") { implicit val i0: Read[HasCustomGetPut0] = Read.derived[HasCustomGetPut0] - assertEquals(i0.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut0(CustomGetPut("x_G"), "y")) - implicit val i1: Read[HasCustomGetPut1] = Read.derived[HasCustomGetPut1] - assertEquals(i1.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut1("x", CustomGetPut("y_G"))) - implicit val iOpt0: Read[HasOptCustomGetPut0] = Read.derived[HasOptCustomGetPut0] - assertEquals(iOpt0.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut0(Some(CustomGetPut("x_G")), "y")) - implicit val iOpt1: Read[HasOptCustomGetPut1] = Read.derived[HasOptCustomGetPut1] - assertEquals(iOpt1.length, 2) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut1("x", Some(CustomGetPut("y_G")))) + + IO { assertEquals(i0.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut0(CustomGetPut("x_G"), "y")) *> + IO { assertEquals(i1.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut1("x", CustomGetPut("y_G"))) *> + IO { assertEquals(iOpt0.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut0(Some(CustomGetPut("x_G")), "y")) *> + IO { assertEquals(iOpt1.length, 2) } *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut1("x", Some(CustomGetPut("y_G")))) } test("Automatic derivation selects custom Read instances when available") { import doobie.implicits.* - insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite0(CustomReadWrite("x_R"), "y")) - insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite1("x", CustomReadWrite("y_R"))) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite0(Some(CustomReadWrite("x_R")), "y")) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite1("x", Some(CustomReadWrite("y_R")))) + insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite0(CustomReadWrite("x_R"), "y")) *> + insertTuple2AndCheckRead(("x", "y"), HasCustomReadWrite1("x", CustomReadWrite("y_R"))) *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite0(Some(CustomReadWrite("x_R")), "y")) *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomReadWrite1("x", Some(CustomReadWrite("y_R")))) } test("Automatic derivation selects custom Get instances to use for Read when available") { import doobie.implicits.* - insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut0(CustomGetPut("x_G"), "y")) - insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut1("x", CustomGetPut("y_G"))) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut0(Some(CustomGetPut("x_G")), "y")) - insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut1("x", Some(CustomGetPut("y_G")))) + insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut0(CustomGetPut("x_G"), "y")) *> + insertTuple2AndCheckRead(("x", "y"), HasCustomGetPut1("x", CustomGetPut("y_G"))) *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut0(Some(CustomGetPut("x_G")), "y")) *> + insertTuple2AndCheckRead(("x", "y"), HasOptCustomGetPut1("x", Some(CustomGetPut("y_G")))) } test("Read should not be derivable for case objects") { @@ -138,7 +132,7 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { val p = readInt.product(readString) - assertEquals(p.gets, (readInt.gets ++ readString.gets)) + assertEquals(p.gets, readInt.gets ++ readString.gets) } test(".map should correctly transform the value") { @@ -160,12 +154,9 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { val frag = sql"SELECT 1, NULL, 3, NULL" val q1 = frag.query[Option[(Int, Option[Int], Int, Option[Int])]].to[List] - val o1 = q1.transact(xa).unsafeRunSync() - assertEquals(o1, List(Some((1, None, 3, None)))) - val q2 = frag.query[Option[(Int, Int, Int, Int)]].to[List] - val o2 = q2.transact(xa).unsafeRunSync() - assertEquals(o2, List(None)) + q1.transact(xa).assertEquals(List(Some((1, None, 3, None)))) *> + q2.transact(xa).assertEquals(List(None)) } test("Read should read correct columns for instances with Option (None) with left join between two tables") { @@ -204,12 +195,10 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { val frag = sql"SELECT 1, 2, 3, 4" val q1 = frag.query[Option[(Int, Option[Int], Int, Option[Int])]].to[List] - val o1 = q1.transact(xa).unsafeRunSync() - assertEquals(o1, List(Some((1, Some(2), 3, Some(4))))) - val q2 = frag.query[Option[(Int, Int, Int, Int)]].to[List] - val o2 = q2.transact(xa).unsafeRunSync() - assertEquals(o2, List(Some((1, 2, 3, 4)))) + + q1.transact(xa).assertEquals(List(Some((1, Some(2), 3, Some(4))))) *> + q2.transact(xa).assertEquals(List(Some((1, 2, 3, 4)))) } test("Read should select correct columns when combined with `ap`") { @@ -217,14 +206,9 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { import doobie.implicits.* val r = Read[Int] - val c = (r, r, r, r, r).tupled - - val q = sql"SELECT 1, 2, 3, 4, 5".query(using c).to[List] - - val o = q.transact(xa).unsafeRunSync() - - assertEquals(o, List((1, 2, 3, 4, 5))) + val q: ConnectionIO[List[(Int, Int, Int, Int, Int)]] = sql"SELECT 1, 2, 3, 4, 5".query(using c).to[List] + q.transact(xa).assertEquals(List((1, 2, 3, 4, 5))) } test("Read should select correct columns when combined with `product`") { @@ -232,29 +216,22 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { import doobie.implicits.* val r = Read[Int].product(Read[Int].product(Read[Int])) - - val q = sql"SELECT 1, 2, 3".query(using r).to[List] - val o = q.transact(xa).unsafeRunSync() - - assertEquals(o, List((1, (2, 3)))) + val q: ConnectionIO[List[(Int, (Int, Int))]] = sql"SELECT 1, 2, 3".query(using r).to[List] + q.transact(xa).assertEquals(List((1, (2, 3)))) } test("Read typechecking should work for Tuples") { val frag = sql"SELECT 1, 's', 3.0 :: DOUBLE" - assertSuccessTypecheckRead[(Int, String, Double)](frag) - assertSuccessTypecheckRead[(Int, (String, Double))](frag) - assertSuccessTypecheckRead[((Int, String), Double)](frag) - - assertSuccessTypecheckRead[(Int, Option[String], Double)](frag) - assertSuccessTypecheckRead[(Option[Int], Option[(String, Double)])](frag) - assertSuccessTypecheckRead[Option[((Int, String), Double)]](frag) - - assertWarnedTypecheckRead[(Boolean, String, Double)](frag) - - assertMisalignedTypecheckRead[(Int, String)](frag) - assertMisalignedTypecheckRead[(Int, String, Double, Int)](frag) - + assertSuccessTypecheckRead[(Int, String, Double)](frag) *> + assertSuccessTypecheckRead[(Int, (String, Double))](frag) *> + assertSuccessTypecheckRead[((Int, String), Double)](frag) *> + assertSuccessTypecheckRead[(Int, Option[String], Double)](frag) *> + assertSuccessTypecheckRead[(Option[Int], Option[(String, Double)])](frag) *> + assertSuccessTypecheckRead[Option[((Int, String), Double)]](frag) *> + assertWarnedTypecheckRead[(Boolean, String, Double)](frag) *> + assertMisalignedTypecheckRead[(Int, String)](frag) *> + assertMisalignedTypecheckRead[(Int, String, Double, Int)](frag) } test("Read typechecking should work for case classes") { @@ -266,98 +243,75 @@ class ReadSuite extends munit.FunSuite with ReadSuitePlatform { assertSuccessTypecheckRead( sql"create table tab(c1 int, c2 varchar not null, c3 varchar)".update.run.flatMap(_ => sql"SELECT c1,c2,c3 from tab".query[SimpleCaseClass].analysis) - ) - assertSuccessTypecheckRead( - sql"create table tab(c1 int, c2 varchar not null, c3 varchar)".update.run.flatMap(_ => - sql"SELECT c1,c2,c3 from tab".query[WrappedSimpleCaseClass].analysis) - ) - - assertSuccessTypecheckRead( - sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => - sql"SELECT c1,c2,c3 from tab".query[Option[SimpleCaseClass]].analysis) - ) - assertSuccessTypecheckRead( - sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => - sql"SELECT c1,c2,c3 from tab".query[Option[WrappedSimpleCaseClass]].analysis) - ) - - assertTypeErrorTypecheckRead( - sql"create table tab(c1 binary, c2 varchar not null, c3 varchar)".update.run.flatMap(_ => - sql"SELECT c1,c2,c3 from tab".query[SimpleCaseClass].analysis) - ) - - assertMisalignedNullabilityTypecheckRead( - sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => - sql"SELECT c1,c2,c3 from tab".query[SimpleCaseClass].analysis) - ) - - assertSuccessTypecheckRead( - sql"create table tab(c1 int, c2 varchar not null, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" - .update.run.flatMap(_ => - sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) - ) - - assertTypeErrorTypecheckRead( - sql"create table tab(c1 binary, c2 varchar not null, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" - .update.run.flatMap(_ => - sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) - ) - - assertMisalignedNullabilityTypecheckRead( - sql"create table tab(c1 int, c2 varchar, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" - .update.run.flatMap(_ => - sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) - ) - + ) *> + assertSuccessTypecheckRead( + sql"create table tab(c1 int, c2 varchar not null, c3 varchar)".update.run.flatMap(_ => + sql"SELECT c1,c2,c3 from tab".query[WrappedSimpleCaseClass].analysis) + ) *> + assertSuccessTypecheckRead( + sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => + sql"SELECT c1,c2,c3 from tab".query[Option[SimpleCaseClass]].analysis) + ) *> + assertSuccessTypecheckRead( + sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => + sql"SELECT c1,c2,c3 from tab".query[Option[WrappedSimpleCaseClass]].analysis) + ) *> + assertTypeErrorTypecheckRead( + sql"create table tab(c1 binary, c2 varchar not null, c3 varchar)".update.run.flatMap(_ => + sql"SELECT c1,c2,c3 from tab".query[SimpleCaseClass].analysis) + ) *> + assertMisalignedNullabilityTypecheckRead( + sql"create table tab(c1 int, c2 varchar, c3 varchar)".update.run.flatMap(_ => + sql"SELECT c1,c2,c3 from tab".query[SimpleCaseClass].analysis) + ) *> + assertSuccessTypecheckRead( + sql"create table tab(c1 int, c2 varchar not null, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" + .update.run.flatMap(_ => + sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) + ) *> + assertTypeErrorTypecheckRead( + sql"create table tab(c1 binary, c2 varchar not null, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" + .update.run.flatMap(_ => + sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) + ) *> + assertMisalignedNullabilityTypecheckRead( + sql"create table tab(c1 int, c2 varchar, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" + .update.run.flatMap(_ => + sql"SELECT c1,c2,c3,c4,c5,c6,c7,c8 from tab".query[ComplexCaseClass].analysis) + ) } private def insertTuple3AndCheckRead[Tup <: (?, ?, ?): Write, A: Read](in: Tup, expectedOut: A)(implicit loc: Location - ): Unit = { - val res = Query[Tup, A]("SELECT ?, ?, ?").unique(in).transact(xa) - .unsafeRunSync() - assertEquals(res, expectedOut) - } + ): IO[Unit] = + Query[Tup, A]("SELECT ?, ?, ?").unique(in).transact(xa).assertEquals(expectedOut) private def insertTuple2AndCheckRead[Tup <: (?, ?): Write, A: Read](in: Tup, expectedOut: A)(implicit loc: Location - ): Unit = { - val res = Query[Tup, A]("SELECT ?, ?").unique(in).transact(xa) - .unsafeRunSync() - assertEquals(res, expectedOut) - } + ): IO[Unit] = + Query[Tup, A]("SELECT ?, ?").unique(in).transact(xa).assertEquals(expectedOut) - private def assertSuccessTypecheckRead(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - assertEquals(analysisResult.columnAlignmentErrors, Nil) - } + private def assertSuccessTypecheckRead(connio: ConnectionIO[Analysis])(implicit loc: Location): IO[Unit] = + connio.transact(xa).map(_.columnAlignmentErrors).assertEquals(Nil) - private def assertSuccessTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): Unit = { + private def assertSuccessTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): IO[Unit] = assertSuccessTypecheckRead(frag.query[A].analysis) - } - private def assertWarnedTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): Unit = { - val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ColumnTypeWarning])) - } + private def assertWarnedTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): IO[Unit] = + frag.query[A].analysis.transact(xa).map(_.columnAlignmentErrors.map(_.getClass)).assertEquals(List( + classOf[ColumnTypeWarning])) - private def assertTypeErrorTypecheckRead(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ColumnTypeError])) - } + private def assertTypeErrorTypecheckRead(connio: ConnectionIO[Analysis])(implicit loc: Location): IO[Unit] = + connio.transact(xa).map(_.columnAlignmentErrors.map(_.getClass)).assertEquals(List(classOf[ColumnTypeError])) - private def assertMisalignedNullabilityTypecheckRead(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[NullabilityMisalignment])) - } + private def assertMisalignedNullabilityTypecheckRead(connio: ConnectionIO[Analysis])(implicit + loc: Location + ): IO[Unit] = + connio.transact(xa).map(_.columnAlignmentErrors.map(_.getClass)).assertEquals(List( + classOf[NullabilityMisalignment])) - private def assertMisalignedTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): Unit = { - val analysisResult = frag.query[A].analysis.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.columnAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ColumnMisalignment])) - } + private def assertMisalignedTypecheckRead[A: Read](frag: Fragment)(implicit loc: Location): IO[Unit] = + frag.query[A].analysis.transact(xa).map(_.columnAlignmentErrors.map(_.getClass)).assertEquals(List( + classOf[ColumnMisalignment])) } diff --git a/modules/core/src/test/scala/doobie/util/StrategySuite.scala b/modules/core/src/test/scala/doobie/util/StrategySuite.scala index abd49128b..5a75236cf 100644 --- a/modules/core/src/test/scala/doobie/util/StrategySuite.scala +++ b/modules/core/src/test/scala/doobie/util/StrategySuite.scala @@ -12,13 +12,12 @@ import doobie.* import doobie.free.{connection, preparedstatement, resultset} import doobie.implicits.* import doobie.util.transactor.Transactor.Aux +import munit.CatsEffectSuite import java.sql.{Connection, PreparedStatement, ResultSet} import java.util -class StrategySuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class StrategySuite extends CatsEffectSuite { val baseXa: Aux[IO, Unit] = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -75,115 +74,109 @@ class StrategySuite extends munit.FunSuite { Transactor.interpret.set(baseXa, i.ConnectionInterpreter) test("Connection.autoCommit should be set to false") { - val i = new Interp - val _ = sql"select 1".query[Int].unique.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.autoCommit, Some(false)) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].unique.transact(xa(i))).map(_.Connection.autoCommit) + .assertEquals(Some(false)) } test("Connection.commit should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].unique.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.commit, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].unique.transact(xa(i))).map(_.Connection.commit) + .assertEquals(Some(())) } test("Connection.commit should NOT be called on failure") { - val i = new Interp - assertEquals(sql"abc".query[Int].unique.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.Connection.commit, None) + IO.pure(new Interp).flatTap(i => + sql"abc".query[Int].unique.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.Connection.commit) + .assertEquals(None) } test("Connection.rollback should NOT be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].unique.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.rollback, None) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].unique.transact(xa(i))).map(_.Connection.rollback) + .assertEquals(None) } test("Connection.rollback should be called on failure") { - val i = new Interp - assertEquals(sql"abc".query[Int].unique.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.Connection.rollback, Some(())) + IO.pure(new Interp).flatTap(i => + sql"abc".query[Int].unique.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.Connection.rollback) + .assertEquals(Some(())) } test("[Streaming] Connection.autoCommit should be set to false") { - val i = new Interp - val _ = sql"select 1".query[Int].stream.compile.toList.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.autoCommit, Some(false)) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].stream.compile.toList.transact(xa(i))).map( + _.Connection.autoCommit).assertEquals(Some(false)) } test("[Streaming] Connection.commit should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].stream.compile.toList.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.commit, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].stream.compile.toList.transact(xa(i))).map( + _.Connection.commit).assertEquals(Some(())) } test("[Streaming] Connection.commit should NOT be called on failure") { - val i = new Interp - assertEquals(sql"abc".query[Int].stream.compile.toList.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.Connection.commit, None) + IO.pure(new Interp).flatTap(i => + sql"abc".query[Int].stream.compile.toList.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.Connection.commit) + .assertEquals(None) } test("[Streaming] Connection.rollback should NOT be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].stream.compile.toList.transact(xa(i)).unsafeRunSync() - assertEquals(i.Connection.rollback, None) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].stream.compile.toList.transact(xa(i))).map( + _.Connection.rollback).assertEquals(None) } test("[Streaming] Connection.rollback should be called on failure") { - val i = new Interp - assertEquals(sql"abc".query[Int].stream.compile.toList.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.Connection.rollback, Some(())) + IO.pure(new Interp).flatTap(i => + sql"abc".query[Int].stream.compile.toList.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.Connection.rollback) + .assertEquals(Some(())) } test("PreparedStatement.close should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].unique.transact(xa(i)).unsafeRunSync() - assertEquals(i.PreparedStatement.close, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].unique.transact(xa(i))).map( + _.PreparedStatement.close).assertEquals(Some(())) } test("PreparedStatement.close should be called on failure") { - val i = new Interp - assertEquals(sql"select 'x'".query[Int].unique.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.PreparedStatement.close, Some(())) + IO.pure(new Interp).flatTap(i => + sql"select 'x'".query[Int].unique.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.PreparedStatement.close) + .assertEquals(Some(())) } test("[Streaming] PreparedStatement.close should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].stream.compile.toList.transact(xa(i)).unsafeRunSync() - assertEquals(i.PreparedStatement.close, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].stream.compile.toList.transact(xa(i))).map( + _.PreparedStatement.close).assertEquals(Some(())) } test("[Streaming] PreparedStatement.close should be called on failure") { - val i = new Interp - assertEquals( - sql"select 'x'".query[Int].stream.compile.toList.transact(xa(i)).attempt.unsafeRunSync().toOption, - None) - assertEquals(i.PreparedStatement.close, Some(())) + IO.pure(new Interp).flatTap(i => + sql"select 'x'".query[Int].stream.compile.toList.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.PreparedStatement.close) + .assertEquals(Some(())) } test("ResultSet.close should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].unique.transact(xa(i)).unsafeRunSync() - assertEquals(i.ResultSet.close, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].unique.transact(xa(i))).map( + _.ResultSet.close).assertEquals(Some(())) } test("ResultSet.close should be called on failure") { - val i = new Interp - assertEquals(sql"select 'x'".query[Int].unique.transact(xa(i)).attempt.unsafeRunSync().toOption, None) - assertEquals(i.ResultSet.close, Some(())) + IO.pure(new Interp).flatTap(i => + sql"select 'x'".query[Int].unique.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.ResultSet.close) + .assertEquals(Some(())) } test("[Streaming] ResultSet.close should be called on success") { - val i = new Interp - val _ = sql"select 1".query[Int].stream.compile.toList.transact(xa(i)).unsafeRunSync() - assertEquals(i.ResultSet.close, Some(())) + IO.pure(new Interp).flatTap(i => sql"select 1".query[Int].stream.compile.toList.transact(xa(i))).map( + _.ResultSet.close).assertEquals(Some(())) } test("[Streaming] ResultSet.close should be called on failure") { - val i = new Interp - assertEquals( - sql"select 'x'".query[Int].stream.compile.toList.transact(xa(i)).attempt.unsafeRunSync().toOption, - None) - assertEquals(i.ResultSet.close, Some(())) + IO.pure(new Interp).flatTap(i => + sql"select 'x'".query[Int].stream.compile.toList.transact(xa(i)).attempt.map(_.isLeft).assert).map( + _.ResultSet.close) + .assertEquals(Some(())) } } diff --git a/modules/core/src/test/scala/doobie/util/TransactorSuite.scala b/modules/core/src/test/scala/doobie/util/TransactorSuite.scala index 2f496627e..85415b124 100644 --- a/modules/core/src/test/scala/doobie/util/TransactorSuite.scala +++ b/modules/core/src/test/scala/doobie/util/TransactorSuite.scala @@ -5,11 +5,10 @@ package doobie.util import cats.effect.{Async, IO} -import doobie.*, doobie.implicits.* +import doobie.* +import doobie.implicits.* -class TransactorSuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class TransactorSuite extends munit.CatsEffectSuite { val q = sql"select 42".query[Int].unique @@ -22,7 +21,7 @@ class TransactorSuite extends munit.FunSuite { ) test("Transactor should support cats.effect.IO") { - assertEquals(q.transact(xa[IO]).unsafeRunSync(), 42) + q.transact(xa[IO]).assertEquals(42) } class ConnectionTracker { @@ -47,29 +46,27 @@ class TransactorSuite extends munit.FunSuite { test("Connection.close should be called on success") { val tracker = new ConnectionTracker val transactor = tracker.track(xa[IO]) - val _ = sql"select 1".query[Int].unique.transact(transactor).unsafeRunSync() - assertEquals(tracker.connections.map(_.isClosed), List(true)) + val _ = sql"select 1".query[Int].unique.transact(transactor).map(_ => + tracker.connections.map(_.isClosed)).assertEquals(List(true)) } test("Connection.close should be called on failure") { val tracker = new ConnectionTracker val transactor = tracker.track(xa[IO]) - assertEquals(sql"abc".query[Int].unique.transact(transactor).attempt.unsafeRunSync().toOption, None) - assertEquals(tracker.connections.map(_.isClosed), List(true)) + sql"abc".query[Int].unique.transact(transactor).attempt.map(_.isLeft).assertEquals(true) } test("[Streaming] Connection.close should be called on success") { val tracker = new ConnectionTracker val transactor = tracker.track(xa[IO]) - val _ = sql"select 1".query[Int].stream.compile.toList.transact(transactor).unsafeRunSync() - assertEquals(tracker.connections.map(_.isClosed), List(true)) + sql"select 1".query[Int].stream.compile.toList.transact(transactor).map(_ => + tracker.connections.map(_.isClosed)).assertEquals(List(true)) } test("[Streaming] Connection.close should be called on failure") { val tracker = new ConnectionTracker val transactor = tracker.track(xa[IO]) - assertEquals(sql"abc".query[Int].stream.compile.toList.transact(transactor).attempt.unsafeRunSync().toOption, None) - assertEquals(tracker.connections.map(_.isClosed), List(true)) + sql"abc".query[Int].stream.compile.toList.transact(transactor).attempt.map(_.isLeft).assertEquals(true) } } diff --git a/modules/core/src/test/scala/doobie/util/WriteSuite.scala b/modules/core/src/test/scala/doobie/util/WriteSuite.scala index 564dd6ff4..31a2ae2fd 100644 --- a/modules/core/src/test/scala/doobie/util/WriteSuite.scala +++ b/modules/core/src/test/scala/doobie/util/WriteSuite.scala @@ -7,7 +7,6 @@ package doobie.util import doobie.{ConnectionIO, Query, Transactor, Update} import doobie.util.TestTypes.* import cats.effect.IO -import cats.effect.unsafe.implicits.global import doobie.testutils.VoidExtensions import doobie.syntax.all.* import doobie.util.analysis.{Analysis, ParameterMisalignment, ParameterTypeError} @@ -15,7 +14,7 @@ import munit.Location import scala.annotation.nowarn -class WriteSuite extends munit.FunSuite with WriteSuitePlatform { +class WriteSuite extends munit.CatsEffectSuite with WriteSuitePlatform { val xa: Transactor[IO] = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -56,55 +55,59 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform { test("Semiauto derivation selects custom Write instances when available") { implicit val i0: Write[HasCustomReadWrite0] = Write.derived[HasCustomReadWrite0] - assertEquals(i0.length, 2) - writeAndCheckTuple2(HasCustomReadWrite0(CustomReadWrite("x"), "y"), ("x_W", "y")) - implicit val i1: Write[HasCustomReadWrite1] = Write.derived[HasCustomReadWrite1] - assertEquals(i1.length, 2) - writeAndCheckTuple2(HasCustomReadWrite1("x", CustomReadWrite("y")), ("x", "y_W")) - implicit val iOpt0: Write[HasOptCustomReadWrite0] = Write.derived[HasOptCustomReadWrite0] - assertEquals(iOpt0.length, 2) - writeAndCheckTuple2(HasOptCustomReadWrite0(Some(CustomReadWrite("x")), "y"), ("x_W", "y")) - implicit val iOpt1: Write[HasOptCustomReadWrite1] = Write.derived[HasOptCustomReadWrite1] - assertEquals(iOpt1.length, 2) - writeAndCheckTuple2(HasOptCustomReadWrite1("x", Some(CustomReadWrite("y"))), ("x", "y_W")) + + IO { + assertEquals(i0.length, 2) + } *> + writeAndCheckTuple2(HasCustomReadWrite0(CustomReadWrite("x"), "y"), ("x_W", "y")) *> + IO { + assertEquals(i1.length, 2) + } *> + writeAndCheckTuple2(HasCustomReadWrite1("x", CustomReadWrite("y")), ("x", "y_W")) *> + IO { + assertEquals(iOpt0.length, 2) + } *> + writeAndCheckTuple2(HasOptCustomReadWrite0(Some(CustomReadWrite("x")), "y"), ("x_W", "y")) *> + IO { + assertEquals(iOpt1.length, 2) + } *> + writeAndCheckTuple2(HasOptCustomReadWrite1("x", Some(CustomReadWrite("y"))), ("x", "y_W")) } test("Semiauto derivation selects custom Put instances to use for Write when available") { implicit val i0: Write[HasCustomGetPut0] = Write.derived[HasCustomGetPut0] - assertEquals(i0.length, 2) - writeAndCheckTuple2(HasCustomGetPut0(CustomGetPut("x"), "y"), ("x_P", "y")) - implicit val i1: Write[HasCustomGetPut1] = Write.derived[HasCustomGetPut1] - assertEquals(i1.length, 2) - writeAndCheckTuple2(HasCustomGetPut1("x", CustomGetPut("y")), ("x", "y_P")) - implicit val iOpt0: Write[HasOptCustomGetPut0] = Write.derived[HasOptCustomGetPut0] - assertEquals(iOpt0.length, 2) - writeAndCheckTuple2(HasOptCustomGetPut0(Some(CustomGetPut("x")), "y"), ("x_P", "y")) - implicit val iOpt1: Write[HasOptCustomGetPut1] = Write.derived[HasOptCustomGetPut1] - assertEquals(iOpt1.length, 2) - writeAndCheckTuple2(HasOptCustomGetPut1("x", Some(CustomGetPut("y"))), ("x", "y_P")) + + IO { assertEquals(i0.length, 2) } *> + writeAndCheckTuple2(HasCustomGetPut0(CustomGetPut("x"), "y"), ("x_P", "y")) *> + IO { assertEquals(i1.length, 2) } *> + writeAndCheckTuple2(HasCustomGetPut1("x", CustomGetPut("y")), ("x", "y_P")) *> + IO { assertEquals(iOpt0.length, 2) } *> + writeAndCheckTuple2(HasOptCustomGetPut0(Some(CustomGetPut("x")), "y"), ("x_P", "y")) *> + IO { assertEquals(iOpt1.length, 2) } *> + writeAndCheckTuple2(HasOptCustomGetPut1("x", Some(CustomGetPut("y"))), ("x", "y_P")) } test("Automatic derivation selects custom Write instances when available") { import doobie.implicits.* - writeAndCheckTuple2(HasCustomReadWrite0(CustomReadWrite("x"), "y"), ("x_W", "y")) - writeAndCheckTuple2(HasCustomReadWrite1("x", CustomReadWrite("y")), ("x", "y_W")) - writeAndCheckTuple2(HasOptCustomReadWrite0(Some(CustomReadWrite("x")), "y"), ("x_W", "y")) - writeAndCheckTuple2(HasOptCustomReadWrite1("x", Some(CustomReadWrite("y"))), ("x", "y_W")) + writeAndCheckTuple2(HasCustomReadWrite0(CustomReadWrite("x"), "y"), ("x_W", "y")) *> + writeAndCheckTuple2(HasCustomReadWrite1("x", CustomReadWrite("y")), ("x", "y_W")) *> + writeAndCheckTuple2(HasOptCustomReadWrite0(Some(CustomReadWrite("x")), "y"), ("x_W", "y")) *> + writeAndCheckTuple2(HasOptCustomReadWrite1("x", Some(CustomReadWrite("y"))), ("x", "y_W")) } test("Automatic derivation selects custom Put instances to use for Write when available") { import doobie.implicits.* - writeAndCheckTuple2(HasCustomGetPut0(CustomGetPut("x"), "y"), ("x_P", "y")) - writeAndCheckTuple2(HasCustomGetPut1("x", CustomGetPut("y")), ("x", "y_P")) - writeAndCheckTuple2(HasOptCustomGetPut0(Some(CustomGetPut("x")), "y"), ("x_P", "y")) - writeAndCheckTuple2(HasOptCustomGetPut1("x", Some(CustomGetPut("y"))), ("x", "y_P")) + writeAndCheckTuple2(HasCustomGetPut0(CustomGetPut("x"), "y"), ("x_P", "y")) *> + writeAndCheckTuple2(HasCustomGetPut1("x", CustomGetPut("y")), ("x", "y_P")) *> + writeAndCheckTuple2(HasOptCustomGetPut0(Some(CustomGetPut("x")), "y"), ("x_P", "y")) *> + writeAndCheckTuple2(HasOptCustomGetPut1("x", Some(CustomGetPut("y"))), ("x", "y_P")) } test("Write should not be derivable for case objects") { @@ -149,17 +152,17 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform { )) }) .transact(xa) - .unsafeRunSync() + } test("Write should yield correct error when Some(null) inserted") { - interceptMessage[RuntimeException]("Expected non-nullable param at 2. Use Option to describe nullable values.") { - testNullPut(("a", Some(null))) - } + interceptMessageIO[RuntimeException]( + "Expected non-nullable param at 2. Use Option to describe nullable values." + )(testNullPut(("a", Some(null)))) } test("Write should yield correct error when null inserted into non-nullable field") { - interceptMessage[RuntimeException]("Expected non-nullable param at 1. Use Option to describe nullable values.") { + interceptMessageIO[RuntimeException]("Expected non-nullable param at 1. Use Option to describe nullable values.") { testNullPut((null, Some("b"))) } } @@ -180,26 +183,25 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform { val insertSql = "INSERT INTO tab VALUES (?,?,?)" assertSuccessTypecheckWrite( - createTable.flatMap(_ => Update[(Option[Int], String, Double)](insertSql).analysis)) - assertSuccessTypecheckWrite( - createTable.flatMap(_ => Update[((Option[Int], String), Double)](insertSql).analysis)) - assertSuccessTypecheckWrite( - createTable.flatMap(_ => Update[(Option[Int], String, Option[Double])](insertSql).analysis)) - assertSuccessTypecheckWrite( - createAllNullableTable.flatMap(_ => Update[(Option[Int], Option[String], Option[Double])](insertSql).analysis)) - assertSuccessTypecheckWrite( - createAllNullableTable.flatMap(_ => Update[Option[(Option[Int], String, Double)]](insertSql).analysis)) - assertSuccessTypecheckWrite( - createAllNullableTable.flatMap(_ => Update[Option[(Int, Option[(String, Double)])]](insertSql).analysis)) - - assertMisalignedTypecheckWrite(createTable.flatMap(_ => Update[(Option[Int], String)](insertSql).analysis)) - assertMisalignedTypecheckWrite(createTable.flatMap(_ => - Update[(Option[Int], String, Double, Int)](insertSql).analysis)) - - assertTypeErrorTypecheckWrite( - sql"create temp table tab(c1 binary not null, c2 varchar not null, c3 int)".update.run.flatMap(_ => - Update[(Int, String, Option[Int])](insertSql).analysis) - ) + createTable.flatMap(_ => Update[(Option[Int], String, Double)](insertSql).analysis)) *> + assertSuccessTypecheckWrite( + createTable.flatMap(_ => Update[((Option[Int], String), Double)](insertSql).analysis)) *> + assertSuccessTypecheckWrite( + createTable.flatMap(_ => Update[(Option[Int], String, Option[Double])](insertSql).analysis)) *> + assertSuccessTypecheckWrite( + createAllNullableTable.flatMap(_ => + Update[(Option[Int], Option[String], Option[Double])](insertSql).analysis)) *> + assertSuccessTypecheckWrite( + createAllNullableTable.flatMap(_ => Update[Option[(Option[Int], String, Double)]](insertSql).analysis)) *> + assertSuccessTypecheckWrite( + createAllNullableTable.flatMap(_ => Update[Option[(Int, Option[(String, Double)])]](insertSql).analysis)) *> + assertMisalignedTypecheckWrite(createTable.flatMap(_ => Update[(Option[Int], String)](insertSql).analysis)) *> + assertMisalignedTypecheckWrite(createTable.flatMap(_ => + Update[(Option[Int], String, Double, Int)](insertSql).analysis)) *> + assertTypeErrorTypecheckWrite( + sql"create temp table tab(c1 binary not null, c2 varchar not null, c3 int)".update.run.flatMap(_ => + Update[(Int, String, Option[Int])](insertSql).analysis) + ) } test("Write typechecking should work for case classes") { @@ -208,67 +210,54 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform { implicit val wwscc: Write[WrappedSimpleCaseClass] = wscc.contramap(_.sc) // Testing contramap doesn't break typechecking - val createTable = sql"create temp table tab(c1 int, c2 varchar not null, c3 varchar)".update.run + val createTable: ConnectionIO[Int] = sql"create temp table tab(c1 int, c2 varchar not null, c3 varchar)".update.run val insertSimpleSql = "INSERT INTO tab VALUES (?,?,?)" - - assertSuccessTypecheckWrite(createTable.flatMap(_ => Update[SimpleCaseClass](insertSimpleSql).analysis)) - assertSuccessTypecheckWrite(createTable.flatMap(_ => Update[WrappedSimpleCaseClass](insertSimpleSql).analysis)) - - // This shouldn't pass but JDBC driver (at least for h2) doesn't tell us when a parameter should be not-nullable - assertSuccessTypecheckWrite(createTable.flatMap(_ => Update[Option[SimpleCaseClass]](insertSimpleSql).analysis)) - assertSuccessTypecheckWrite(createTable.flatMap(_ => - Update[Option[WrappedSimpleCaseClass]](insertSimpleSql).analysis)) - val insertComplexSql = "INSERT INTO tab VALUES (?,?,?,?,?,?,?,?)" - assertSuccessTypecheckWrite( - sql"create temp table tab(c1 int, c2 varchar, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" - .update.run - .flatMap(_ => Update[ComplexCaseClass](insertComplexSql).analysis) - ) - - assertTypeErrorTypecheckWrite( - sql"create temp table tab(c1 int, c2 varchar, c3 varchar, c4 BINARY, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" - .update.run - .flatMap(_ => Update[ComplexCaseClass](insertComplexSql).analysis) - ) + assertSuccessTypecheckWrite(createTable.flatMap(_ => Update[SimpleCaseClass](insertSimpleSql).analysis)) *> + assertSuccessTypecheckWrite(createTable.flatMap(_ => Update[WrappedSimpleCaseClass](insertSimpleSql).analysis)) *> + // This shouldn't pass but JDBC driver (at least for h2) doesn't tell us when a parameter should be not-nullable + assertSuccessTypecheckWrite( + createTable.flatMap(_ => Update[Option[SimpleCaseClass]](insertSimpleSql).analysis)) *> + assertSuccessTypecheckWrite(createTable.flatMap(_ => + Update[Option[WrappedSimpleCaseClass]](insertSimpleSql).analysis)) *> + assertSuccessTypecheckWrite( + sql"create temp table tab(c1 int, c2 varchar, c3 varchar, c4 int, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" + .update.run + .flatMap(_ => Update[ComplexCaseClass](insertComplexSql).analysis) + ) *> + assertTypeErrorTypecheckWrite( + sql"create temp table tab(c1 int, c2 varchar, c3 varchar, c4 BINARY, c5 varchar, c6 varchar, c7 int, c8 varchar not null)" + .update.run + .flatMap(_ => Update[ComplexCaseClass](insertComplexSql).analysis) + ) } - private def assertSuccessTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - assertEquals(analysisResult.parameterAlignmentErrors, Nil) + private def assertSuccessTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): IO[Unit] = { + connio.transact(xa).map(_.parameterAlignmentErrors).assertEquals(Nil) } - private def assertMisalignedTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.parameterAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ParameterMisalignment])) + private def assertMisalignedTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): IO[Unit] = { + connio.transact(xa).map(_.parameterAlignmentErrors.map(_.getClass)).assertEquals(List( + classOf[ParameterMisalignment])) } - private def assertTypeErrorTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): Unit = { - val analysisResult = connio.transact(xa).unsafeRunSync() - val errorClasses = analysisResult.parameterAlignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ParameterTypeError])) + private def assertTypeErrorTypecheckWrite(connio: ConnectionIO[Analysis])(implicit loc: Location): IO[Unit] = { + connio.transact(xa).map(_.parameterAlignmentErrors.map(_.getClass)).assertEquals(List(classOf[ParameterTypeError])) } private def writeAndCheckTuple2[A: Write, Tup <: (?, ?): Read](in: A, expectedOut: Tup)(implicit loc: Location - ): Unit = { - val res = Query[A, Tup]("SELECT ?, ?").unique(in).transact(xa) - .unsafeRunSync() - assertEquals(res, expectedOut) - } + ): IO[Unit] = + Query[A, Tup]("SELECT ?, ?").unique(in).transact(xa).assertEquals(expectedOut) private def writeAndCheckTuple3[A: Write, Tup <: (?, ?, ?): Read](in: A, expectedOut: Tup)(implicit loc: Location - ): Unit = { - val res = Query[A, Tup]("SELECT ?, ?, ?").unique(in).transact(xa) - .unsafeRunSync() - assertEquals(res, expectedOut) - } + ): IO[Unit] = + Query[A, Tup]("SELECT ?, ?, ?").unique(in).transact(xa).assertEquals(expectedOut) - private def testNullPut(input: (String, Option[String])): Int = { + private def testNullPut(input: (String, Option[String])): IO[Int] = { import doobie.implicits.* (for { @@ -276,7 +265,6 @@ class WriteSuite extends munit.FunSuite with WriteSuitePlatform { n <- Update[(String, Option[String])]("insert into t0 (a, b) values (?, ?)").run(input) } yield n) .transact(xa) - .unsafeRunSync() } } diff --git a/modules/core/src/test/scala/doobie/util/meta/MetaSuite.scala b/modules/core/src/test/scala/doobie/util/meta/MetaSuite.scala index f4eeec45a..48fb22db7 100644 --- a/modules/core/src/test/scala/doobie/util/meta/MetaSuite.scala +++ b/modules/core/src/test/scala/doobie/util/meta/MetaSuite.scala @@ -7,6 +7,7 @@ package doobie.util.meta import cats.effect.IO import doobie.util.transactor.Transactor import doobie.util.{Get, Put} +import munit.CatsEffectAssertions.MUnitCatsAssertionsForIOOps import scala.annotation.nowarn @@ -32,7 +33,6 @@ class MetaSuite extends munit.FunSuite { class MetaDBSuite extends munit.FunSuite { import doobie.implicits.* - import cats.effect.unsafe.implicits.global lazy val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -45,13 +45,13 @@ class MetaDBSuite extends munit.FunSuite { implicit def FooMeta: Meta[Foo] = Meta[String].tiemap(s => Either.cond(!s.isEmpty, Foo(s), "may not be empty"))(_.str) test("Meta.tiemap should accept valid values") { - val x = sql"select 'bar'".query[Foo].unique.transact(xa).unsafeRunSync() - assertEquals(x, Foo("bar")) + val x = sql"select 'bar'".query[Foo].unique.transact(xa) + x.assertEquals(Foo("bar")) } test("Meta.tiemap should reject invalid values") { - val x = sql"select ''".query[Foo].unique.transact(xa).attempt.unsafeRunSync() - assertEquals(x, Left(doobie.util.invariant.InvalidValue[String, Foo]("", "may not be empty"))) + val x = sql"select ''".query[Foo].unique.transact(xa).attempt + x.assertEquals(Left(doobie.util.invariant.InvalidValue[String, Foo]("", "may not be empty"))) } } diff --git a/modules/h2-circe/src/test/scala/doobie/h2/circe/H2JsonSuite.scala b/modules/h2-circe/src/test/scala/doobie/h2/circe/H2JsonSuite.scala index 0c2891a6a..ba2257c61 100644 --- a/modules/h2-circe/src/test/scala/doobie/h2/circe/H2JsonSuite.scala +++ b/modules/h2-circe/src/test/scala/doobie/h2/circe/H2JsonSuite.scala @@ -8,10 +8,9 @@ import cats.effect.IO import doobie.* import doobie.implicits.* import io.circe.{Decoder, Encoder, Json} +import munit.CatsEffectSuite -class H2JsonSuite extends munit.FunSuite { - - import cats.effect.unsafe.implicits.global +class H2JsonSuite extends CatsEffectSuite { val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", @@ -29,13 +28,13 @@ class H2JsonSuite extends munit.FunSuite { def testInOut[A](col: String, a: A, t: Transactor[IO])(implicit m: Get[A], p: Put[A]) = { test(s"Mapping for $col as ${m.typeStack} - write+read $col as ${m.typeStack}") { - assertEquals(inOut(col, a).transact(t).attempt.unsafeRunSync(), Right(a)) + inOut(col, a).transact(t).attempt.assertEquals(Right(a)) } test(s"Mapping for $col as ${m.typeStack} - write+read $col as Option[${m.typeStack}] (Some)") { - assertEquals(inOut[Option[A]](col, Some(a)).transact(t).attempt.unsafeRunSync(), Right(Some(a))) + inOut[Option[A]](col, Some(a)).transact(t).attempt.assertEquals(Right(Some(a))) } test(s"Mapping for $col as ${m.typeStack} - write+read $col as Option[${m.typeStack}] (None)") { - assertEquals(inOut[Option[A]](col, None).transact(t).attempt.unsafeRunSync(), Right(None)) + inOut[Option[A]](col, None).transact(t).attempt.assertEquals(Right(None)) } } @@ -48,15 +47,12 @@ class H2JsonSuite extends munit.FunSuite { test("json should check ok for read") { import doobie.h2.circe.json.implicits.* - - val a = sql"SELECT '{}' FORMAT JSON".query[Json].analysis.transact(xa).unsafeRunSync() - assertEquals(a.columnTypeErrors, Nil) + sql"SELECT '{}' FORMAT JSON".query[Json].analysis.transact(xa).map(_.columnTypeErrors).assertEquals(Nil) } + test("json should check ok for write") { import doobie.h2.circe.json.implicits.* - - val a = sql"SELECT ${Json.obj()} FORMAT JSON".query[Json].analysis.transact(xa).unsafeRunSync() - assertEquals(a.parameterTypeErrors, Nil) + sql"SELECT ${Json.obj()} FORMAT JSON".query[Json].analysis.transact(xa).map(_.parameterTypeErrors).assertEquals(Nil) } // Encoder / Decoders @@ -70,12 +66,12 @@ class H2JsonSuite extends munit.FunSuite { } test("fooGet should check ok for read") { - val a = sql"SELECT '{}' FORMAT JSON".query[Foo].analysis.transact(xa).unsafeRunSync() - assertEquals(a.columnTypeErrors, Nil) + sql"SELECT '{}' FORMAT JSON".query[Foo].analysis.transact(xa).map(_.columnTypeErrors).assertEquals(Nil) } + test("fooPut check ok for write") { - val a = sql"SELECT ${Foo(Json.obj())} FORMAT JSON".query[Foo].analysis.transact(xa).unsafeRunSync() - assertEquals(a.parameterTypeErrors, Nil) + sql"SELECT ${Foo(Json.obj())} FORMAT JSON".query[Foo].analysis.transact(xa).map(_.parameterTypeErrors).assertEquals( + Nil) } } diff --git a/modules/h2/src/test/scala/doobie/h2/h2types.scala b/modules/h2/src/test/scala/doobie/h2/h2types.scala index 91f881a17..3122fbf58 100644 --- a/modules/h2/src/test/scala/doobie/h2/h2types.scala +++ b/modules/h2/src/test/scala/doobie/h2/h2types.scala @@ -5,7 +5,6 @@ package doobie.h2 import java.util.UUID - import cats.effect.IO import doobie.* import doobie.implicits.* @@ -13,14 +12,13 @@ import doobie.h2.implicits.* import doobie.util.analysis.{Analysis, ColumnTypeError} import doobie.util.arbitraries.SQLArbitraries.* import doobie.util.arbitraries.StringArbitraries.* -import org.scalacheck.Prop.forAll import org.scalacheck.{Arbitrary, Gen} +import munit.CatsEffectAssertions.MUnitCatsAssertionsForIOOps +import org.scalacheck.effect.PropF // Establish that we can read various types. It's not very comprehensive as a test, bit it's a start. class h2typesspec extends munit.ScalaCheckSuite { - import cats.effect.unsafe.implicits.global - val xa = Transactor.fromDriverManager[IO]( driver = "org.h2.Driver", url = "jdbc:h2:mem:ch3;DB_CLOSE_DELAY=-1", @@ -49,15 +47,15 @@ class h2typesspec extends munit.ScalaCheckSuite { def testInOutWithCustomGen[A](col: String, gen: Gen[A])(implicit m: Get[A], p: Put[A]) = { test(s"Mapping for $col as ${m.typeStack} - write+read $col as ${m.typeStack}") { - forAll(gen) { (t: A) => assertEquals(inOut(col, t).transact(xa).attempt.unsafeRunSync(), Right(t)) } + PropF.forAllF(gen) { (t: A) => inOut(col, t).transact(xa).attempt.assertEquals(Right(t)) } } test(s"Mapping for $col as ${m.typeStack} - write+read $col as Option[${m.typeStack}] (Some)") { - forAll(gen) { (t: A) => - assertEquals(inOutOpt[A](col, Some(t)).transact(xa).attempt.unsafeRunSync(), Right(Some(t))) + PropF.forAllF(gen) { (t: A) => + inOutOpt[A](col, Some(t)).transact(xa).attempt.assertEquals(Right(Some(t))) } } test(s"Mapping for $col as ${m.typeStack} - write+read $col as Option[${m.typeStack}] (None)") { - assertEquals(inOutOpt[A](col, None).transact(xa).attempt.unsafeRunSync(), Right(None)) + inOutOpt[A](col, None).transact(xa).attempt.assertEquals(Right(None)) } } @@ -115,30 +113,30 @@ class h2typesspec extends munit.ScalaCheckSuite { skip("GEOMETRY") test("Mapping for Boolean should pass query analysis for unascribed 'true'") { - val a = sql"select true".query[Boolean].analysis.transact(xa).unsafeRunSync() - assertEquals(a.alignmentErrors, Nil) + val a = sql"select true".query[Boolean].analysis.transact(xa) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for Boolean should pass query analysis for ascribed BIT") { - val a = sql"select true::BIT".query[Boolean].analysis.transact(xa).unsafeRunSync() - assertEquals(a.alignmentErrors, Nil) + val a = sql"select true::BIT".query[Boolean].analysis.transact(xa) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for Boolean should pass query analysis for ascribed BOOLEAN") { - val a = sql"select true::BOOLEAN".query[Boolean].analysis.transact(xa).unsafeRunSync() - assertEquals(a.alignmentErrors, Nil) + val a = sql"select true::BOOLEAN".query[Boolean].analysis.transact(xa) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for UUID should pass query analysis for unascribed UUID") { - val a = sql"select random_uuid()".query[UUID].analysis.transact(xa).unsafeRunSync() - assertEquals(a.alignmentErrors, Nil) + val a = sql"select random_uuid()".query[UUID].analysis.transact(xa) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for UUID should pass query analysis for ascribed UUID") { - val a = sql"select random_uuid()::UUID".query[UUID].analysis.transact(xa).unsafeRunSync() - assertEquals(a.alignmentErrors, Nil) + val a = sql"select random_uuid()::UUID".query[UUID].analysis.transact(xa) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for LocalDate should pass query analysis for DATE") { val a = analyzeDate[java.time.LocalDate] - assertEquals(a.alignmentErrors, Nil) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for LocalDate should fail query analysis for TIMESTAMP") { @@ -148,7 +146,7 @@ class h2typesspec extends munit.ScalaCheckSuite { test("Mapping for LocalTime should pass query analysis for TIME") { val a = analyzeTime[java.time.LocalTime] - assertEquals(a.alignmentErrors, Nil) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for LocalTime should fail query analysis for TIME WITH TIME ZONE") { @@ -158,7 +156,7 @@ class h2typesspec extends munit.ScalaCheckSuite { test("Mapping for OffsetTime should pass query analysis for TIME WITH TIME ZONE") { val a = analyzeTimeWithTimeZone[java.time.OffsetTime] - assertEquals(a.alignmentErrors, Nil) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for OffsetTime should fail query analysis for TIME") { @@ -168,7 +166,7 @@ class h2typesspec extends munit.ScalaCheckSuite { test("Mapping for LocalDateTime should pass query analysis for TIMESTAMP") { val a = analyzeTimestamp[java.time.LocalDateTime] - assertEquals(a.alignmentErrors, Nil) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for LocalDateTime should fail query analysis for DATE") { @@ -188,7 +186,7 @@ class h2typesspec extends munit.ScalaCheckSuite { test("Mapping for OffsetDateTime should pass query analysis for TIMESTAMP WITH TIME ZONE") { val a = analyzeTimestampWithTimeZone[java.time.OffsetDateTime] - assertEquals(a.alignmentErrors, Nil) + a.map(_.alignmentErrors).assertEquals(Nil) } test("Mapping for OffsetDateTime should fail query analysis for TIME WITH TIME ZONE") { @@ -201,18 +199,19 @@ class h2typesspec extends munit.ScalaCheckSuite { assertAnalyzeColumnError(a) } - private def analyzeDate[R: Read] = analyze(sql"select '2000-01-02'::DATE".query[R]) - private def analyzeTime[R: Read] = analyze(sql"select '01:02:03'::TIME".query[R]) - private def analyzeTimeWithTimeZone[R: Read] = analyze(sql"select '01:02:03+04:05'::TIME WITH TIME ZONE".query[R]) - private def analyzeTimestamp[R: Read] = analyze(sql"select '2000-01-02T01:02:03'::TIMESTAMP".query[R]) - private def analyzeTimestampWithTimeZone[R: Read] = + private def analyzeDate[R: Read]: IO[Analysis] = analyze(sql"select '2000-01-02'::DATE".query[R]) + private def analyzeTime[R: Read]: IO[Analysis] = analyze(sql"select '01:02:03'::TIME".query[R]) + private def analyzeTimeWithTimeZone[R: Read]: IO[Analysis] = + analyze(sql"select '01:02:03+04:05'::TIME WITH TIME ZONE".query[R]) + private def analyzeTimestamp[R: Read]: IO[Analysis] = analyze(sql"select '2000-01-02T01:02:03'::TIMESTAMP".query[R]) + private def analyzeTimestampWithTimeZone[R: Read]: IO[Analysis] = analyze(sql"select '2000-01-02T01:02:03+04:05'::TIMESTAMP WITH TIME ZONE".query[R]) - private def analyze[R](q: Query0[R]) = q.analysis.transact(xa).unsafeRunSync() + private def analyze[R](q: Query0[R]): IO[Analysis] = q.analysis.transact(xa) - private def assertAnalyzeColumnError(result: Analysis): Unit = { - val errorClasses = result.alignmentErrors.map(_.getClass) - assertEquals(errorClasses, List(classOf[ColumnTypeError])) + private def assertAnalyzeColumnError(result: IO[Analysis]): Unit = { + val errorClasses = result.map(_.alignmentErrors.map(_.getClass)) + val _ = errorClasses.assertEquals(List(classOf[ColumnTypeError])) } } diff --git a/modules/hikari/src/test/scala/doobie/HikariQueryCancellationSuite.scala b/modules/hikari/src/test/scala/doobie/HikariQueryCancellationSuite.scala index 5ab66f03e..c42cd5999 100644 --- a/modules/hikari/src/test/scala/doobie/HikariQueryCancellationSuite.scala +++ b/modules/hikari/src/test/scala/doobie/HikariQueryCancellationSuite.scala @@ -5,7 +5,6 @@ package doobie import cats.effect.* -import cats.effect.unsafe.implicits.global import com.zaxxer.hikari.HikariConfig import doobie.hikari.HikariTransactor import doobie.implicits.* @@ -14,7 +13,7 @@ import fs2.Stream import scala.concurrent.duration.DurationInt -class HikariQueryCancellationSuite extends munit.FunSuite { +class HikariQueryCancellationSuite extends munit.CatsEffectSuite { // Typically you construct a transactor this way, using lifetime-managed thread pools. val transactorRes: Resource[IO, Transactor[IO]] = @@ -46,12 +45,10 @@ class HikariQueryCancellationSuite extends munit.FunSuite { _ <- IO.sleep(3.second) _ <- fiber.join.attempt result <- sql"select * from query_cancel_test order by i".query[String].to[List].transact(xa) - } yield { - assertEquals(result, List("1")) - } + } yield result } - scenario.unsafeRunSync() + assertIO(scenario, List("1")) } test("Stream query cancel with Hikari") { @@ -69,11 +66,9 @@ class HikariQueryCancellationSuite extends munit.FunSuite { _ <- IO.sleep(3.second) _ <- fiber.join.attempt result <- sql"select * from stream_cancel_test order by i".query[String].to[List].transact(xa) - } yield { - assertEquals(result, List("1")) - } + } yield result } - scenario.unsafeRunSync() + assertIO(scenario, List("1")) } }