-
Notifications
You must be signed in to change notification settings - Fork 8
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
Use initial Seed in CheckConfig and failure message. #39
Conversation
case class CheckConfig( | ||
minimumSuccessful: Int, | ||
maximumDiscardRatio: Int, | ||
maximumGeneratorSize: Int, | ||
perPropertyParallelism: Int, | ||
initialSeed: Option[Long] | ||
initialSeed: Option[Seed] |
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.
I noticed that this was already implemented in disneystreaming/weaver-test#633, which also future-proofs the CheckConfig
datatype.
Perhaps we should retarget that instead?
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.
The future-proofing of CheckConfig
would be greatly appreciated. Feel free to retarget it need be (or copy and credit the original author)
@@ -106,22 +106,18 @@ trait Checkers { | |||
|
|||
private def forall_[A: Show](gen: Gen[A], f: A => F[Expectations])( | |||
implicit loc: SourceLocation): F[Expectations] = { |
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.
The changes here are a bit dense. To summarize:
paramStream: fs2.Stream[F, (Gen.Parameters, Seed)]
has a fixedGen.Parameters
value, so can be better expressed as(Gen.Parameters, Stream[F, Seed])
.
Since we need the first seed, I've expressed it as:val (params, initialSeed) = startParams seedStream(initialSeed)
mapAccumulate(...).map(_.1)
can be reduced toscan
.takeRight(1).compile.last
can be reduced tocompile.last
.
.takeWhile(_.shouldContinue(config), takeFailure = true) | ||
.takeRight(1) // getting the first error (which finishes the stream) | ||
.compile | ||
.last | ||
.map { (x: Option[Status[A]]) => |
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 stream will always contain at least one value due to the properties of scan
, so the None
case will never be hit.
We could use lastOrError
here instead. What's your preference?
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.
lastOrError
is good 👍
private def paramStream: fs2.Stream[F, (Gen.Parameters, Seed)] = { | ||
val initial = startSeed( | ||
private def startParams: (Gen.Parameters, Seed) = { | ||
startSeed( |
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.
Question: do we need the startSeed
function to be public? It seems a bit odd for users to want to override it.
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.
as long as the user as a way of overriding the seed to reproduce failed tests deterministically, it's fine (ie the startX
function shouldn't be public)
} | ||
|
||
val expectedMessage = | ||
s"Property test failed on try $attempt with seed $seed and input $value" | ||
|
||
s"""Property test failed on try $attempt with seed Seed.fromBase64("$seed") and input $value""" |
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.
We should probably improve the message by displaying the method override necessary for the seed value to be injected in the suite (I think munit does that). That'd improve the UX
|
||
override def productArity: Int = 5 | ||
|
||
override def productElement(n: Int): Any = n match { |
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.
mmm, I had not remembered the implementation on the PR you merged in, not a fan of how it was done here.
It's fine to use a case-class implementation (to get toString/equals/hashCode), as long as the constructor is made private (to hide copy methods), the default extractor is overriden as described here, and an explicit apply
method is provided in the companion. It's less error-prone.
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.
Thanks! That article is incredibly valuable.
Great work 👍, just one last nitpick on the config data class |
@@ -3,104 +3,37 @@ package scalacheck | |||
|
|||
import org.scalacheck.rng.Seed | |||
|
|||
final class CheckConfig( |
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.
you still need the unapply
override in the companion object, which signals to the compiler that the traditional case class extractor should be eluded. Otherwise adding fields changes the shape of the generated unapply in non bincompat ways.
IE private def unapply(c: CheckConfig): Some[CheckConfig] = Some(c)
This PR:
org.scalacheck.rng.Seed
instead ofLong
disneystreaming/weaver-test#632Checkers.forall_
logic, in particular the stream.