clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
practicalli-john 2020-06-12T03:14:36.276Z

How can I specify the number of tests run when using clojure.spec.test.alpha/check ? By default its running 1000 tests for a check against 1 spec (the playing cards example from https://clojure.org/guides/spec#_a_game_of_cards ) and takes around 80 seconds to complete. The docs mention :num-tests within clojure.spec.test.check/opts but either I have the syntax wrong or missing something

;; runs 1000 tests
(spec-test/check `deal-cards
                 {:num-tests 1})

;; java.lang.RuntimeException
;; Invalid token: ::clojure.spec-test-check/opts
(spec-test/check `deal-cards
                 {::clojure.spec-test-check/opts {:num-tests 1}})
Apart from Clojure 1.10.1, the project includes the dependency :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"}} Requiring clojure.spec.test.check generates an error when the namespace is evaluated
java.io.FileNotFoundException
   Could not locate clojure/spec/test/check__init.class,
   clojure/spec/test/check.clj or clojure/spec/test/check.cljc on classpath.
Project code is at https://github.com/practicalli/spec-generative-testing if it helps...

seancorfield 2020-06-12T04:07:22.277600Z

The option should be a qualified keyword -- but that doesn't mean a namespace exists.

seancorfield 2020-06-12T04:09:15.277900Z

:clojure.spec.test.check/opts

seancorfield 2020-06-12T04:11:04.278700Z

If you want to use ::stc you can introduce an alias:

user=> (alias 'stc (create-ns 'clojure.spec.test.check))
nil
user=> ::stc/opts
:clojure.spec.test.check/opts
user=>

seancorfield 2020-06-12T04:12:21.279600Z

Your ::clojure.spec-test-check/opts is going to fail because :: will try to auto-resolve clojure.spec-test-check which is not an alias.

practicalli-john 2020-06-12T11:21:27.290500Z

It would never work as I got the namespace wrong, using - instead of . Now I have the namespace correct in the fully qualified name, surprisingly it works

