-
Notifications
You must be signed in to change notification settings - Fork 348
/
analysisspec.scala
92 lines (80 loc) · 3.16 KB
/
analysisspec.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 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.specs2
import cats.effect.{ Async, IO }
import doobie.{ Update, Update0 }
import doobie.syntax.connectionio._
import doobie.util.query.{ Query, Query0 }
import doobie.util.testing._
import org.specs2.mutable.Specification
import org.specs2.specification.core.{ Fragment, Fragments }
import org.specs2.specification.create.{ FormattingFragments => Format }
import org.specs2.specification.dsl.Online._
import org.tpolecat.typename._
/**
* Module with a mix-in trait for specifications that enables checking of doobie `Query` and `Update` values.
* {{{
* // An example specification, taken from the examples project.
* class AnalysisTestSpec extends Specification with AnalysisSpec {
*
* // The transactor to use for the tests.
* val transactor = Transactor.fromDriverManager[IO](...)
*
* // Now just mention the queries. Arguments are not used.
* check(MyDaoModule.findByNameAndAge(null, 0))
* check(MyDaoModule.allWoozles)
*
* }
* }}}
*/
object analysisspec {
trait Checker[M[_]] extends CheckerBase[M] { this: Specification =>
def check[A: Analyzable](a: A): Fragments =
checkImpl(Analyzable.unpack(a))
def checkOutput[A: TypeName](q: Query0[A]): Fragments =
checkImpl(AnalysisArgs(
s"Query0[${typeName[A]}]", q.pos, q.sql, q.outputAnalysis
))
def checkOutput[A: TypeName, B: TypeName](q: Query[A, B]): Fragments =
checkImpl(AnalysisArgs(
s"Query[${typeName[A]}, ${typeName[B]}]", q.pos, q.sql, q.outputAnalysis
))
def checkOutput[A: TypeName](u: Update[A]): Fragments =
checkImpl(AnalysisArgs(
s"Update[${typeName[A]}]", u.pos, u.sql, u.analysis
))
def checkOutput(u: Update0): Fragments =
checkImpl(AnalysisArgs(
"Update0", u.pos, u.sql, u.analysis
))
private def checkImpl(args: AnalysisArgs): Fragments =
// continuesWith is necessary to make sure the query doesn't run too early
s"${args.header}\n\n${args.cleanedSql.padLeft(" ").toString}\n" >> ok.continueWith {
val report = U.unsafeRunSync(analyze(args).transact(transactor))
indentBlock(
report.items.map { item =>
item.description ! item.error.fold(ok) {
err => ko(err.wrap(70).toString)
}
}
)
}
private def indentBlock(fs: Seq[Fragment]): Fragments =
// intersperse fragments with newlines, and indent them.
// This differs from standard version (FragmentsDsl.fragmentsBlock()) in
// that any failure gets properly indented, too.
Fragments.empty
.append(Format.t)
.append(fs.flatMap(Seq(Format.br, _)))
.append(Format.bt)
}
/** Implementation of Checker[IO] */
trait IOChecker extends Checker[IO] { this: Specification =>
import cats.effect.unsafe.implicits.global
override implicit val M: Async[IO] = IO.asyncForIO
override implicit val U: UnsafeRun[IO] = new UnsafeRun[IO] {
def unsafeRunSync[A](ioa: IO[A]) = ioa.unsafeRunSync()
}
}
}