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

Add support for Hedgehog property testing #78

Closed
wants to merge 8 commits into from

Conversation

mpilquist
Copy link

@mpilquist mpilquist commented Mar 28, 2020

Opening this draft PR for feedback on overall API. Besides review comments, this also needs the following updates before merging:

Copy link
Member

@olafurpg olafurpg left a 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
Copy link
Member

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))

Copy link
Author

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.

Copy link
Author

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]?

Copy link
Member

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

Copy link
Author

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.

Copy link
Member

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

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.

Copy link
Author

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

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?

Copy link
Author

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.

Comment on lines 32 to 33
lazy val propertyConfig: PropertyConfig = PropertyConfig.default
lazy val propertySeed: Long = System.currentTimeMillis()
Copy link
Member

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 in ScalaCheckSuite (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 use val in the override
  • we prefix the variables using scalaCheck as in scalaCheckTestParameters. Since these modules are defined as trait 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?

Copy link
Author

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.

Copy link
Member

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?

Comment on lines 32 to 33
lazy val propertyConfig: PropertyConfig = PropertyConfig.default
lazy val propertySeed: Long = System.currentTimeMillis()
Copy link
Member

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

Copy link
Member

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

Suggested change
lazy val propertyConfig: PropertyConfig = PropertyConfig.default
lazy val propertySeed: Long = System.currentTimeMillis()
lazy val propertySeed: Seed = Seed.fromTime()

instead?

See https://github.com/hedgehogqa/scala-hedgehog/blob/master/core/shared/src/main/scala/hedgehog/core/Seed.scala#L48-L49

Copy link
Author

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.

Copy link
Member

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 =
Copy link
Member

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

Copy link
Member

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

Copy link
Author

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))))
Copy link
Member

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 🤔

Suggested change
"custom config".tag(HedgehogConfig(hedgehogPropertyConfig.copy(testLimit = SuccessCount(1000))))
"custom config".tag(TestLimit(1000))

Copy link
Member

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

Suggested change
"custom config".tag(HedgehogConfig(hedgehogPropertyConfig.copy(testLimit = SuccessCount(1000))))
"custom config".tag(HedgehogTestLimit(1000))

Copy link
Member

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

Copy link
Author

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?

Base automatically changed from master to main January 20, 2021 08:13
@olafurpg
Copy link
Member

Closing this PR since it's been inactive for a while. Don't hesitate to reopen if you want to pick this up again!

@olafurpg olafurpg closed this Oct 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants