Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added weaver module #1668

Merged
merged 9 commits into from
Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p modules/scalatest/target modules/refined/target target modules/postgres/target modules/docs/target modules/postgres-circe/target modules/h2/target modules/hikari/target modules/munit/target modules/h2-circe/target modules/core/target modules/example/target modules/specs2/target modules/free/target modules/bench/target project/target
run: mkdir -p modules/weaver/target modules/scalatest/target modules/refined/target target modules/postgres/target modules/docs/target modules/postgres-circe/target modules/h2/target modules/hikari/target modules/munit/target modules/h2-circe/target modules/core/target modules/example/target modules/specs2/target modules/free/target modules/bench/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar modules/scalatest/target modules/refined/target target modules/postgres/target modules/docs/target modules/postgres-circe/target modules/h2/target modules/hikari/target modules/munit/target modules/h2-circe/target modules/core/target modules/example/target modules/specs2/target modules/free/target modules/bench/target project/target
run: tar cf targets.tar modules/weaver/target modules/scalatest/target modules/refined/target target modules/postgres/target modules/docs/target modules/postgres-circe/target modules/h2/target modules/hikari/target modules/munit/target modules/h2-circe/target modules/core/target modules/example/target modules/specs2/target modules/free/target modules/bench/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
19 changes: 18 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lazy val scala212Version = "2.12.15"
lazy val scala213Version = "2.13.8"
lazy val scala30Version = "3.1.1"
lazy val slf4jVersion = "1.7.36"
lazy val weaverVersion = "0.7.14"

// Basic versioning and publishing stuff
ThisBuild / tlBaseVersion := "1.0"
Expand Down Expand Up @@ -169,6 +170,7 @@ lazy val doobie = project.in(file("."))
scalatest,
munit,
specs2,
weaver
)

lazy val free = project
Expand Down Expand Up @@ -418,6 +420,21 @@ lazy val munit = project
)
)

lazy val weaver = project
.in(file("modules/weaver"))
.enablePlugins(AutomateHeaderPlugin)
.dependsOn(core)
.settings(doobieSettings)
.settings(
name := s"doobie-weaver",
description := "Weaver support for doobie.",
testFrameworks += new TestFramework("weaver.framework.CatsEffect"),
libraryDependencies ++= Seq(
"com.disneystreaming" %% "weaver-cats" % weaverVersion,
"com.h2database" % "h2" % h2Version % "test"
)
)

lazy val bench = project
.in(file("modules/bench"))
.enablePlugins(NoPublishPlugin)
Expand All @@ -428,7 +445,7 @@ lazy val bench = project

lazy val docs = project
.in(file("modules/docs"))
.dependsOn(core, postgres, specs2, munit, hikari, h2, scalatest)
.dependsOn(core, postgres, specs2, munit, hikari, h2, scalatest, weaver)
.enablePlugins(NoPublishPlugin)
.enablePlugins(ParadoxPlugin)
.enablePlugins(ParadoxSitePlugin)
Expand Down
27 changes: 26 additions & 1 deletion modules/docs/src/main/mdoc/docs/13-Unit-Testing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Unit Testing

