clojure-spec

pavlosmelissinos 2022-02-01T10:01:43.483839Z

Ok so here's a scenario I'm not sure how to approach: I've changed some specs and introduced a bug. Now my generative tests don't work because "Couldn't satisfy such-that predicate after X tries." I'm pretty sure I've identified the spec that contains the bug but I can't tell exactly why it's wrong. My first idea was to generate a config (even if it doesn't satisfy all the predicates) and then use explain-data on it and navigate :clojure.spec/problems. However, :clojure.spec/gen and :clojure.test.check.generators/sample refuse to even show me the generated data if it's invalid. Is there an alternative that would work in this case?

lassemaatta 2022-02-01T10:05:48.245649Z

have you tried generating values directly from that "possibly broken" spec instead of the whole config?

πŸ‘ 1
lassemaatta 2022-02-01T10:08:05.555019Z

I usually debug such problems by starting from the simplest case which generates successfully and then gradually add more restrictions until I find the spec/predicate, which fails to pass the data

πŸ‘ 1
pavlosmelissinos 2022-02-01T10:26:59.533899Z

Sure but I was wondering if spec could assist with that. The culprit was a custom predicate that's part of the property tests. It's used to ensure that the generated data satisfies certain properties (e.g. make sure that a key only appears once in a vector of maps). However, in the process it introduced a regression (it used concat on a vector, which turned it into a list). After some digging I figured it out and fixed it; I was just wondering if there was a less manual way to do it, with spec, to save me some time in the future. As in: 1. generate the data 2. run explain-data 3. get back a map with s/problems 4. see something like ":v should be a vector, not a list"

Alex Miller (Clojure team) 2022-02-01T14:21:49.854519Z

There has been some work in test.check to provide better reporting and eventually I would like to help surface some of that through spec for cases like this

πŸ™ 1
pavlosmelissinos 2022-02-01T14:45:45.278269Z

That's good to know, thanks πŸ™‚ To be honest I'm a bit surprised there's no way to get the generated data if it doesn't satisfy all the predicates but I'm sure there's a reason it's like that. I mean I understand it's an all or nothing situation but on the other hand having the incomplete state would be useful for debugging.

Alex Miller (Clojure team) 2022-02-01T14:52:15.413039Z

there's good reason to verify generated values match the spec, but there is a general problem with not getting example failures if no matching generated value can be found

πŸ™ 1
karol.adamiec 2022-02-01T10:10:47.063479Z

with spec, is it good idiomatic usage/taste to say that for example a reduce over collection should satisfy a predicate? in real terms, lets imagine we have a spec of a Security. And we build a holding. So each security, that is included in holding has a weight. And naturally all weights must sum up to a 100. Is this too convoluted and going too far with spec? Goal is to use spec not only to validate inputs, but also generate artificial holdings, that make sense.

vlaaad 2022-02-01T10:19:01.412669Z

You can have arbitrary predicates composed with s/and , it’s very idiomatic IMO

vlaaad 2022-02-01T10:20:06.520469Z

e.g. your holding spec can be something like (s/and (s/coll-of ::security) holdings-add-up-to-100?) where holdings-add-up-to-100? is your predicate

karol.adamiec 2022-02-01T10:20:40.979199Z

nice. good to know... now off to write a generator πŸ˜‚

vlaaad 2022-02-01T10:20:48.063489Z

yeah, that πŸ˜„

lassemaatta 2022-02-01T10:20:57.792459Z

that's the fun part πŸ™‚

vlaaad 2022-02-01T10:21:20.626029Z

but I would say it’s all idiomatic and good taste

vlaaad 2022-02-01T10:21:42.128549Z

especially the generator

karol.adamiec 2022-02-01T10:22:08.721489Z

yeah, sounded like a good idea, but it is easy to get overexcited and go in directions one was not meant to travel in...

vlaaad 2022-02-01T10:25:28.971719Z

that depends on where your specs are going to be used. if it’s for testing the code that relies on β€œholdings add up to 100” invariant, then it makes sense to invest in generator. If that code is not of super importance, you can have your generator to be a set of examples as a starting point…

karol.adamiec 2022-02-01T10:26:57.159049Z

yeah, ideally we would use this extensively in unit tests. First, to catch edge cases that real data does not expose easily. and second to get rid of huge and obnoxius fixtures. πŸ™‚

karol.adamiec 2022-02-01T10:27:33.761969Z

but a good point, generator writing is optional.