Is there a way to do this:
(s/def ::bar #{:one :two :three})
(s/def ::foo
(s/with-gen
(s/keys :req [::bar])
#(gen/fmap
(fn [foo]
(assoc foo ::bar :one))
(s/gen ::foo))))
without having to create temporary specs like this due to the spec lookup loop
(s/def ::bar #{:one :two :three})
(s/def ::foo*
(s/keys :req [::bar]))
(s/def ::foo
(s/with-gen
::foo*
#(gen/fmap
(fn [foo]
(assoc foo ::bar :one))
(s/gen ::foo*))))
You can gen recursive specs
Can you explain why you need with-gen?
::foo
is actually a multi-spec and only the one value of ::bar
is valid for this implementation
So without tweaking the generator it fails to generate valid examples
Have you looked at the generator override map?
By “You can gen recursive specs” do you mean create the s/def
calls dynamically in a macro?
It’s being fed into another library that does the generating so I can’t supply the override map
No, I mean you can have recursive specs and call gen on them
I think I did not get what you were trying to do
Haha, so it actually is both ultimately a multi-spec and a recursive one. The recursion ‘within’ the generator works fine if I split the spec into the ::foo*
and ::foo
defs
It’s just the call to s/gen
within the s/with-gen
that loops expectedly
What’s the point of the initial gen if you just override :bar?
Aren’t you always generating the same map?
If so, just (s/gen #{ {::bar :one} })
Or gen/return if that is easier to read
I’m not I follow exactly; the ::bar
spec is used in all the multi-specs it’s just that some of the types it’s values are restricted
Is this simplified and there are other keys too?
Oh, yes sorry this is rather simplified!
You left out half the problem and half the constraints
It may not actually be in an inifinite loop - it may just be making very big intermediate colls
Apologies 🙂 I was just trying to ask the simplest question. I could mull it over some more…
The simple case above loops forever
If you have any coll-of specs, make sure you set :gen-max 3 on them
There are also some spec dynvars controlling recursion, might want to check those too
All that said, there are some outstanding bugs in this area and it’s inherently tricky
Scratching a little deeper the loop I’m referring to is within the implementation, as spec is currently implemented you cannot define a generator ‘in terms of itself’ like I do above for any of the implementations such as map-spec-impl
. It’s deeply baked into the implementation so I suspect it won’t change even though at first glance that seemed like a natural way to scratch my itch 😄 .
I’ll re-evaluate what I’m trying to do, thanks @alexmiller
does it make sense to call instrument
from a clojure.test
fixture? https://clojuredocs.org/clojure.test/use-fixtures
ex: if we have tests for various namespaces, each of which should really instrument the specs as they run
yes
and unstrument at the end :)
@jeffrey.wayne.evans I've also got an fdef-testing library here: https://github.com/borkdude/respeced
nice, thanks. will have a look
how about this situation? I want b
to be an optional argument to my-fn
, but if provided, then it should match some predicate (like map?
)?
this doesn’t quite work.
(defn my-fn
[a & [b]]
;; do stuff with a and b
)
(s/fdef my-fn
:args (s/cat :a int? :b map?)
:ret map?)
(my-fn 1)
Execution error - invalid arguments to user/my-fn at (form-init6149560983531223969.clj:1).
() - failed: Insufficient input at: [:b]
you spec'ed it as a function taking 2 args (int and map)
you can use s/? to spec an optional part of a regex op
it's a little unclear to me if you meant [b] or b in the my-fn definition
I guess you mean what you said
and :args (s/cat :a int? :b (s/? map?))
should work for int and optional map
awesome. thank you, Alex!