Let us say I have a generator users-gen
, that generates a group of 1 or more users. And another parameterized generator called user-actions-gen
that takes a sequence of one or more users and generates a sequence of actions that those users might perform.
(def user-gen
;; generates a user
...)
(def users-gen
;; sequences of 1 or more users
(gen/such-that not-empty (gen/vector gen/users))
(defn user-actions-gen [users]
;; a action performed by some user from the `users argument
...)
If I want to generate a single action for a single sequence of users generated by users-gen, then it is simple, just gen/bind users-gen to user-actions-gen directly.
However, I want to generate many actions from the same sequence of users. I have this problem because I am basically just trying to say "Here is the state, let any random action come in, let us apply the action to the state, let us confirm that the state is still valid; do this for all actions." I have the following code.
(defspec check-that-state-is-always-valid
100
(let [state-atm (atom {})]
(prop/for-all
[[actions users]
(gen/bind users-gen
(fn [users]
(gen/tuple
(gen/vector (user-actions-gen users))
(gen/return users))))]
(doseq [action actions
:let [state (swap! state-atm state-atm-transform-fx action)]]
(is (state-still-valid? state))))))
This sort of works. The problems are that:
- It seems to fully evaluate the doseq rather than halting on the first error
- It just looks kind of wrong. The code is all over the place, it is not entirely evident what it does.
- It seems like maybe user-actions-gen should be taking a generator of users-gen, rather than the realized users value of users-gen? Would this help with composability? Note that I don't want to put them together as users-gen is probably useful to other generators.
So, to recap. I am taking a single generated value from one generator and passing it as an argument to more than one generators. How do I go about doing this in a more attractive/elegant way?