Fork me on GitHub
#clojure-spec
<
2019-01-18
>
borkdude09:01:36

Trying to spec partition-all, but I get an exception. Repro:

Clojure 1.10.0
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (require '[clojure.spec.test.alpha :as stest])
nil
user=> (s/fdef clojure.core/partition-all :args (s/cat :n pos-int?))
clojure.core/partition-all
user=> (stest/instrument `partition-all)
[clojure.core/partition-all]
user=> (partition-all 1)
Execution error (ClassCastException) at user/eval142 (REPL:1).
clojure.spec.test.alpha$spec_checking_fn$fn__3026 cannot be cast to clojure.lang.IFn$LO

borkdude09:01:04

that could be because of the type hint on the arity-1 of partition-all

bronsa10:01:26

yep, that's it

borkdude10:01:22

this is actually the first time I couldn’t instrument something on CLJ that I could on CLJS, normally it’s the other way around 😉

bronsa10:01:38

I guess instrument could look at the arglists and figure out if it needs to compile the wrapping fn to a primitive fn

rickmoynihan12:01:07

How do people manage the seperation of concerns around app code, specs and generators? Generators are essentially a testing concern, but you also use want them at dev time so you can inspect sample values etc… Part of my issue is that writing generators eats up quite a few LOCs, so I’m tempted to put them in a different ns to the specs and app logic… I also feel like in clojure that they should be optional… which means you don’t really want them in the require tree in the production app. Are there any patterns people have for arranging these? Currently I just them all in the same ns — but at some point I worry it’ll be hard to see the wood for the trees.

rickmoynihan12:01:32

Thinking also are there any lazy-loading patterns I can easily apply to my own generators?

rickmoynihan12:01:13

Also you often want different generators depending on context

rickmoynihan12:01:01

I know you can override them via st/check etc — but not really sure how to organise all these bits

borkdude12:01:36

@rickmoynihan clojure.spec.test/check takes overridable generators. I use those, if I don’t make a generator attached to the spec itself.

borkdude12:01:55

oh sorry, that was your last sentence

thomas15:01:40

Hi, I am trying to write a generator... but no luck... I am trying to follow the examples from the test.check page... but no luck (yet)

thomas15:01:42

it can't find certain functions for instance.. while as far as I can work out they should be in the clojure.spec.gen.alpha namespace

taylor15:01:10

You may have to reference test check namespaces directly. Spec doesn’t expose all the stuff that’s available

thomas15:01:56

frequency for instance.

borkdude15:01:43

@thomas are you using CLJ or CLJS?

borkdude15:01:50

maybe make a small repro of what you’re trying to do so we can take it from there?

thomas15:01:23

ok will do. thank you. (I can't do it now, but I'll try later tonight) Thank you @borkdude

rickmoynihan16:01:23

@thomas you know about lazy loading right?

rickmoynihan16:01:27

you need to be aware that clojure.spec.gen.alpha generators are subtly different to test.check ones

rickmoynihan16:01:28

I always forget the details… but IIRC generators from spec are wrapped in a 0 arg function to allow lazy loading… so if you’re trying to use test.check generators you need to wrap them one more time in a fn call… or something like that. 🙂

rickmoynihan16:01:25

@borkdude: respeced looks useful… do you have any machinery to trigger st/check in a deftest and get a good error out from clojure.test when things go wrong?

rickmoynihan16:01:04

I’ve written a few things like this in the past — but the results have always been lackluster.

borkdude16:01:39

yes, I’m doing that here: https://github.com/borkdude/speculative/blob/master/test/speculative/test_utils.cljc#L31 when a spec doesn’t conform, I see the thrown exception

borkdude16:01:46

the crucial part is (clojure.test/is (respeced.test/successful? stc-result#)), succesful? realizes the stc-result (it’s lazy) and checks if there’s at least one

rickmoynihan16:01:22

yeah I’ve done things like that before — but yours looks like it might give better output…

borkdude16:01:37

it works fine for me

rickmoynihan16:01:00

I’m guessing clojure.test just prints the error as (is (not (successful? ,,,)))

borkdude16:01:02

try it in the REPL and you’ll see 🙂

rickmoynihan16:01:24

Yeah I’m going to 🙂

rickmoynihan16:01:20

@borkdude: would you say a use case for rt/check-call is to check real sample data conforms to the spec then? i.e. as a way to check that the spec is valid, as much as the data/fdef?

borkdude16:01:39

@rickmoynihan check-call is made for checking fdefs on example based tests

borkdude16:01:05

e.g. to verify that your ret spec works given an example

borkdude17:01:57

one problem I’d like to tackle is: your function accepts arity 1 and 2, but you spec’ed only 2. there’s no way to detect this with stest/check since it only generates 2 args.

borkdude17:01:28

so fdefs could be insuffient. would be nice if that could be automatically detected.

borkdude17:01:25

it would be nice if you could generate data to detected that your fdef missed a case. I guess you can

rickmoynihan17:01:42

related what I also find useful is a test of a s/conform against some example data… so you know you spec’d the right thing.

rickmoynihan17:01:37

but then you find that clojure.test/is is effectively testing a boolean s/valid? is insufficient as you want to see the spec failure not just (is (not (s/valid? ,,,)))

borkdude17:01:38

that’s why I made check I think

borkdude17:01:48

this library is aimed at fdef checking

rickmoynihan17:01:07

but I think there’s an equivalent need for spec checking too

rickmoynihan17:01:31

as you say check requires an fdef

rickmoynihan17:01:08

I was writing things like (is (not= :clojure.spec.alpha/invalid (s/conform :spec/here {:sample :data})) but you need to be careful using :clojure.spec.alpha/invalid in these macros because it can cause compilation errors due to :clojure.spec.alpha/invalid being used to check the macro syntax

rickmoynihan17:01:07

but the reasoning there is that comparing (= ::s/invalid sample-result) gives you a meaningful error in clojure.test.

borkdude17:01:18

hah, yeah, I had a similar issue when spec’ing assoc, because then you cannot assoc the value :clojure.spec.alpha/invalid which Cursive does when you use the REPL 😛

borkdude17:01:41

solved that by writing my own ::any spec

rickmoynihan17:01:51

yeah IIRC there’s a ticket open on the issue somewhere

borkdude17:01:15

yeah. one of the proposals there is to use a singleton object to represent invalid inside spec and only to the outside use the keyword

borkdude17:01:22

I think that makes sense

Alex Miller (Clojure team)17:01:42

you can use s/valid? rather than checking ::s/invalid

borkdude17:01:34

I used s/invalid?

Alex Miller (Clojure team)17:01:42

I don’t think a singleton object is a feasible solution

Alex Miller (Clojure team)17:01:51

I’m not even sure I agree it’s a problem

borkdude17:01:05

@alexmiller It’s a problem when you instrument a function that must be able to deal with ::s/invalid as one of the arguments. This is important in tooling such as Cursive where ::s/invalid is remembered as REPL state for example

Alex Miller (Clojure team)17:01:38

there are a very small number of such functions and some workarounds even for those cases

borkdude17:01:55

yes, a workaround is writing your own ::any 😉

Alex Miller (Clojure team)17:01:26

I consider “do nothing” to be a leading contender alternative for that ticket

borkdude17:01:16

how many votes are representative of “considered important enough by the community” in general for a CLJ Jira issue?

Alex Miller (Clojure team)17:01:01

votes are about attention, not answers

Alex Miller (Clojure team)17:01:44

if you’re not spec’ing core functions used by spec, how often is this an issue?

Alex Miller (Clojure team)17:01:52

b/c that’s when I’ve seen people raise it

borkdude17:01:23

probably tooling, but then it would only be an issue for the developer of that tooling

Alex Miller (Clojure team)17:01:03

I don’t consider this a settled result (which is why the ticket is still open)

Alex Miller (Clojure team)17:01:10

that’s just my current thinking on it

Alex Miller (Clojure team)17:01:07

changing topics, I just merged a giant refactor in spec-alpha2 in case anyone is interested

Alex Miller (Clojure team)17:01:46

although since it’s a refactoring rather than new stuff, it’s not particularly exciting

borkdude17:01:49

I think this issue is more important than the any problem: https://dev.clojure.org/jira/browse/CLJ-2443

borkdude17:01:00

that’s not only about self-spec’ing

Alex Miller (Clojure team)17:01:53

I haven’t had time to look at it seriously but I am aware of it

borkdude17:01:42

about spec-alpha2: cool! I’ll try it out soon

Alex Miller (Clojure team)17:01:06

there are some subtly important differences in the api for callers

Alex Miller (Clojure team)17:01:12

I will try to write about those in my journal today rather than spew them here

👍 20
tyler19:01:28

What approach do folks use to handle the namespacing of nested keys. We are trying to spec check the interface to an external service and the payload is a nested map. Namespaced keywords would seem to imply having to create a namespace for each nesting which isn’t ideal. Alternatively, we could generate the specs but this seems to have its own problems. As far as I can tell this would imply having to compose macros which seems excessive. Additionally, the caller side would have to construct the long form of these keywords in order to pull out the necessary data since there would be no namespaces to alias with this approach. Is there another approach that I’m missing? Its also possible that spec isn’t appropriate for this use case.

borkdude19:01:51

@tyler a similar question was asked not too long ago here

tyler19:01:49

Is it in the recent history? I’m having trouble searching for it.

taylor19:01:12

you don't have to create "real" namespaces for the keys, you can qualify keys with arbitrary prefixes e.g. :fake-ns/foo

manutter5119:01:19

We sometimes use "dotted-path" namespaces like :order.line-item/qty

👍 5
tyler20:01:15

Thank you