Fork me on GitHub
#clojure-spec
<
2019-05-22
>
Jakub Holý (HolyJak)11:05:20

@alexmiller To improve docs of the lazy-loaded gen/* functions, since including the whole docstring is not feasible according to https://clojure.atlassian.net/browse/CLJ-2018, what about at least changing the docstring to contain the URL of the function's online documentation, such as http://clojure.github.io/test.check/clojure.test.check.generators.html#var-elements for elements?

Jakub Holý (HolyJak)12:05:32

Should I make a jira issue and send a patch?

Alex Miller (Clojure team)12:05:07

I assume you are aware we have migrated jira to new system...

👍 4
Jakub Holý (HolyJak)17:05:38

@alexmiller I wanted to try my patch by including local clone of spec.alpha in my project but starting clj in my project then fails with

...
Caused by: java.lang.Exception: #object[clojure.spec.alpha$and_spec_impl$reify__1047 0xf9b5552 "[email protected]"] is not a fn, expected predicate fn
	at clojure.spec.alpha$dt.invokeStatic(alpha.clj:762)
...
Any idea what I do wrong? I have cloned to /Users/me/tmp/spec.alpha, git hash 5228bb7. In my project's deps.edn:
{:deps {org.clojure/clojure {:mvn/version "1.10.1-beta2"}
        org.clojure/spec.alpha {:local/root "/Users/me/tmp/spec.alpha"}
...}
(This is before I did any changes to the code. java -version 1.8.0_192, OSX) But mvn package in the spec.alpha project runs just fine.

Jakub Holý (HolyJak)17:05:57

Same problem when running clj in the spec.alpha project:

🐟  clj
Clojure 1.10.0
user=> (load-file "src/main/clojure/clojure/spec/alpha.clj")
Syntax error macroexpanding clojure.core/defn at (alpha.clj:78:1).
#object[clojure.spec.alpha$and_spec_impl$reify__2183 0x1698fc68 "[email protected]"] is not a fn, expected predicate fn

Jakub Holý (HolyJak)12:05:19

Question: I often do something like the following, to restrict the domain of the generated values to ensure interesting conflicts:

(s/fdef filter-adult-users
        :args (s/cat :youngsters (s/coll-of ::uid :kind set?), :users ::users) #_... )
(deftest filter-adult-users-spec
  (let [[user-ids] (sg/sample (sg/set (s/gen ::uid)
                                      {:min-elements 1
                                       :max-elements 20})
                              1)]
    (is (true? (check `filter-adult-users
                      {:gen {::uid (constantly (sg/elements user-ids))}})))))
I.e. I have a let where I use sample to generate a random, small set of data then used in generator overrides in the test itself. Do you do that too? Is there a better way? update Sometimes I need to use the customized random data from multiple generators, which prevents I. believe the usage of simple generator derivation such as gen/let and gen/rmap

Jakub Holý (HolyJak)14:05:03

thank you! but what if eg 2 different generators need the value? any tips?

tangrammer14:05:11

yep, that’s a very interesting need …

Jakub Holý (HolyJak)15:05:29

example: having a function taking a map with known values and a list of "things", the fn throws if any " thing" has an unknown value. If I want to test its other functionality I must ensure that things only use the values in the map get I prefer not to hardcode the map.

misha15:05:48

you can test.check/let the generator, which generates [map things] tuples, and then apply function you want to test to the generated tuples

Jakub Holý (HolyJak)16:05:35

good idea! But I guess I would need to invoke it manually instead of using spec.test/check

misha10:05:10

defining wrapper which applies your function to the tuple, and specing/checking wrapper instead might be an option for you

👍 4
tangrammer10:05:45

could anyone write/share a gist detailed example 🙏 ? 🙂

Jakub Holý (HolyJak)11:05:35

BTW I tried to replace my (let [uids ..] (check .. {:gen {::uid (const. (sg/elements uids)))}})) with

(check .. 
       {:gen {::uid (constantly
                      (gen/bind
                        (sg/set (s/gen ::kd/sid) :num-elements 1)
                        sg/elements))}})
but it does not seem to really work. If I replace it with (constantly (sg/return (s/gen ::uid))) then I get many (desired) "collisions" in the tests but with ☝️ I get none. So my hypothesis is that the set of values is re-created every time that a new value fpr ::uid is generated, instead of creating it once and reusing it every time ::uid is requested.

👍 4
Jakub Holý (HolyJak)06:05:46

@U051KJGTX a gist of what exactly? As mentioned above, bind did not work for me, and showing a let wrapping check with some :gen overrides is perhaps not all that interesting?

Jakub Holý (HolyJak)12:05:44

@U051KJGTX here is an example of what misha proposed, a wrapper fn taking a tuple of related inputs:

(defn apply-profile-compute-totals-wrapper
  "Smart wrapper around apply-profile-compute-totals that 'unpacks' the subscr-summaries+userid->profile tuple
   we generate so that both have the same subscribers before invoking the wrapped fn
   "
  [subscr-summaries+userid->profile]
  (let [[subscr-summaries userid->profile] subscr-summaries+userid->profile]
    (apply-profile-compute-totals
      {:db/userid->profile userid->profile}
      subscr-summaries)))

(s/def ::subscr-summaries+userid->profile
  (s/with-gen
    (s/cat :subscr-summaries ::kd/subscr-summaries, :userid->profile :db/userid->profile)
    ;; GENERATOR: Ensure that the generated userid->profile have a profile for every subscriber in
    ;;            subscr-summaries (b/c those without profile would have been filtered out before)
    (constantly
      (gen/let
        [subscr-summaries (s/gen ::kd/subscr-summaries)
         profiles         (sg/vector (s/gen :db/profile) (count subscr-summaries))]
        [subscr-summaries
         (zipmap
           (keys subscr-summaries)
           profiles)]))))

(s/fdef apply-profile-compute-totals-wrapper
        :args (s/cat :1 ::subscr-summaries+userid->profile)
        ;:args (s/cat
        ;        :org  (s/keys :req [:db/userid->profile])
        ;        :subscr-summaries ::kd/subscr-summaries)
        :ret ::kd/subscrs+profile-usages)

(st/check `apply-profile-compute-totals-wrapper)

tangrammer13:05:51

thanks a lot for sharing … I’ll give a try next week!

misha16:05:50

here is more explanation about fmap and bind: https://youtu.be/F4VZPxLZUdA?t=655