Fork me on GitHub
#clojure-spec
<
2017-07-05
>
curlyfry10:07:35

Hi, I just realized that spec/instrument is incompatible with event handler-type systems like re-frame (systems where functions are stored in a data structure prior to instrument being called). Since instrument wraps the original var, the function calls that are made through lookup in a data structure (e.g a map of event->handler-fn) aren't spec checked. Since this is a clear majority of the calls being made in for example re-frame, the value of spec:ing (with fdef) the handler functions is decreased. Is there any way around this at all? I guess you could add the vars themselves in the map instead of the functions they point to, but that seems like a big change to make in an existing system, and feels pretty unidiomatic.

carocad10:07:17

curlyfry: why do you need to fdef the function? doesnt it suffice to just spec your :db such that it is always in the right state? you could also avoid instrumenting your functions and have them tested separatedly

curlyfry11:07:40

I'd like to use the philosophy of spec: instrument ensures that functions are called correctly, tests ensures that they have the correct return values. Checking the whole app-db after every event is also not viable when the app gets large.

carocad11:07:22

> tests ensures that they have the correct return values. not really. You can fdef a function without instrumenting it. Then on your test spec will generate random input against which you can check the output of the function and the relation between them. > I’d like to use the philosophy of spec: instrument ensures that functions are called correctly If you only interested in that then I dont know how to 😞

curlyfry11:07:21

What do you mean by "not really"? You're describing exactly what I mean :) Some discussion on the topic: https://groups.google.com/forum/m/#!msg/clojure/JU6EmjtbRiQ/WSrueFvsBQAJ

carocad14:07:38

hehe sorry for the confusion. In retrospective my message is quite confusing 😄. What I meant is that if you can test your function with generated random input and it returns what you expect then instrumenting at runtime is not necessary since you can be sure of the integrity of your function. It is definitely not so dynamic since changing your functions would break that certainty but so far I dont know any other option

curlyfry15:07:23

I guess I'd just prefer to be able to do both... Anyway, thanks a lot for your input! :)

carocad15:07:07

oh another idea. Can you not redefine the handlers so that re-frame uses the new instrumented functions? I use Cursive so I usually just do sync file to repl and it automatically loads all changed files which would trigger the reg-event-handler … I guess. Have not tried it yet 🙂 Hope it helps

danielcompton00:07:25

@U0J30HBRS with re-frame in particular I suspect we'll have to add support for turning on spec checking so you can do this. Would be nice to integrate with spec/instrument, but doesn't seem like its possible

stathissideris10:07:38

a similar problem is when you have a chain of ring handlers and you reload the code for one of them, but because the data structure is already referring to the original var, reloading the handler has not effect. In cases like that, I’ve seen people adding the original vars as you suggested and I don’t think it’s unidiomatic. Just a bit unusual!

andrewmcveigh10:07:19

I guess you could potentially spec the data structure and use fspec at the leaves where the event-handler functions are.

andrewmcveigh10:07:56

Seems complicated though

danielstockton10:07:43

Not possible to def the functions outside the datastructure?

danielstockton10:07:13

(defn my-fn [x y z] ...) (def handler {:event1 my-fn})

curlyfry11:07:44

@danielstockton That's how I do it. The problem is that the functions, not the vars, are what are added to the datastructure. If you try it out, you'll notice that the function calls made from lookups in the map aren't checked. In my particular case the functions are added to a data structure behind the scenes, by re-frames reg-event.

rickmoynihan12:07:21

I’m trying to instrument an fdef’d function with a stub and a custom generator but get an exception ClassCastException clojure.test.check.generators.Generator cannot be cast to clojure.lang.IFn clojure.spec.alpha/gensub (alpha.clj:268)

(defn foo [a]
    (+ a a))

  (s/def ::num number?)

  (s/fdef foo
          :args (s/cat :a ::num)
          :ret number?)

  (st/instrument `foo {:stub #{`foo} :gen {::num (g/elements [1])}})

rickmoynihan12:07:15

the instrument call itself raises the exception

rickmoynihan12:07:35

ahh I see the problem - I need to obviously gen the return value not the args… doh

rickmoynihan12:07:08

changing it to :ret ::num works

andrewmcveigh13:07:07

Is it that (g/elements [1]) should be #(g/elements [1])?

rickmoynihan13:07:27

ahh yeah sorry I’d fixed that locally too

rickmoynihan13:07:12

actually that’s one thing I’m never really clear on… when to pass a generator and when to pass a 0-arg function returning a generator…

rickmoynihan13:07:22

is there a resource somewhere explaining that?

andrewmcveigh13:07:09

I think you always need to give a function to something that comes from spec (and is deffed). As far as I worked out, it's to do with the way spec lazy-loads test.check. Not seen it written down anyway though.

andrewmcveigh13:07:19

I can't think of a place where spec requires a plain generator.

andrewmcveigh13:07:38

Only if you're calling test.check stuff directly

rickmoynihan13:07:10

at least I know the heuristic now 🙂

rickmoynihan13:07:58

Is it just me or does instrument with a stub also try and generate conforming values for the :args spec; even though it’ll never need a generator for them - as it only needs to generate return values?

rickmoynihan13:07:19

basically I’m getting a such-that error on instrument but if I replace the :args spec with something like (s/* any?) it works.

jannis14:07:48

Hi folks! Is there a good approach to writing function specs and running clojure.spec.test.alpha/check in a way that allows functions to either return a value (checked with :ret) or throw an exception?

jannis14:07:01

Or, simplified, is there a way to spec a function so that the fact that it throws under certain conditions is part of the spec?

wilkerlucio14:07:59

@jannis I remember reading something here about spec not contemplating exceptional cases, I believe the idea is that specs are about data only

jannis14:07:10

Yes, that’s what I was thinking.

jannis14:07:29

I think I’ve found a solution for myself: have the spec verify the data is ok, then have the function only operate on valid data. That way generative tests only generate valid data but invalid data would still be caught at runtime if I use instrumentation.

jannis14:07:50

That completely eliminates the need for exceptions. 🙂

joshjones14:07:43

You don’t want to use instrument at runtime, generally speaking @jannis

jannis14:07:13

I think I do—but only while developing.

jannis14:07:29

And while not testing performance perhaps 😉

joshjones14:07:35

yes, that’s what i meant and should have said “production” 😉

jannis14:07:44

@joshjones No worries, I assumed that’s what you meant already.

hkjels20:07:59

Is there a clever way to get the merged form of a spec that uses spec/merge?

rickmoynihan22:07:33

Just ran into this issue ( https://dev.clojure.org/jira/browse/CLJ-1989 ) and surprised that the lazy load coverage doesn’t extend to all of test.check.generators