Skip to content
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
30 changes: 16 additions & 14 deletions modules/core/shared/src/main/scala-2/weaver/ExpectMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,25 @@ private[weaver] trait ExpectMacro {
*
* Use the [[Expectations.Helpers.clue]] function to investigate any failures.
*/
def apply(value: Boolean): Expectations = macro ExpectMacro.applyImpl
def apply(value: Boolean)(implicit loc: SourceLocation): Expectations =
macro ExpectMacro.applyImpl

/**
* Asserts that a boolean value is true and displays a failure message if not.
*
* Use the [[Expectations.Helpers.clue]] function to investigate any failures.
*/
def apply(value: Boolean, message: => String): Expectations =
def apply(value: Boolean, message: => String)(implicit
loc: SourceLocation): Expectations =
macro ExpectMacro.messageImpl

/**
* Asserts that boolean values are all true.
*
* Use the [[Expectations.Helpers.clue]] function to investigate any failures.
*/
def all(values: Boolean*): Expectations = macro ExpectMacro.allImpl
def all(values: Boolean*)(implicit loc: SourceLocation): Expectations =
macro ExpectMacro.allImpl
}

private[weaver] object ExpectMacro {
Expand All @@ -36,9 +39,9 @@ private[weaver] object ExpectMacro {
* If any value evaluates to false, all generated clues are displayed as part
* of the failed expectation.
*/
def allImpl(c: blackbox.Context)(values: c.Tree*): c.Tree = {
def allImpl(c: blackbox.Context)(values: c.Tree*)(
loc: c.Tree): c.Tree = {
import c.universe._
val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree]
val clueMethodSymbol = getClueMethodSymbol(c)
val allExpectations = values.toList.map { value =>
val (cluesName, cluesValDef) = makeClues(c)
Expand All @@ -50,7 +53,7 @@ private[weaver] object ExpectMacro {
makeExpectations(c)(cluesName = cluesName,
cluesValDef = cluesValDef,
value = transformedValue,
sourceLoc = sourceLoc,
loc = loc,
sourceCode = sourceCode,
message = q"None")
}
Expand All @@ -65,9 +68,8 @@ private[weaver] object ExpectMacro {
*/
def messageImpl(c: blackbox.Context)(
value: c.Tree,
message: c.Tree): c.Tree = {
message: c.Tree)(loc: c.Tree): c.Tree = {
import c.universe._
val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree]
val sourcePos = c.enclosingPosition
val sourceCode =
new String(sourcePos.source.content.slice(sourcePos.start, sourcePos.end))
Expand All @@ -79,7 +81,7 @@ private[weaver] object ExpectMacro {
makeExpectations(c)(cluesName = cluesName,
cluesValDef = cluesValDef,
value = transformedValue,
sourceLoc = sourceLoc,
loc = loc,
sourceCode = sourceCode,
message = q"Some($message)")
}
Expand All @@ -97,10 +99,10 @@ private[weaver] object ExpectMacro {
* After the value is evaluated, the [[Clues]] collection is used to contruct
* [[Expectations]].
*/
def applyImpl(c: blackbox.Context)(value: c.Tree): c.Tree = {
def applyImpl(c: blackbox.Context)(value: c.Tree)(
loc: c.Tree): c.Tree = {

import c.universe._
val sourceLoc = new weaver.macros.Macros(c).fromContext.asInstanceOf[c.Tree]
val sourcePos = c.enclosingPosition
val sourceCode =
new String(sourcePos.source.content.slice(sourcePos.start, sourcePos.end))
Expand All @@ -113,7 +115,7 @@ private[weaver] object ExpectMacro {
makeExpectations(c)(cluesName = cluesName,
cluesValDef = cluesValDef,
value = transformedValue,
sourceLoc = sourceLoc,
loc = loc,
sourceCode = sourceCode,
message = q"None")
}
Expand All @@ -123,12 +125,12 @@ private[weaver] object ExpectMacro {
cluesName: c.TermName,
cluesValDef: c.Tree,
value: c.Tree,
sourceLoc: c.Tree,
loc: c.Tree,
sourceCode: String,
message: c.Tree): c.Tree = {
import c.universe._
val block =
q"$cluesValDef; _root_.weaver.internals.Clues.toExpectations($sourceLoc, Some($sourceCode), $message, $cluesName, $value)"
q"$cluesValDef; _root_.weaver.internals.Clues.toExpectations($loc, Some($sourceCode), $message, $cluesName, $value)"
val untyped = c.untypecheck(block)
val retyped = c.typecheck(untyped, pt = c.typeOf[Expectations])
retyped
Expand Down
36 changes: 19 additions & 17 deletions modules/core/shared/src/main/scala-3/weaver/ExpectMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ private[weaver] trait ExpectMacro {
*
* Use the [[Expectations.Helpers.clue]] function to investigate any failures.
*/
inline def apply(assertion: Clues ?=> Boolean): Expectations =
${ ExpectMacro.applyImpl('assertion) }
inline def apply(assertion: Clues ?=> Boolean)(using
loc: SourceLocation): Expectations =
${ ExpectMacro.applyImpl('assertion, 'loc) }

/**
* Asserts that a boolean value is true and displays a failure message if
Expand All @@ -25,16 +26,17 @@ private[weaver] trait ExpectMacro {
*/
inline def apply(
assertion: Clues ?=> Boolean,
message: => String): Expectations =
${ ExpectMacro.applyMessageImpl('assertion, 'message) }
message: => String)(using loc: SourceLocation): Expectations =
${ ExpectMacro.applyMessageImpl('assertion, 'message, 'loc) }

/**
* Asserts that boolean values are all true.
*
* Use the [[Expectations.Helpers.clue]] function to investigate any failures.
*/
inline def all(inline assertions: (Clues ?=> Boolean)*): Expectations =
${ ExpectMacro.allImpl('assertions) }
inline def all(inline assertions: (Clues ?=> Boolean)*)(using
loc: SourceLocation): Expectations =
${ ExpectMacro.allImpl('assertions, 'loc) }
}
private[weaver] object ExpectMacro {

Expand All @@ -49,18 +51,18 @@ private[weaver] object ExpectMacro {
* After the assertion is evaluated, the [[Clues]] collection is used to
* contruct [[Expectations]].
*/
def applyImpl[T: Type](assertion: Expr[Clues ?=> Boolean])(using
q: Quotes): Expr[Expectations] = {
def applyImpl[T: Type](
assertion: Expr[Clues ?=> Boolean],
loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = {
import q.reflect.*
val sourceLoc = weaver.macros.fromContextImpl(using q)
// The compiler doesn't return the correct position information
// for `assertion.asTerm.pos`. Use the position of the entire
// expect statement instead.
val sourceCode = Expr(Position.ofMacroExpansion.sourceCode)
'{
val clues = new Clues
val result = ${ assertion }(using clues)
Clues.toExpectations($sourceLoc,
Clues.toExpectations($loc,
sourceCode = $sourceCode,
message = None,
clues,
Expand All @@ -76,14 +78,14 @@ private[weaver] object ExpectMacro {
*/
def applyMessageImpl[T: Type](
assertion: Expr[Clues ?=> Boolean],
message: => Expr[String])(using q: Quotes): Expr[Expectations] = {
message: => Expr[String],
loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = {
import q.reflect.*
val sourceLoc = weaver.macros.fromContextImpl(using q)
val sourceCode = Expr(Position.ofMacroExpansion.sourceCode)
'{
val clues = new Clues
val result = ${ assertion }(using clues)
Clues.toExpectations($sourceLoc,
Clues.toExpectations($loc,
sourceCode = $sourceCode,
message = Some($message),
clues,
Expand All @@ -97,10 +99,10 @@ private[weaver] object ExpectMacro {
* If any assertion evaluates to false, all generated clues are displayed as
* part of the failed expectation.
*/
def allImpl[T: Type](assertions: Expr[Seq[(Clues ?=> Boolean)]])(using
q: Quotes): Expr[Expectations] = {
def allImpl[T: Type](
assertions: Expr[Seq[(Clues ?=> Boolean)]],
loc: Expr[SourceLocation])(using q: Quotes): Expr[Expectations] = {
import q.reflect.*
val sourceLoc = weaver.macros.fromContextImpl(using q)
val sourceCodes: Expr[List[Option[String]]] = Expr(assertions match {
case Varargs(exprs) => exprs.toList.map(_.asTerm.pos.sourceCode)
case _ => Nil
Expand All @@ -111,7 +113,7 @@ private[weaver] object ExpectMacro {
val clues = new Clues
val result = assertion(using clues)
val sourceCode = ${ sourceCodes }.get(index).flatten
Clues.toExpectations($sourceLoc,
Clues.toExpectations($loc,
sourceCode = sourceCode,
message = None,
clues,
Expand Down
18 changes: 9 additions & 9 deletions modules/framework-cats/shared/src/test/scala/DogFoodTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ object DogFoodTests extends IOSuite {
| of
| multiline
| (failure)
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:34)
| assertion failed (src/main/DogFoodTests.scala:5)
Copy link
Contributor Author

@zainab-ali zainab-ali Jul 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests now display the implicit source location defined in the test suite object.

|
| expect(clue(x) == y)
|
Expand Down Expand Up @@ -317,7 +317,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (failure) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:83)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(clue(x) == clue(y))
|
Expand All @@ -339,7 +339,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (nested) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:89)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(clue(List(clue(x), clue(y))) == List(x, x))
|
Expand All @@ -363,7 +363,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (map) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:95)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(List(x, y).map(v => clue(v)) == List(x, x))
|
Expand All @@ -384,7 +384,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (all) 0ms
| [0] assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:102)
| [0] assertion failed (src/main/DogFoodTests.scala:5)
| [0]
| [0] clue(x) == clue(y)
| [0]
Expand All @@ -393,7 +393,7 @@ object DogFoodTests extends IOSuite {
| [0] y: Int = 2
| [0] }
|
| [1] assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:102)
| [1] assertion failed (src/main/DogFoodTests.scala:5)
| [1]
| [1] clue(y) == clue(z)
| [1]
Expand All @@ -414,7 +414,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (show) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:109)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(clue(x) == clue(y))
|
Expand All @@ -437,7 +437,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (show-from-to-string) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:119)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(clue(x) == clue(y))
|
Expand All @@ -459,7 +459,7 @@ object DogFoodTests extends IOSuite {
val expected =
s"""
|- (helpers) 0ms
| assertion failed (modules/framework-cats/shared/src/test/scala/Meta.scala:128)
| assertion failed (src/main/DogFoodTests.scala:5)
|
| expect(CustomHelpers.clue(x) == otherclue(y) || x == clue(z))
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object TracingTests extends SimpleIOSuite {
val locations = e.head.locations.toList
val paths = locations.map(_.fileRelativePath).map(standardise)
forEach(paths)(p => expect(p == thisFile)) &&
expect(locations.map(_.line).distinct.size == 4)
expect(locations.map(_.line).distinct.size == 3)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behaviour has changed.

Previously, the locations would be:

    val result = isOdd(2) // Line 24
      .traced(here) // Line 25
      .traced(here) // Line 26
      
 ...
   def isOdd(i: Int)(implicit loc: SourceLocation): Expectations =
    expect(i % 2 == 1).traced(loc) // Line 39, since `expect` would capture it's own location

With this changeset, expect doesn't capture its location, and uses the source location passed to isOdd instead. This means we no longer get line 39 as part of the trace.

This aligns the behaviour to other expectations. We could replace expect(1 % 2 == 1) with expect.eql(1 % 2, 1) and the trace would still have three locations.

case Valid(_) => failure("Should have been invalid")
}
}
Expand Down