Fork me on GitHub
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, what about at least changing the docstring to contain the URL of the function's online documentation, such as 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})
    (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?


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.


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


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

👍 4

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
                        (sg/set (s/gen ::kd/sid) :num-elements 1)
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
  (let [[subscr-summaries userid->profile] subscr-summaries+userid->profile]
      {:db/userid->profile userid->profile}

(s/def ::subscr-summaries+userid->profile
    (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)
        [subscr-summaries (s/gen ::kd/subscr-summaries)
         profiles         (sg/vector (s/gen :db/profile) (count subscr-summaries))]
           (keys subscr-summaries)

(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)


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


here is more explanation about fmap and bind: