-
Notifications
You must be signed in to change notification settings - Fork 91
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
Add support for Hedgehog property testing #78
Conversation
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.
Thank you for this contribution! I'm excited to try this out myself. I have never enjoyed using ScalaCheck so much and I have heard great things about the Haskell implementation of Hedgehog.
Please let us know if there's anything we can do to unblock this PR
|
||
def property( | ||
name: String, | ||
config: PropertyConfig |
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.
To reduce the number of method overloads, would it make sense to pass the property config via tags instead?
property("name".tag(customPropertyConfig))
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 looked in to that but note doing so would require the ability to convert property config to/from String, unless I'm misunderstanding.
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.
BTW, I used Conversion
to avoid the need for importing language.implicitConversions
when working with Dotty. Maybe we should do the same for Conversion[String, TestOptions]
?
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.
It's possible to create a tag that contains custom values like this
case class HedgehogConfig(config: PropertyConfig) extends Tag("HedgehogConfig")
See https://scalameta.org/munit/docs/tests.html#customize-evaluation-of-tests-with-tags
I didn't know about the Conversion
trick to avoid the import. That's handy. However, I think it's good to keep the name: String
method overload to make the sources more approachable for Scala beginners
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.
Ah, cool, I'll update the overloads.
Re: Conversion
, I'm getting warnings with munit 0.7.1 when calling test("name".ignore)
. I was wondering if we could use Conversion
to avoid such warnings. I'll open a separate issue on that.
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.
Please do!
sharedSettings, | ||
crossScalaVersions := List(scala213, scala212, scala211, dotty), | ||
resolvers += "bintray-scala-hedgehog".at( | ||
"https://dl.bintray.com/hedgehogqa/scala-hedgehog" |
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.
It would be nice to have a hedgehog release on Maven Central.
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.
Agreed, there's an open issue for it and lead maintainer has been working it but not sure how close it is to being merged.
* Checks the supplied `Property[Result]`, throwing a `HedgehogFailException` | ||
* if the property was falsified. | ||
*/ | ||
def check( |
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.
would it make sense to make the check
methods private?
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 made it public b/c in scodec-bits, I had some cases where I wanted to call check
multiple times in a single test. That's not really necessary though so I'm happy with this being private.
lazy val propertyConfig: PropertyConfig = PropertyConfig.default | ||
lazy val propertySeed: Long = System.currentTimeMillis() |
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.
Not a huge deal, but I would like to standardize these with respect to the scalacheck module. A few differences I can spot:
- we use
protected
inScalaCheckSuite
(maybe redundant, but I think it makes sense) - we use
def
for the config, to allow overriding with anything (def
,val
,lazy val
) instead of forcing to useval
in the override - we prefix the variables using
scalaCheck
as inscalaCheckTestParameters
. Since these modules are defined astrait
I wanted to maintain some kind of namespace to avoid potential clashes when we add more modules that can be mixed and matched.
I'm fine to standardize either way, as long as it's consistent.
What do you think @mpilquist?
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.
👍 on protected
and prefixing names. Re: def versus val, I used lazy val in order to guarantee the same seed is used in all tests in a suite. I'm okay with using a def instead though. No strong opinion.
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.
def versus val, I used lazy val in order to guarantee the same seed is used in all tests in a suite
👍 makes sense if that's the intended semantics. Does the same reasoning apply to the config?
lazy val propertyConfig: PropertyConfig = PropertyConfig.default | ||
lazy val propertySeed: Long = System.currentTimeMillis() |
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.
Also, does it make sense to memoize the seed? IIUC in ScalaCheck the seed is generated fresh for each prop
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.
Minor, but would it make sense to use
lazy val propertyConfig: PropertyConfig = PropertyConfig.default | |
lazy val propertySeed: Long = System.currentTimeMillis() | |
lazy val propertySeed: Seed = Seed.fromTime() |
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.
Yeah I'd like to use Seed
but need to figure out a way to convert an arbitrary seed to/from a string for reporting.
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 ended up using String
myself in ScalaCheckSuite
, since it's then easier for the user to set it when trying to reproduce a failure.
See the updated docs at https://scalameta.org/munit/docs/integrations/scalacheck.html#reproducing-a-property-failure
@@ -7,7 +7,7 @@ class ScalaCheckFrameworkSuite extends ScalaCheckSuite { | |||
|
|||
// NOTE(gabro): this is needed for making the test output stable for the failed test below. | |||
// It also serves as a test for overriding these parameters. | |||
override def scalaCheckTestParameters = | |||
override def scalaCheckTestParameters: org.scalacheck.Test.Parameters = |
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.
was this required by Scalafix? I must have missed 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.
anyway, if you rebase on master that's gone now
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.
Yep
@@ -42,8 +42,7 @@ class HedgehogFrameworkSuite extends HedgehogSuite { | |||
} | |||
|
|||
property( | |||
"custom config", | |||
config = propertyConfig.copy(testLimit = SuccessCount(1000)) | |||
"custom config".tag(HedgehogConfig(hedgehogPropertyConfig.copy(testLimit = SuccessCount(1000)))) |
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 is quite a mouthful, I wonder if we want to add custom tags for the most common configuration options? Just brainstorming, it would be nice if the user could do something like this instead 🤔
"custom config".tag(HedgehogConfig(hedgehogPropertyConfig.copy(testLimit = SuccessCount(1000)))) | |
"custom config".tag(TestLimit(1000)) |
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.
If we go down this route I would still namespace the tags
"custom config".tag(HedgehogConfig(hedgehogPropertyConfig.copy(testLimit = SuccessCount(1000)))) | |
"custom config".tag(HedgehogTestLimit(1000)) |
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 is totally optional BTW, just a suggestion. I'm happy to merge this PR as is. I believe we can update this PR to skip projects where the cross-build is missing
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 agree it's too verbose as-is. I'll update like you suggested. I think I'll add support for passing a seed as a tag as well.
I can also remove the propertyF
stuff for now (until Hedgehog merges support for that).
One note about tags - figuring out the supported tags is not discoverable via auto-completion. We could address that via extension methods - what do you think?
Closing this PR since it's been inactive for a while. Don't hesitate to reopen if you want to pick this up again! |
Opening this draft PR for feedback on overall API. Besides review comments, this also needs the following updates before merging: