-
-
Notifications
You must be signed in to change notification settings - Fork 15
Refactor Result and Exception classes. #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6cc6c0c
38e3ee1
da565c1
d71998d
70fd516
e9c34a4
65a1a2d
699275d
6eac8c5
204a577
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,20 @@ | ||
| package weaver | ||
|
|
||
| import scala.util.Try | ||
|
|
||
| import cats.data.NonEmptyList | ||
| import cats.data.Validated.{ Invalid, Valid } | ||
|
|
||
| sealed trait Result { | ||
| private[weaver] sealed trait Result { | ||
| def formatted: Option[String] | ||
| } | ||
|
|
||
| object Result { | ||
| private[weaver] object Result { | ||
| import Formatter._ | ||
|
|
||
| def fromAssertion(assertion: Expectations): Result = assertion.run match { | ||
| case Valid(_) => Success | ||
| case Invalid(failed) => | ||
| Failures(failed.map(ex => | ||
| Result.Failure(ex.message, Some(ex), ex.locations.toList))) | ||
| Failures.Failure(ex.message, ex, ex.locations))) | ||
| } | ||
|
|
||
| case object Success extends Result { | ||
|
|
@@ -39,19 +37,25 @@ object Result { | |
| } | ||
| } | ||
|
|
||
| final case class Failures(failures: NonEmptyList[Failure]) extends Result { | ||
| final case class Failures(failures: NonEmptyList[Failures.Failure]) | ||
| extends Result { | ||
|
|
||
| def formatted: Option[String] = | ||
| if (failures.size == 1) failures.head.formatted | ||
| else { | ||
| if (failures.size == 1) { | ||
| val failure = failures.head | ||
| Some(formatError(failure.msg, | ||
| Some(failure.source), | ||
| failure.locations.toList, | ||
| Some(0))) | ||
| } else { | ||
|
|
||
| val descriptions = failures.zipWithIndex.map { | ||
| case (failure, idx) => | ||
| import failure._ | ||
|
|
||
| formatDescription( | ||
| if (msg != null && msg.nonEmpty) msg else "Test failed", | ||
| location, | ||
| locations.toList, | ||
| Console.RED, | ||
| s" [$idx] " | ||
| ) | ||
|
|
@@ -61,20 +65,25 @@ object Result { | |
| } | ||
| } | ||
|
|
||
| final case class Failure( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| msg: String, | ||
| source: Option[Throwable], | ||
| location: List[SourceLocation]) | ||
| object Failures { | ||
| final case class Failure( | ||
| msg: String, | ||
| source: Throwable, | ||
| locations: NonEmptyList[SourceLocation]) | ||
| } | ||
|
|
||
| final case class OnlyTagNotAllowedInCI( | ||
| location: SourceLocation) | ||
| extends Result { | ||
|
|
||
| def formatted: Option[String] = | ||
| Some(formatError(msg, source, location, Some(0))) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I walked through the code for I think we can reserve |
||
| Some(formatError("'Only' tag is not allowed when `isCI=true`", | ||
| None, | ||
| List(location), | ||
| Some(0))) | ||
| } | ||
|
|
||
| final case class Exception( | ||
| source: Throwable, | ||
| location: Option[SourceLocation]) | ||
| extends Result { | ||
| final case class Exception(source: Throwable) extends Result { | ||
|
|
||
| def formatted: Option[String] = { | ||
| val description = { | ||
|
|
@@ -85,16 +94,10 @@ object Result { | |
| .fold(className)(m => s"$className: $m") | ||
| } | ||
|
|
||
| val maxStackFrames = sys.props.get("WEAVER_MAX_STACKFRAMES").flatMap(s => | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we removing this altogether ?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We're removing If you're reviewing on GitHub, this is easier to see in the split diff view. |
||
| Try(s.trim.toInt).toOption).getOrElse(50) | ||
|
|
||
| val stackTraceLimit = | ||
| if (location.isDefined) Some(maxStackFrames) else None | ||
|
|
||
| Some(formatError(description, | ||
| Some(source), | ||
| location.toList, | ||
| stackTraceLimit)) | ||
| Nil, | ||
| None)) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -103,15 +106,16 @@ object Result { | |
| def from(error: Throwable): Result = { | ||
| error match { | ||
| case ex: AssertionException => | ||
| Result.Failure(ex.message, Some(ex), ex.locations.toList) | ||
| Failures(NonEmptyList.of(Failures.Failure( | ||
| ex.message, | ||
| ex, | ||
| ex.locations))) | ||
| case ex: IgnoredException => | ||
| Result.Ignored(ex.reason, ex.location) | ||
| Ignored(ex.reason, ex.location) | ||
| case ex: CanceledException => | ||
| Result.Cancelled(ex.reason, ex.location) | ||
| case ex: WeaverException => | ||
| Result.Exception(ex, Some(ex.getLocation)) | ||
| Cancelled(ex.reason, ex.location) | ||
| case other => | ||
| Result.Exception(other, None) | ||
| Exception(other) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,27 @@ | ||
| package weaver | ||
|
|
||
| import scala.util.control.NonFatal | ||
|
|
||
| import cats.data.NonEmptyList | ||
|
|
||
| abstract class WeaverException( | ||
| private[weaver] sealed abstract class WeaverTestException( | ||
| message: String, | ||
| cause: Option[Throwable], | ||
| location: SourceLocation) | ||
| extends RuntimeException(message, cause.orNull) { | ||
|
|
||
| def getLocation: SourceLocation = location | ||
|
|
||
| cause: Option[Throwable] | ||
| ) extends RuntimeException(message, cause.orNull) | ||
|
|
||
| private[weaver] final class AssertionException( | ||
| private[weaver] val message: String, | ||
| private[weaver] val locations: NonEmptyList[SourceLocation]) | ||
| extends WeaverTestException(message, None) { | ||
| private[weaver] def withLocation( | ||
| location: SourceLocation): AssertionException = | ||
| new AssertionException(message, locations.append(location)) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that If users want to create their own
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think we need to put more thought into it : userland should have the ability to recover from throwables resulting from failed assertions to create some combinators for things like "I want this test to eventually pass".
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does need more thought. The case class Expectations(run: ValidatedNel[AssertionException, Unit])What do you think about making the type public, but the Here's an example of a userland retry function using a public def retryThreeTimes(expectationIO: IO[Unit]): IO[Expectations] = {
fs2.Stream
.repeatEval(expectationIO.attemptNarrow[AssertionException])
.takeWhile({
case Left(_) => true
case Right(_) => false
},
takeFailure = true
) // Repeat until the assertion is successful
.take(3) // Repeat at most three times
.scan(success)({
case (_, Right(_)) =>
success
// If an expectation succeeds, the retry operation is successful
case (failedExpectations, Left(assertionException)) =>
failedExpectations.and(Expectations(Validated.invalidNel(assertionException)))
// Accumulate the failures into a single expectation
})
.compile
.lastOrError
}The gist of it is the ability to pattern match on We could expose
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed. While we're at it making breaking changes, we may as well rename it, I was never really happy with
Neither am I.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have any suggestions for the new name? We're using the term
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| sealed abstract class WeaverTestException( | ||
| message: String, | ||
| cause: Option[Throwable], | ||
| location: SourceLocation) | ||
| extends WeaverException(message, cause, location) | ||
|
|
||
| final case class AssertionException( | ||
| message: String, | ||
| locations: NonEmptyList[SourceLocation]) | ||
| extends WeaverTestException(message, None, locations.head) | ||
|
|
||
| final class IgnoredException( | ||
| val reason: Option[String], | ||
| val location: SourceLocation) | ||
| extends WeaverTestException(reason.orNull, None, location) | ||
| private[weaver] final class IgnoredException( | ||
| private[weaver] val reason: Option[String], | ||
| private[weaver] val location: SourceLocation) | ||
| extends WeaverTestException(reason.orNull, None) | ||
|
|
||
| final class CanceledException( | ||
| val reason: Option[String], | ||
| val location: SourceLocation) | ||
| extends WeaverTestException(reason.orNull, None, location) | ||
|
|
||
| object OurException { | ||
|
|
||
| /** | ||
| * Utility for pattern matching. | ||
| */ | ||
| def unapply(ex: Throwable): Option[WeaverException] = ex match { | ||
| case ref: WeaverTestException => | ||
| Some(ref) | ||
| case _ => | ||
| None | ||
| } | ||
| } | ||
|
|
||
| object NotOurException { | ||
|
|
||
| /** | ||
| * Utility for pattern matching. | ||
| */ | ||
| def unapply(ex: Throwable) = ex match { | ||
| case OurException(_) => | ||
| None | ||
| case NonFatal(_) => | ||
| Some(ex) | ||
| case _ => | ||
| None | ||
| } | ||
| } | ||
| private[weaver] final class CanceledException( | ||
| private[weaver] val reason: Option[String], | ||
| private[weaver] val location: SourceLocation) | ||
| extends WeaverTestException(reason.orNull, None) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scaladoc was incorrect.