(spec-test/check 
  `deal-cards
  {:clojure.spec.test.check/opts {:num-tests 1}})
Victim to more late night coding 😞

practicalli-john 2020-06-12T13:28:48.291800Z

Running with 10 tests is pretty instantaneous and 100 tests is only a couple of seconds. Much better. Now to start figuring out how to run tests as part of the development workflow...

seancorfield 2020-06-12T15:45:38.294400Z

My point was that, even if you got the namespace right, ::some-namespace/key isn't going to work because some-namespace is not an alias. It needed to be a single : if you're using the whole name (and you get it right 🙂 ).

practicalli-john 2020-06-12T16:37:09.307300Z

Starting to find my way and looking through the next-jdbc project was a good example of using spec. Lots to still learn about spec and testing though 😁

seancorfield 2020-06-12T16:40:24.307500Z

And just when you get it all down pat, Spec 2 will come along and change everything you know 🙂

practicalli-john 2020-06-12T16:43:53.307700Z

There will still be a lot of spec 1 examples out there to work with, so good to have spec1 skills for my semi-retirement plan over the next 18 years 😁

seancorfield 2020-06-12T16:59:08.308100Z

Based on our experience with Spec 2, I suspect our own codebase will contain lots of Spec 1 code for many years...

seancorfield 2020-06-12T16:59:32.308300Z

(Spec 2 is much better, but also very different, so migration would be painful)

2020-06-12T05:08:14.282300Z

Spec2 seems so cool. Increased programmability is such a good advantage. I have been using custom macros to generate spec, and it was a bit odd.

Aron 2020-06-12T07:18:56.284200Z

Sorry if this question has an easy answer discoverable that I missed, didn't do a deep dive. I see that https://github.com/clojure/spec-alpha2/wiki/Schema-and-select#select is available in alpha2 but not in alpha. Obviously lot of people use spec already, but which version?

mpenet 2020-06-12T07:22:02.284700Z

"spec1" (spec.alpha)

mpenet 2020-06-12T07:23:21.285800Z

it's not really clear what will/wont change in spec2 and there are also a few bugs

Aron 2020-06-12T07:31:15.287Z

Thanks! is there something similar to spec/select already existing, perhaps written by someone else, published under a different name?

seancorfield 2020-06-12T15:42:38.294300Z

@ashnur Spec 2 will eventually become the official clojure.spec. It's just not ready for use yet as it is still being actively designed and changed. Spec 1 will remain available for everyone already using it. I don't think anyone has tried to copy Spec 2 -- because it is not yet complete (and why would anyone try to recreate an official part of Clojure when Rich himself hasn't fully figured out parts of the design?).

Aron 2020-06-12T16:03:21.295400Z

I am just curious if I want to use select or something that does similar stuff to select, what are my best options currently.

seancorfield 2020-06-12T16:11:21.296100Z

@ashnur If you're just building toy stuff or learning/experimenting, you could use Spec 2. It's just not ready for production use.

seancorfield 2020-06-12T16:13:05.298100Z

We were tracking it at work, with a branch of our (95k lines) codebase, and I really like the changes in Spec 2 -- above and beyond the schema/`select` stuff -- but there's been a lot of churn in Spec 2 and Alex has said that Rich will likely overhaul s/fdef completely before it is released (and it may drop s/keys completely as well), so we stopped tracking it months ago.

seancorfield 2020-06-12T16:13:28.298500Z

We're just going to wait for it to be "fully baked" at this point.

seancorfield 2020-06-12T16:14:07.299200Z

Once Alex signifies that it is stable and just needs testing to help iron out the bugs, we'll pick it up again.

alexmiller 2020-06-12T16:14:25.299500Z

I will signify that by making a release :)

Aron 2020-06-12T16:22:37.300300Z

so, if I understand that correctly, there is nothing else that targets the same problem domain to be used in production in the interim?

seancorfield 2020-06-12T16:26:54.301100Z

clojure.spec.alpha targets that domain and can be used in production. Spec 2 is the "next generation" of that and will be the preferred solution when it becomes ready.

seancorfield 2020-06-12T16:27:46.302300Z

Spec 2 is definitely "better" than Spec 1 -- because it's designed to incorporate lessons learned from the first version. But Spec 1 definitely has value in production.

Aron 2020-06-12T16:36:56.307200Z

I like what s/select does and would like to use something like it in production, even if it's not necessarily exactly the kind of s/select that is planned by Rich, since as I understand it, there are still months, maybe years until that version will be ready.

alexmiller 2020-06-12T16:47:40.308Z

god I hope it's not years :)

4
🎉 1
misha 2020-06-12T17:27:27.310600Z

how can not be expressed in spec? to maximize built-in generators reuse and composability (really just ability to wrap any spec in "it" without looking at spec/form I am wrapping)

misha 2020-06-12T17:28:42.311600Z

(before you look at me funny, I am writing a translator from json-schema to clojure-spec, particularly https://json-schema.org/understanding-json-schema/reference/combining.html#not)

misha 2020-06-12T17:33:05.312200Z

anything better than this?

(do
  (s/def ::foo string?)
  (s/def ::bar (s/with-gen
                 (complement (partial s/valid? ::foo)) 
                 #(s/gen any?)))
  (s/exercise ::bar))

alexmiller 2020-06-12T17:33:29.312400Z

not really

alexmiller 2020-06-12T17:33:55.313100Z

not is weird and I would generally avoid doing it :)

misha 2020-06-12T17:35:06.314300Z

at this time, I am trying to generate spec as close to schema as possible, as code you than paste into file, and then might chose to change

misha 2020-06-12T17:37:43.315400Z

Alex, is there an (out the box) way to conform unqualified map and get qualified conformed map back?

misha 2020-06-12T17:40:29.315800Z

(do
  (s/def :my/foo string?)
  (s/def ::map (s/keys :req-un [:my/foo]))
  (s/magic-conform ::map {:foo "x"})  #_=> {:my/foo "x"})

seancorfield 2020-06-12T18:21:10.317800Z

I'm not Alex @misha but I can't think of any easy way to do that. You'd probably have to derive the (qualified) keys from the s/form of the Spec, and then zipmap with a version of those keys that had been mapped to unqualified keys, and then use clojure.set/rename-keys on your validated data.

seancorfield 2020-06-12T18:22:00.318500Z

(but that won't work with nested data structures/specs or anything more complex than just s/keys)

alexmiller 2020-06-12T18:25:16.318800Z

You could unform

alexmiller 2020-06-12T18:25:51.319300Z

I guess you still wouldn’t get unqual

alexmiller 2020-06-12T18:26:08.319600Z

So I’ll go with no :)

misha 2020-06-12T18:57:11.321800Z

I thought about just including both :req and :req-un sets of keys, but it does not solve "I have a map from example page, show me the specs it uses", and screws up the generators, which you probably want to generate either entirely qualified or entirely unqualified deep tree.

seancorfield 2020-06-12T19:03:13.324800Z

If we were starting again from scratch with Spec available, and using next.jdbc instead of clojure.java.jdbc, I think we would only have unqualified keys at the boundary of our system: either as API input or user input (forms, URLs), and at outgoing boundaries for JSON-based systems. So our use of :req-un/`:opt-un` would be a lot smaller, and we'd explicitly transform validated input into a domain model that always used qualified keys. Interacting with JDBC via next.jdbc means you can use qualified keys going out to the DB and you would get qualified keys coming in from the DB as well, automatically.

2020-06-12T19:04:19.325600Z

I'm playing around with custom generators, since there's a type which it seems like spec is having a hard time generating for some tests.

(s/def ::value pos-int?)
(s/def ::name keyword?)
(s/def ::symbol (s/keys :req [::value ::name]))
(s/def ::symbols (s/coll-of ::symbol :kind set?))
(s/def ::rows pos-int?)
(s/def ::columns (s/and pos-int?
                        #(>= % 3)))
(def machine-gen
  (gen/let [machine (gen/fmap
                     (fn [[cols rows]]
                       {::rows rows ::columns cols})
                     (gen/tuple (gen/fmap (partial + 3) gen/nat)
                                (gen/fmap inc gen/nat)))
            symbols (gen/vector-distinct (s/gen ::symbol)
                                         {:min-elements (inc (::rows machine))})]
    (assoc machine ::symbols symbols)))
(s/def ::machine (s/with-gen
                   (s/and (s/keys :req [::symbols ::rows ::columns])
                          #(> (count (::symbols %)) (::rows %)))
                   (constantly machine-gen)))
The problem is that whenever I try to sample the machine-gen, it works fine, but if I try to sample the result of (s/gen ::machine) it always says that a such-that isn't met after 100 tries. What would be the cause of this?

2020-06-12T19:13:46.325900Z

Here's a simpler version of the generator:

(def machine-gen
  (gen/let [cols (gen/fmap (partial + 3) gen/nat)
            rows (gen/fmap inc gen/nat)
            machine (gen/return {::rows rows ::columns cols})
            symbols (gen/vector-distinct (s/gen ::symbol)
                                         {:min-elements (::rows machine)})]
    (assoc machine ::symbols symbols)))

misha 2020-06-12T19:22:04.330100Z

Sean, my initial motivation is exploration, specifically of https://vega.github.io/ So I want to generate spec from schema (which is 9999km long), then take an example json, and with magic-qualify-conform see, which spec is that, and then navigate through keywords and specs in my IDE, instead trying to find things in huge json schema: https://vega.github.io/schema/vega-lite/v4.json or https://vega.github.io/schema/vega/v5.json This, and, the usual spec goods: exercise, etc.

misha 2020-06-12T19:23:32.331100Z

so it seems I'd have to come up with "qualiform" too.

seancorfield 2020-06-12T19:34:28.331600Z

Yeah, I can definitely see the utility of this and it would be nice as an option in s/conform.

seancorfield 2020-06-12T19:35:27.332600Z

I think it's interesting that Spec 2 takes a different approach, where you can specify unqualified keys inline in a hash map spec or else qualified keys in a schema, to be select'ed

2020-06-12T20:26:24.333800Z

Search is being very unhelpful for this problem, seems like few people run into it. @seancorfield would you happen to know of anything I could do to debug this issue or to alter the generator so that it'll work?

seancorfield 2020-06-12T20:30:57.334500Z

@suskeyhose what is gen/ in your code above? I gather it's not clojure.spec.gen.alpha...?

2020-06-12T20:32:23.334900Z

It's clojure.test.check.generators

2020-06-12T20:35:56.335700Z

My understanding was that clojure.spec.gen.alpha was just a namespace that re-exposed some of the test.check vars.

2020-06-12T20:37:55.335900Z

Which seems to be true looking at the source.

seancorfield 2020-06-12T20:41:41.336500Z

Hahaha... OK, it took me a while... What is ::symbols? What does it generate?

seancorfield 2020-06-12T20:41:54.336900Z

And then in machine-gen, what type is symbols?

2020-06-12T20:42:35.337700Z

symbols is just a set of ::symbol, which are just maps with ::name and ::value

seancorfield 2020-06-12T20:43:11.338200Z

::symbols is a set. symbols in machine-gen is a vector.

2020-06-12T20:43:33.338500Z

Oh boy, of course that's it

seancorfield 2020-06-12T20:44:04.338900Z

That was a nice Friday afternoon debugging diversion -- thank you! 🙂

2020-06-12T20:44:48.339400Z

Thanks for helping me out! I feel so dumb when I just get my types misaligned like that 🙃

seancorfield 2020-06-12T20:46:04.340400Z

No worries. I couldn't see it either. And I was simplifying the ::machine spec trying to figure out what the problem was... I was quite bewildered by it!