Expose hedgehog function for testing#158
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the public Test API by exposing a new hedgehog test constructor that accepts a raw Hedgehog.Gen, enabling consumers to use Hedgehog’s native generator combinators directly (e.g., Gen.recursive) while still running through the existing Test runner.
Changes:
- Added
hedgehog :: Hedgehog.Gen a -> Text -> (a -> Expectation) -> TesttoTest.Internal. - Refactored
fuzz,fuzz2, andfuzz3to pass the underlyingHedgehog.Gendirectly intofuzzBody. - Re-exported
hedgehogfrom the publicTestmodule.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| nri-prelude/src/Test/Internal.hs | Introduces hedgehog and adjusts fuzzBody/fuzz helpers to operate on Hedgehog.Gen directly. |
| nri-prelude/src/Test.hs | Re-exports Internal.hedgehog as part of the public API. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- | Run a fuzz test using a hedgehog generator | ||
| hedgehog :: (Stack.HasCallStack, Show a) => Hedgehog.Gen a -> Text -> (a -> Expectation) -> Test | ||
| hedgehog gen name expectation = |
|
💭 Why not wrap Our API tries to expose things with sane defaults (e.g. |
|
I thought about it! One thing I'm caught up on is I'm not sure what the intent of the design of the library is at this point. Originally, along with most things in nri-prelude, I assume the intent was to mimic the corresponding Elm library as closely as possible. The problem is that I guess I think I would prefer the free-for-all To elaborate on the constructive philosophy a little bit: I need to create strings in specific shapes (e.g. conforming to JSON Pointer specification). This would be much easier to do with a primitive like text :: MonadGen m => Range Int -> m Char -> m TextTrying to do this with nri-preludes' Could we wrap a bunch of functions to give us more flexibility? Sure. But at a certain point why not just use the underlying library itself? |
omnibs
left a comment
There was a problem hiding this comment.
Context
One thing I'm caught up on is I'm not sure what the intent of the design of the library is at this point. Originally, along with most things in nri-prelude, I assume the intent was to mimic the corresponding Elm library as closely as possible.
As with other nri-prelude things, the goal was creating a subset of the haskell ecosystem that's easy to use and learn, explicit, consistent, predictable, and safe. Part of the effort Jasper, Stö and Glass put into this was keeping footguns out of our codebase.
We should feel free to extend nri-prelude keeping these principles in mind, if we think something here is half-baked.
I don't know hedgehog well enough to point at footguns folks were trying to keep out when designing Fuzz, but from the list of things Claude says it can't do, one seemingly intentional choice was blocking monadic generators.
Practical feedback
It seems like we could re-expose everything you're missing, without bringing any footguns onboard, right?
I won't oppose merging Fuzz.hedgehog, but I worry folks Claudes will reach out for it even when not necessary and write unbounded lists and bad do block generators.
Can you help keep an eye on how this ends up being used?
|
I'll tell you what, why don't I implement my json-render spec fuzzer in terms of hedgehog and then I'll have a clearer picture of what (if any) things we would want to bring into our PD-3068: Consider re-exposing key hedgehog functions into our |
This PR exposes a new function from the
Testmodule calledhedgehog. This function is almost identical to thefuzzfunction except that we accept theGentype that underlies theFuzzopaque type:This allows consumers of the library to use the raw powerful Hedgehog combinators if they need to instead of our Elm-like wrappers.
Why do we need this?
I'm trying to write a Fuzzer for a recursive type that is an extension of a JSON object (for json-render):
A first attempt at a fuzzer might look like:
This of course will cause an infinite recursion. (It's odd that this library doesn't expose lazy like Elm's does).
The documentation from
frequencysuggests manually passing a depth parameter:This still has exponential behavior in terms of depth however. This leads to trees that are wide but not deep as leave can have an
ObjectorArraywith a potentially largelist.Hedgehog provides a
recursivefunction that reduces theSizeparameter by half. This ensures that the size of lists continues to shrink as we recuse and when size hits zero evenutally then we stop recursing entirely.I'm currently hitting hangs and system out of memory errors when running
fuzzExp 4.Aside from recursion issues, I also find it frustrating how I cannot build up fuzzers constructively. In one case I only want a string of alpha numeric characters and AFAICT the only way to do that is build up a
Textand map into it to remove unwanted things. Aside from being harder than it needs to be (see HedgehogsalphaNumandelementfunctions), I'm sure this has less than ideal shrinking behavior.