Skip to content
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

Merged
merged 7 commits into from
May 3, 2024

Conversation

zainab-ali
Copy link
Contributor

case class CheckConfig(
minimumSuccessful: Int,
maximumDiscardRatio: Int,
maximumGeneratorSize: Int,
perPropertyParallelism: Int,
initialSeed: Option[Long]
initialSeed: Option[Seed]
Copy link
Contributor Author

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?

Copy link
Collaborator

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] = {
Copy link
Contributor Author

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 fixed Gen.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 to scan.
  • takeRight(1).compile.last can be reduced to compile.last.

.takeWhile(_.shouldContinue(config), takeFailure = true)
.takeRight(1) // getting the first error (which finishes the stream)
.compile
.last
.map { (x: Option[Status[A]]) =>
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 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?

Copy link
Collaborator

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(
Copy link
Contributor Author

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.

Copy link
Collaborator

@Baccata Baccata Apr 26, 2024

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"""
Copy link
Collaborator

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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@Baccata
Copy link
Collaborator

Baccata commented May 3, 2024

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(
Copy link
Collaborator

@Baccata Baccata May 3, 2024

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)

@Baccata Baccata merged commit 52916a5 into typelevel:main May 3, 2024
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants