This repository has been archived by the owner on Jan 13, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix clean-up; add support for functional effects
- Loading branch information
1 parent
fe40aa2
commit c132013
Showing
24 changed files
with
891 additions
and
361 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
modules/cats/src/main/scala/backstub/CatsEffectStubs.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package backstub | ||
|
||
import backstub.effect.StubEffect | ||
import cats.effect.IO | ||
|
||
trait CatsEffectStubs extends Stubs: | ||
given StubEffect.Mono[IO] = new StubEffect.Mono[IO]: | ||
def unit[T](t: => T): IO[T] = IO(t) | ||
def flatMap[E, EE >: E, T, T2](fa: IO[T])(f: T => IO[T2]): IO[T2] = fa.flatMap(f) | ||
|
62 changes: 62 additions & 0 deletions
62
modules/cats/src/test/scala/catseffecttests/IOExpectationsSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package catseffecttests | ||
|
||
import backstub.* | ||
import cats.effect.IO | ||
import munit.CatsEffectSuite | ||
|
||
class IOExpectationsSpec extends CatsEffectSuite, CatsEffectStubs: | ||
override def beforeEach(context: BeforeEach) = | ||
resetStubs() | ||
|
||
trait Foo: | ||
def zeroArgsIO: IO[Option[String]] | ||
|
||
def oneArgIO(x: Int): IO[Option[String]] | ||
|
||
def twoArgsIO(x: Int, y: String): IO[Option[String]] | ||
|
||
def overloaded(x: Int, y: Boolean): IO[Int] | ||
|
||
def overloaded(x: String): IO[Boolean] | ||
|
||
def overloaded: IO[String] | ||
|
||
val foo: Stub[Foo] = stub[Foo]: | ||
Expect[Foo] | ||
.methodF0(_.zeroArgsIO).returnsOnly(IO(Some("foo"))) | ||
.methodF(_.oneArgIO).returns(_ => IO(None)) | ||
.methodF(_.twoArgsIO).returns((x, y) => IO(None)) | ||
.methodF0(_.overloaded: IO[String]).returnsOnly(IO("")) | ||
.methodF(_.overloaded: String => IO[Boolean]).returns(x => IO(true)) | ||
.methodF(_.overloaded: (Int, Boolean) => IO[Int]).returns((x, y) => IO(1)) | ||
|
||
test("zero args"): | ||
val result = for | ||
_ <- foo.zeroArgsIO | ||
_ <- foo.zeroArgsIO | ||
times <- foo.timesF(_.zeroArgsIO) | ||
yield times | ||
|
||
assertIO(result, 2) | ||
|
||
test("one arg"): | ||
val result = for | ||
_ <- foo.oneArgIO(1) | ||
_ <- foo.oneArgIO(2) | ||
times <- foo.timesF(_.oneArgIO) | ||
calls <- foo.callsF(_.oneArgIO) | ||
yield (times, calls) | ||
|
||
assertIO(result, (2, List(1, 2))) | ||
|
||
|
||
test("two args"): | ||
val result = for | ||
_ <- foo.twoArgsIO(1, "foo") | ||
_ <- foo.twoArgsIO(2, "bar") | ||
times <- foo.timesF(_.twoArgsIO) | ||
calls <- foo.callsF(_.twoArgsIO) | ||
yield (times, calls) | ||
|
||
assertIO(result, (2, List((1, "foo"), (2, "bar")))) | ||
|
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package backstub | ||
|
||
import effect.StubEffect | ||
|
||
import scala.annotation.compileTimeOnly | ||
import scala.util.{NotGiven, TupledFunction} | ||
|
||
trait Expect[T]: | ||
def method[R]( | ||
select: T => R | ||
)(using | ||
NotGiven[<:<[R, Tuple => ?]] | ||
): Expect.Returns0[T, R] | ||
|
||
def method[Arg, R](select: T => Arg => R): Expect.Returns1[T, Arg, R] | ||
|
||
def method[F, Args <: ? *: ? *: EmptyTuple, R]( | ||
select: T => F | ||
)(using | ||
TupledFunction[F, Args => R] | ||
): Expect.Returns[T, Args, R] | ||
|
||
def methodF0[F[+_]: StubEffect.Mono, R]( | ||
select: T => F[R] | ||
): Expect.Returns0[T, F[R]] | ||
|
||
def methodF[Arg, F[+_]: StubEffect.Mono, R]( | ||
select: T => Arg => F[R] | ||
): Expect.Returns1[T, Arg, F[R]] | ||
|
||
def methodF[Fun, Args <: ? *: ? *: EmptyTuple, F[+_]: StubEffect.Mono, R]( | ||
select: T => Fun | ||
)(using | ||
TupledFunction[Fun, Args => F[R]] | ||
): Expect.Returns[T, Args, F[R]] | ||
|
||
def methodIO[F[+_, +_]: StubEffect, E, R]( | ||
select: T => F[E, R] | ||
): Expect.Returns0[T, F[E, R]] | ||
|
||
def methodIO[Arg, F[+_, +_]: StubEffect, E, R]( | ||
select: T => Arg => F[E, R] | ||
): Expect.Returns1[T, Arg, F[E, R]] | ||
|
||
def methodIO[Fun, Args <: ? *: ? *: EmptyTuple, F[+_, +_]: StubEffect, E, R]( | ||
select: T => Fun | ||
)(using | ||
TupledFunction[Fun, Args => F[E, R]] | ||
): Expect.Returns[T, Args, F[E, R]] | ||
|
||
object Expect: | ||
@compileTimeOnly("Expect[T] is considered to be an inline given or passed directly to stub[T]") | ||
def apply[T]: Expect[T] = throw IllegalAccessError() | ||
|
||
trait Returns0[T, R]: | ||
def returnsOnly[RR <: R](value: RR): Expect[T] | ||
|
||
trait Returns1[T, Arg, R]: | ||
def returns[RR <: R](value: Arg => RR): Expect[T] | ||
|
||
trait Returns[T, Args <: Tuple, R]: | ||
def returns[RR <: R](value: Args => RR): Expect[T] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package backstub | ||
|
||
import effect.StubEffect | ||
|
||
trait Stubs: | ||
final given stubs: CreatedStubs = CreatedStubs() | ||
|
||
final def resetStubs(): Unit = stubs.clearAll() | ||
|
||
final def resetStubsIO[F[+_, +_]: StubEffect]: F[Nothing, Unit] = | ||
summon[StubEffect[F]].unit(resetStubs()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package backstub | ||
|
||
import backstub.effect.StubEffect | ||
import backstub.internal.UntupleOne | ||
|
||
import scala.quoted.{Expr, Quotes, Type} | ||
import scala.util.{NotGiven, TupledFunction} | ||
|
||
extension [T](service: Stub[T]) | ||
inline def calls[Fun, Args <: Tuple, R]( | ||
inline select: T => Fun | ||
)(using | ||
TupledFunction[Fun, Args => R] | ||
): List[UntupleOne[Args]] = | ||
${ callsMacro[T, Fun, Args, R]('{ service }, '{ select }) } | ||
|
||
inline def callsF[F[+_]: StubEffect.Mono, Fun, Args <: Tuple, R]( | ||
inline select: T => Fun | ||
)(using | ||
TupledFunction[Fun, Args => F[R]] | ||
): F[List[UntupleOne[Args]]] = | ||
summon[StubEffect.Mono[F]] | ||
.unit(calls[Fun, Args, F[R]](select)) | ||
|
||
inline def callsIO[F[+_, +_]: StubEffect, Fun, Args <: Tuple, E, R]( | ||
inline select: T => Fun | ||
)(using | ||
TupledFunction[Fun, Args => F[E, R]] | ||
): F[Nothing, List[UntupleOne[Args]]] = | ||
summon[StubEffect[F]].unit(calls[Fun, Args, F[E, R]](select)) | ||
|
||
inline def times[F, Args <: Tuple, R]( | ||
inline select: T => F | ||
): Int = | ||
scala.compiletime.summonFrom { | ||
case given NotGiven[TupledFunction[F, _]] => | ||
times0[F](select) | ||
|
||
case tf: TupledFunction[F, args => r] => | ||
type Tupled[X] <: Tuple = X match | ||
case head *: tail => head *: tail | ||
|
||
calls[F, Tupled[args], r](select)(using | ||
tf.asInstanceOf).size | ||
} | ||
|
||
inline def timesF[Fun, Args <: Tuple, R, F[+_]: StubEffect.Mono]( | ||
inline select: T => Fun | ||
): F[Int] = | ||
scala.compiletime.summonFrom { | ||
case given NotGiven[TupledFunction[Fun, _]] => | ||
summon[StubEffect.Mono[F]].unit(times0[Fun](select)) | ||
|
||
case tf: TupledFunction[Fun, args => F[r]] => | ||
type Tupled[X] <: Tuple = X match | ||
case head *: tail => head *: tail | ||
|
||
summon[StubEffect.Mono[F]].unit( | ||
calls[Fun, Tupled[args], F[r]](select)(using | ||
tf.asInstanceOf).size | ||
) | ||
} | ||
|
||
inline def timesIO[Fun, Args <: Tuple, R, F[+_, +_]: StubEffect]( | ||
inline select: T => Fun | ||
): F[Nothing, Int] = | ||
scala.compiletime.summonFrom { | ||
case given NotGiven[TupledFunction[Fun, _]] => | ||
summon[StubEffect[F]].unit(times0[Fun](select)) | ||
|
||
case tf: TupledFunction[Fun, args => F[e, r]] => | ||
type Tupled[X] <: Tuple = X match | ||
case head *: tail => head *: tail | ||
|
||
summon[StubEffect[F]].unit( | ||
calls[Fun, Tupled[args], F[e, r]](select)(using | ||
tf.asInstanceOf).size | ||
) | ||
} | ||
|
||
inline private def times0[F](inline select: T => F): Int = | ||
${ times0Macro[T, F]('{ service }, '{ select }) } | ||
|
||
inline private[backstub] def clear(): Unit = | ||
${ clearMacro[T]('{ service }) } | ||
|
||
private def times0Macro[T: Type, R: Type]( | ||
service: Expr[T], | ||
select: Expr[T => R] | ||
)(using | ||
quotes: Quotes | ||
): Expr[Int] = | ||
new internal.Calls().times0[T, R](service, select) | ||
|
||
private def callsMacro[T: Type, F: Type, Args <: Tuple: Type, R: Type]( | ||
service: Expr[T], | ||
select: Expr[T => F] | ||
)(using | ||
quotes: Quotes | ||
): Expr[List[UntupleOne[Args]]] = | ||
new internal.Calls().calls[T, F, Args, R](service, select) | ||
|
||
private def clearMacro[T: Type]( | ||
service: Expr[T] | ||
)(using | ||
quotes: Quotes | ||
): Expr[Unit] = | ||
new internal.Calls().clearAll[T](service) |
Oops, something went wrong.