The YOLO-mode query checking feature demonstated in an earlier chapter is also available as a trait you can mix into your [Specs2](http://etorreborre.github.io/specs2/), [ScalaTest](http://www.scalatest.org/) or [MUnit](https://scalameta.org/munit) unit tests.
The YOLO-mode query checking feature demonstated in an earlier chapter is also available as a trait you can mix into your [Specs2](http://etorreborre.github.io/specs2/), [ScalaTest](http://www.scalatest.org/), [MUnit](https://scalameta.org/munit) or [Weaver](https://disneystreaming.github.io/weaver-test/) unit tests.

### Setting Up

Expand Down Expand Up @@ -150,3 +150,28 @@ class AnalysisTestSuite extends FunSuite with doobie.munit.IOChecker {

}
```

### The Weaver Package

The `doobie-weaver` add-on provides a mix-in trait what we can add to any effectful test Suite.
The `check` function takes an implicit `Transactor[F]` parameter. Since Weaver has its own way
to manage shared resources, it is convenient to use that to allocate the transcator.

```scala mdoc:silent
import _root_.weaver._
import doobie.weaver._

object AnalysisTestSuite extends IOSuite with IOChecker {

override type Res = Transactor[IO]
override def sharedResource: Resource[IO,Res] =
Resource.pure(Transactor.fromDriverManager[IO](
"org.postgresql.Driver", "jdbc:postgresql:world", "postgres", ""
))

test("trivial") { implicit transactor => check(trivial) }
test("biggerThan") { implicit transactor => check(biggerThan(0)) }
test("update") { implicit transactor => check(update("", "")) }

}
```
62 changes: 62 additions & 0 deletions modules/weaver/src/main/scala/doobie/weaver/Checker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.weaver

import weaver._
import weaver.Expectations.Helpers._
import doobie.util.testing._
import doobie.syntax.connectionio._
import cats.effect.kernel.Sync
import cats.syntax.all._
import doobie.util.Colors
import doobie._
import org.tpolecat.typename._

/**
* Module with a mix-in trait for specifications that enables checking of doobie `Query` and `Update` values.
*
* {{{
* object ExampleSuite extends IOSuite with IOChecker {
*
* override type Res = Transactor[IO]
* override def sharedResource: Resource[IO, Res] =
* // The transactor to use for the tests.
* Resource.pure(Transactor.fromDriverManager[IO](
* "org.postgresql.Driver",
* "jdbc:postgresql:world",
* "postgres", ""
* ))
*
* // Now just mention the queries. Arguments are not used.
* test("findByNameAndAge") { implicit transactor => check(MyDaoModule.findByNameAndAge(null, 0)) }
* test("allWoozles") { implicit transactor => check(MyDaoModule.allWoozles) }
*
* }
* }}}
*/
trait Checker[M[_]] {
def check[A: Analyzable](a: A)(implicit M: Sync[M], pos: SourceLocation, transactor: Transactor[M]): M[Expectations] =
checkImpl(Analyzable.unpack(a))

def checkOutput[A: TypeName](q: Query0[A])(implicit M: Sync[M], pos: SourceLocation, transactor: Transactor[M]): M[Expectations] =
checkImpl(AnalysisArgs(
s"Query0[${typeName[A]}]", q.pos, q.sql, q.outputAnalysis
))

def checkOutput[A: TypeName, B: TypeName](q: Query[A, B])(implicit M: Sync[M], pos: SourceLocation, transactor: Transactor[M]): M[Expectations] =
checkImpl(AnalysisArgs(
s"Query[${typeName[A]}, ${typeName[B]}]", q.pos, q.sql, q.outputAnalysis
))

private def checkImpl(args: AnalysisArgs)(implicit M: Sync[M], pos: SourceLocation, transactor: Transactor[M]) = {
analyze(args).transact(transactor).map { report =>
if (!report.succeeded)
failure(formatReport(args, report, Colors.Ansi)
.padLeft(" ")
.toString)
else success
}
}
}
11 changes: 11 additions & 0 deletions modules/weaver/src/main/scala/doobie/weaver/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie

import cats.effect.IO

package object weaver {
type IOChecker = Checker[IO]
}
63 changes: 63 additions & 0 deletions modules/weaver/src/test/scala/doobie/weaver/CheckerTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.weaver

import cats.effect.IO
import doobie.syntax.string._
import doobie.util.Read
import doobie.util.transactor.Transactor
import weaver._
import cats.effect.kernel.Resource

object CheckerTests extends IOSuite with IOChecker {

override type Res = Transactor[IO]
override def sharedResource: Resource[IO,Res] =
Resource.pure(Transactor.fromDriverManager[IO](
"org.h2.Driver",
"jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1",
"sa", ""
))

test("trivial") { implicit transactor =>
check(sql"select 1".query[Int])
}

test("fail") { implicit transactor =>
check(sql"select 1".query[String]).map(expectation =>
expectation.xor(success)
)
}

final case class Foo[F[_]](x: Int)

test ("trivial case-class") { implicit transactor =>
check(sql"select 1".query[Foo[cats.Id]])
}

test("Read should select correct columns when combined with `product`") { implicit transactor =>
import cats.syntax.all._
import doobie.implicits._

val ri = Read[Int]
val rs = Read[String]

// tupled use product under the hood
val combined: Read[(Int, String)] = (ri, rs).tupled

check(sql"SELECT 1, '2'".query(combined))
}

test("Read should select correct columns for checking when combined with `ap`") { implicit transactor =>
val readInt = Read[(Int, Int)]
val readIntToInt: Read[Tuple2[Int, Int] => String] =
Read[(String, String)].map(i => k => s"$i,$k")

val combined: Read[String] = readInt.ap(readIntToInt)

check(sql"SELECT '1', '2', 3, 4".query(combined))
}

}