Fork me on GitHub
#clojure-spec
<
2018-05-23
>
flowthing06:05:33

I have something like this:

(spec/def ::groups
    (spec/every-kv ::id ::group))

  (spec/def ::students
    (spec/every-kv ::group/id (spec/every-kv ::id ::student)))
  
  (spec/def ::root
    (spec/keys :req [::groups ::students]))
Is there a way for me to specify that when I do (gen/generate (spec/gen ::root)) that every group ID for every student has an equivalent group ID under the ::groups key?

flowthing07:05:43

In other words, I'd like every group ID in ::students to have a corresponding entry in ::groups. I know how to spec that, but I'm wondering what the best way to write a generator like that is.

flowthing07:05:09

I could always just post-process the generated map, but for more complicated maps, that becomes a bit difficult.

andy.fingerhut07:05:00

I've not used this before, but have heard that test.check has something called bind that lets you generate one value randomly, e.g. in your case perhaps a set of group IDs, and then use that set to generate other things, e.g. your ::groups map and also the ::students map, which could be generated to have exactly the same set of group IDs in both.

andy.fingerhut07:05:23

Not sure if the test.check examples of bind on this doc page are enough to get you going or not: https://clojure.github.io/test.check/generator-examples.html

gfredericks10:05:01

@flowthing you can generate the groups first, and then (using bind) generate students where their group-id is from (gen/elements (map :id groups))

flowthing10:05:09

Many thanks for the tips! I'll give gen/bind a go.

gfredericks10:05:55

@flowthing gen/let can do the same thing, and you may find it more intuitive

flowthing10:05:43

Yeah, I've tried gen/let actually, but I haven't yet come up with a clean solution. Nonetheless, it's good to know I'm heading in the right direction.

lwhorton15:05:29

hey peoples, i’m not really sure the term/noun for what i’m trying to do with spec, so it’s hard to google… I want to define a spec where an id field that’s present in two places is guaranteed to be the same:

{"id" {:id "id" :other-stuff true}}

lwhorton15:05:52

I’m assuming I have to write a custom generator to enforce this?

guy15:05:02

I think you would have to have a predicate that for that map, it checks that key and the :id value is the same. And i believe yes, you would have to have a custom generator for it too.

guy15:05:40

Like i don’t know how you would do it with s/keys for example

guy15:05:05

but saying that, there might be another way

lwhorton15:05:17

yea i’m not sure how to handle it either, i might just scrap this gen test entirely.. like how do you s/map-of ::id ::some-spec with also ::some-spec (s/keys :req [::id]), then write a generator that builds out two(?) specs. maybe i have to merge the map-of?

guy15:05:28

(s/def ::some-spec (s/spec #{200 202 400}
                        :gen (fn [] (gen/return (gen/generate (s/gen #{200 400 500}))))))
like something like this maybe but catered for making your map

guy15:05:52

where u can supply a spec or a predicate function which would check both id key and value

lwhorton15:05:14

ah, that’s interesting

guy15:05:36

yeah let me find an example soz

rickmoynihan16:05:16

So I’ve written some specs in an existing project with a clojure.test test suite. Does anyone have any recommendations on incorporating generative tests using clojure.spec.test.alpha/check with clojure.test? Obviously I could do:

(t/is (:result (first (clojure.spec.test.alpha/check `foo))
But that will mask the errors.

rickmoynihan16:05:38

A while back I had a similar issue and tried hooking into the clojure.test assert macros but it was never very satisfactory

rickmoynihan16:05:02

is there a lein test runner for fdefs?

rickmoynihan16:05:25

actually just remembered I’ve been here before… and have already implemented a lein alias in this project for running specs using instrument etc…

lwhorton16:05:27

i wrote a little helper for your first problem..

(defn passes?
  "A light helper function to evaluate a generative/property test so that
  clojure.test spits out the failing result (if any) for easier debugging."
  [spec]
  (let [res (spec)]
    (t/is (nil? (:shrunk res)))
    (t/is (true? (:result res)))))

lwhorton16:05:22

i’m not sure if that’s too implementation-specific with checking shrunk, but it works for me and i didnt have to dig around docs for a long time

rickmoynihan16:05:02

@lwhorton what are the arguments?

rickmoynihan16:05:17

obviously it’s a function… but I guess I should ask how do you use it?

lwhorton16:05:25

ah, it’s just like you would use testing or is/are:

(defspec my-spec my-prop-test)

(deftest some-test
  (testing "some generative test should pass"
    (passes? my-spec))))

lwhorton16:05:09

erm, rather ☝️

rickmoynihan16:05:37

what is defspec?

lwhorton16:05:53

[clojure.test.check.clojure-test :refer-macros [defspec]]

rickmoynihan16:05:37

ahh ok — that’s not strictly spec though is it?

rickmoynihan16:05:20

Isn’t that a property test defined with test.check? Rather than through s/fdef etc

rickmoynihan16:05:06

I appreciate spec builds on test-check and that they’re compatible to some degree; but will that pick up an fdef :fn spec etc

lwhorton16:05:29

yea there’s a few moving pieces, the spec itself is defined using clojure.test.check.properties and a generator that uses spec/gen

lwhorton16:05:33

particularly:

(defspec first-element-is-min-after-sorting ;; the name of the test
         100 ;; the number of iterations for test.check to test
         (prop/for-all [v (gen/not-empty (gen/vector gen/int))]
           (= (apply min v)
              (first (sort v)))))

rickmoynihan16:05:49

yes I’ve seen that before; but I’m not sure it’s what I need — e.g. if you had a spec defined like this from the clojure docs:

(s/fdef ranged-rand
  :args (s/and (s/cat :start int? :end int?)
               #(< (:start %) (:end %)))
  :ret int?
  :fn (s/and #(>= (:ret %) (-> % :args :start))
             #(< (:ret %) (-> % :args :end))))

lwhorton16:05:52

ooph, i’m not sure man sorry 😕 … im just getting into the gen testing myself

rickmoynihan16:05:04

that spec is already defed so to speak; as it’s metadata is registered already with the system.

rickmoynihan16:05:22

@lwhorton: 🙂 no worries; it’s subtle stuff

rickmoynihan16:05:33

I have used defspec before… and tbh it seems like it’s a better way to integrate with clojure.test right now

rickmoynihan16:05:51

@lwhorton in the interests of sharing I have something like this that works as a different entry point i.e. run via a lein alias:

rickmoynihan16:05:07

it uses st/instrument to enable all the fdef specs and run them through st/check