diff --git a/modules/core/shared/src/main/scala/weaver/Formatter.scala b/modules/core/shared/src/main/scala/weaver/Formatter.scala index 6da10ab4..1823a04b 100644 --- a/modules/core/shared/src/main/scala/weaver/Formatter.scala +++ b/modules/core/shared/src/main/scala/weaver/Formatter.scala @@ -1,6 +1,7 @@ package weaver import scala.concurrent.duration.FiniteDuration +import scala.util.Try import cats.data.Chain import cats.syntax.show._ @@ -58,6 +59,9 @@ object Formatter { import outcome._ import TestOutcome.{ Verbose, Summary } + val maxStackFrames = sys.props.get("WEAVER_MAX_STACKFRAMES").flatMap(s => + Try(s.trim.toInt).toOption).getOrElse(50) + val builder = new StringBuilder() val newLine = '\n' builder.append(formatResultStatus(name, result, outcome.duration)) @@ -100,6 +104,19 @@ object Formatter { } builder.append(newLine) + entry.cause.map { t => + builder.append(TAB4) + builder.append(t.toString) + builder.append(newLine) + + TestErrorFormatter.formatStackTrace(t, Some(maxStackFrames)).map { + line => + builder.append(TAB4.prefix * 2) + builder.append(line) + builder.append(newLine) + } + } + () } diff --git a/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala b/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala index db404b2f..e8540945 100644 --- a/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala +++ b/modules/framework-cats/shared/src/test/scala/DogFoodTests.scala @@ -121,6 +121,30 @@ object DogFoodTests extends IOSuite { } } + test("failures with exceptions in logs display them correctly") { + _.runSuite(Meta.SucceedsWithErrorInLogs).map { + case (logs, _) => + val expected = + """ + |- failure 0ms + | expected (src/main/DogFoodTests.scala:5) + | + | [ERROR] 12:54:35 [DogFoodTests.scala:5] error + | weaver.framework.test.Meta$CustomException: surfaced error + | DogFoodTests.scala:15 my.package.MyClass#MyMethod + | DogFoodTests.scala:20 my.package.ClassOfDifferentLength#method$new$1 + | cats.effect.internals.<...> + | java.util.concurrent.<...> + |""".stripMargin.trim + + exists(extractLogEventAfterFailures(logs) { + case LoggedEvent.Error(msg) => msg + }) { actual => + expect.same(actual, expected) + } + } + } + test("failures with multi-line test name are rendered correctly") { _.runSuite(Meta.Rendering).map { case (logs, _) => diff --git a/modules/framework-cats/shared/src/test/scala/Meta.scala b/modules/framework-cats/shared/src/test/scala/Meta.scala index 90ddd5c8..58709e49 100644 --- a/modules/framework-cats/shared/src/test/scala/Meta.scala +++ b/modules/framework-cats/shared/src/test/scala/Meta.scala @@ -111,6 +111,24 @@ object Meta { } } + object SucceedsWithErrorInLogs extends SimpleIOSuite { + override implicit protected def effectCompat: UnsafeRun[IO] = + SetTimeUnsafeRun + implicit val sourceLocation: SourceLocation = TimeCop.sourceLocation + + loggedTest("failure") { log => + for { + _ <- log.error( + "error", + cause = CustomException( + "surfaced error", + withSnips = true + ) + ) + } yield failure("expected") + } + } + case class CustomException( str: String, causedBy: Exception = null,