Skip to content

Commit

Permalink
Merge pull request #24 from zainab-ali/comparison-trait
Browse files Browse the repository at this point in the history
Add Comparison trait.
  • Loading branch information
Baccata authored Apr 8, 2024
2 parents 5fa632e + 5e28248 commit b4f318a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 39 deletions.
77 changes: 77 additions & 0 deletions modules/core/shared/src/main/scala/weaver/Comparison.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package weaver

import cats.Eq
import cats.Show
import com.eed3si9n.expecty._
import scala.annotation.implicitNotFound

/**
* A type class used to compare two instances of the same type and construct an
* informative report.
*
* If the comparison succeeds with [[Result.Success]] then no report is printed.
* If the comparison fails with [[Result.Failure]], then the report is printed
* with the test failure.
*
* The report is generally a diff of the `expected` and `found` values. It may
* use ANSI escape codes to add color.
*/
@implicitNotFound("Could not find an implicit Comparison[${A}]. Does ${A} have an associated cats.Eq[${A}] instance?")
trait Comparison[A] {

def diff(expected: A, found: A): Comparison.Result
}

object Comparison {
sealed trait Result
object Result {
case object Success extends Result
case class Failure(report: String) extends Result
}

/**
* Create a [[Comparison]] instance from an [[cats.kernel.Eq]] implementation.
*
* Uses the [[cats.Show]] instance or [[cats.Show.fromToString]] to construct
* a string diff of the `expected` and `found` values on failure.
*/
implicit def fromEq[A](
implicit eqv: Eq[A],
showA: Show[A] = Show.fromToString[A]
): Comparison[A] = {
new Comparison[A] {
def diff(expected: A, found: A): Result = {
if (eqv.eqv(found, expected)) {
Result.Success
} else {
val expectedLines = showA.show(expected).linesIterator.toSeq
val foundLines = showA.show(found).linesIterator.toSeq
val report = DiffUtil
.mkColoredLineDiff(expectedLines, foundLines)
.linesIterator
.toSeq
.map(str => Console.RESET.toString + str)
.mkString("\n")
Result.Failure(report)
}
}
}
}

/**
* Create a [[Comparison]] instance from a `diff` implementation.
*/
def instance[A](f: (A, A) => Option[String]): Comparison[A] =
new Comparison[A] {
def diff(expected: A, found: A): Result = f(expected, found) match {
case None => Result.Success
case Some(report) => Result.Failure(report)
}
}

/**
* Create a [[Comparison]] instance from a `diff` implementation.
*/
def instance[A](f: PartialFunction[(A, A), String]): Comparison[A] =
instance((expected, found) => f.lift((expected, found)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,21 @@ import cats.Show
import cats.data.{ NonEmptyList, Validated }
import cats.kernel.Eq

import com.eed3si9n.expecty._

private[weaver] trait ExpectSame {

def eql[A](
expected: A,
found: A)(
implicit eqA: Eq[A],
showA: Show[A] = Show.fromToString[A],
implicit comparisonA: Comparison[A],
loc: SourceLocation): Expectations = {

if (eqA.eqv(expected, found))
Expectations(Validated.validNel(()))
else {
val header = "Values not equal:"

val expectedLines = showA.show(expected).linesIterator.toSeq
val foundLines = showA.show(found).linesIterator.toSeq
val sourceLocs = NonEmptyList.of(loc)
val diff = DiffUtil
.mkColoredLineDiff(expectedLines, foundLines)
.linesIterator
.toSeq
.map(str => Console.RESET.toString + str)
.mkString("\n")

Expectations(
Validated.invalidNel[AssertionException, Unit](
new AssertionException(header + "\n\n" + diff, sourceLocs)))
comparisonA.diff(expected, found) match {
case Comparison.Result.Success => Expectations(Validated.validNel(()))
case Comparison.Result.Failure(report) =>
val header = "Values not equal:"
val sourceLocs = NonEmptyList.of(loc)
Expectations(
Validated.invalidNel[AssertionException, Unit](
new AssertionException(header + "\n\n" + report, sourceLocs)))
}
}

Expand All @@ -43,7 +29,8 @@ private[weaver] trait ExpectSame {
def same[A](
expected: A,
found: A)(
implicit eqA: Eq[A] = Eq.fromUniversalEquals[A],
showA: Show[A] = Show.fromToString[A],
loc: SourceLocation): Expectations = eql(expected, found)
implicit comparisonA: Comparison[A] =
Comparison.fromEq[A](Eq.fromUniversalEquals, Show.fromToString),
loc: SourceLocation): Expectations =
eql(expected, found)(comparisonA, loc)
}
31 changes: 28 additions & 3 deletions modules/framework-cats/shared/src/test/scala/DogFoodTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,41 @@ object DogFoodTests extends IOSuite {
}

test(
"expect.same delegates to show when an instance is found") {
"expect.eql delegates to Comparison show when an instance is found") {
_.runSuite(Meta.Rendering).map {
case (logs, _) =>
val actual =
extractLogEventAfterFailures(logs) {
case LoggedEvent.Error(msg) if msg.contains("(cats.Show)") => msg
case LoggedEvent.Error(msg) if msg.contains("(eql Comparison)") =>
msg
}.get

val expected = """
|- (cats.Show) 0ms
|- (eql Comparison) 0ms
| Values not equal: (src/main/DogFoodTests.scala:5)
|
| Foo { | Foo {
| s: foo | s: foo
| i: [1] | i: [2]
| } | }
""".stripMargin.trim

expect.same(actual, expected)
}
}

test(
"expect.same delegates to Comparison show when an instance is found") {
_.runSuite(Meta.Rendering).map {
case (logs, _) =>
val actual =
extractLogEventAfterFailures(logs) {
case LoggedEvent.Error(msg) if msg.contains("(same Comparison)") =>
msg
}.get

val expected = """
|- (same Comparison) 0ms
| Values not equal: (src/main/DogFoodTests.scala:5)
|
| Foo { | Foo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ object ExpectationsTests extends SimpleIOSuite {
})
}

pureTest("expect.same respects cats.kernel.Eq") {
pureTest("expect.eql respects cats.kernel.Eq") {
implicit val eqInt: Eq[Int] = Eq.allEqual
expect.same(0, 1)
expect.eql(0, 1)
}

pureTest("expect.eql respects weaver.Comparison") {
implicit val comparison: Comparison[Int] = Comparison.fromEq(Eq.allEqual)
expect.eql(0, 1)
}

pureTest("when success") {
Expand Down
21 changes: 13 additions & 8 deletions modules/framework-cats/shared/src/test/scala/Meta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,26 @@ object Meta {
cancel("I was cancelled :(")
}

pureTest("(cats.Show)") {
import cats.Show
case class Foo(s: String, i: Int)
object Foo {
implicit val show: Show[Foo] = Show.show[Foo] {
case Foo(s, i) =>
s"""
import cats.Show
case class Foo(s: String, i: Int)
object Foo {
val show: Show[Foo] = Show.show[Foo] {
case Foo(s, i) =>
s"""
|Foo {
| s: ${Show[String].show(s)}
| i: ${Show[Int].show(i)}
|}
""".stripMargin.trim()
}
}
implicit val comparison: Comparison[Foo] =
Comparison.fromEq[Foo](cats.Eq.fromUniversalEquals, show)
}

pureTest("(eql Comparison)") {
expect.eql(Foo("foo", 1), Foo("foo", 2))
}
pureTest("(same Comparison)") {
expect.same(Foo("foo", 1), Foo("foo", 2))
}
}
Expand Down

0 comments on commit b4f318a

Please sign in to comment.