Fork me on GitHub

hey guys… anyone here using spec and prop. based testing in clojuresript? I have encountered strange problem. (s/gen) within a test that works perfectly on my machine, on CI machine (same java, lein, node and phantomjs versions) says ""Couldn't satisfy such-that predicate after 100 tries.”,


is it possible that a certain spec would work on one machine and fail on other?


it’s not like random. I run it many times. it passes on my machine and it ALWAYS fails on CI machine


other tests work fine though




what kind of spec is it?


oh… oh… it’s a bit nasty… I need to generate iso-date strings and I did this:

(s/def :date/iso (s/with-gen (s/and string?
                               #(re-matches iso-date-regex %))
                   #(gen'/for [d (gen'/datetime)]
                      #?(:clj  (time-f/unparse local-date-formatter d)
                         :cljs (.format (js/moment (js/Date. d)))))))


using test.chuck


@ag my first guess would be a different timezone setting between CI and your pc


I know this has probably been asked 1000 times but what is the prevailing preference for where to put specs? In their own parallel namespace or inline?


my impression is inline for function specs, different namespace for “domain” specs


But yeah, I think @stathissideris comment reflects what is becoming the most likely best practice at this point. It's what we're doing at World Singles so far...


and "domain" spec means specs which are likely to impact more than one namespace and be reused a lot?


Data structure specs, as opposed to function specs.


Know of any open source projects that are already using it, that I could take a look at?

seancorfield06:09:53 🙂


Thanks. Looks like they follow the some.namespace and some.namespace.spec convention instead


I was thinking an advantage of in-lining is that it provides some documentation, but am I right that spec adds something to the docstring anyway?


s/they/yourself/ i see


fdef specs are added to the output of doc ya


Ok, I might be leaning towards a separate namespace in that case


Inlining fdef makes sense for documentation, but not for portability across versions. def for data structures in a separate namespace makes sense regardless.


We've generally found if we start to def a data structure spec in a namespace with functions, it's gets messy ... keeping the data structure specs separate is more flexible...


I'm worried things will be messy with function specs too, it seems the documentation advantage is somewhat negated by the docstring


Not sure what you mean...? Specs just add to the docstring...


I mean that if it's added to the docstring then it isn't much of an advantage having it inline as well.


Right, yes, but the docstring is only updated if you've loaded the ns with the spec so you might as well have the fdef inline (IMO)


yeah.. different documentation requirements. documentation for people reading/maintaining the code, documentation for people using the function


Hi, I am trying to use spec to conform the arguments of a function: there is at most 4 arguments with 4 distinct types. I want to conform them into a map but the solution I came up with is not fully compliant because it allows 2 arguments of the same type. Is there a way to make sure there isn't 2 arguments of the same type ?


Not sure I follow... can you share some code?


@seancorfield yes sorry you are too quick 😄


Your coll-of is a map so each :message item overwrites any prior item before :distinct can be checked.


And if you used :into [] you would still accept those two :message arguments because they are different values so they are distinct.


The :distinct flag says that all the conformed values must be distinct -- which they are.


You'd need your ::argument spec to conform to just the type, not the type and value.


Hmm, that doesn't quite work either:

(s/def ::argument (s/and (s/or :message string? :app-data map? :type keyword? :variables vector?) (s/conformer first)))
(s/def ::arguments (s/coll-of ::argument :distinct true :into [] :max-count 4))


(s/conform ::arguments ["helo {a} hello"  "hello" ::random-log {:c '(inc c)}])
[:message :message :type :app-data]
Not what I expected.


@seancorfield if I have 2 identical arguments the predicate distinct? is triggered so the distinct check happens before the into {}


however this not really what I want I could remove it, what I would like is to have distinct types for the arguments not distinct arguments


Yeah, I think you're right about the :distinct check... it would check distinct values before the conforming.


there's something from a very long time ago whispering in my ear that this is an irregular grammar, and so you're going to need a custom predicate to validate it. but I could be wrong


s/irregular/not context free/


So you need s/and on ::arguments to force it to be distinct types.


(s/def ::arguments (s/and (s/coll-of ::argument :into [] :max-count 4) (s/coll-of keyword? :distinct true)))


(s/conform ::arguments ["helo {a} hello"  "hello" ::random-log {:c '(inc c)}])


(s/conform ::arguments ["helo {a} hello" ['b] ::random-log {:c '(inc c)}])
[:message :variables :type :app-data]


And that's with

(s/def ::argument (s/and (s/or :message string? :app-data map? :type keyword? :variables vector?) (s/conformer first)))


So each argument needs to conform to its type (throwing away the value) and then the arguments collection needs to flow the conformed arguments into a distinct collection


wow thank you it works but it also makes me realize I still have a lot of work to do on spec 🙂


Yeah, it's radically changing how we approach several types of problems.


oh ok I understand now because I didn't realize that I don't get the values anymore while conforming


the function I don't get is conformer


Took me a while too. conformer uses its function argument to transform the data rather than just being a true/false predicate.


But Alex says it's an anti-pattern so be cautious.


well I'll play a bit to see if I can keep my conformed map and still have the validation on distinct keywords


btw do I misunderstand unform ? I thought this call (s/unform ::x (s/conform ::x data-to-conform)) would return data-to-conform but it just returns the conformed data


@seancorfield is the explanation on why it is an anti-pattern available somewhere ?


below is the final spec I made thanks to your input that matches my needs, I could eventually just do the map transform outside of the spec iff conformer is an anti-pattern


@ag Here's how I generate date strings:

(require '[clj-time.core :as t]
         '[clj-time.format :as f])
(import '(org.joda.time.format DateTimeFormatter))

(defn parseable-timestamp
  "Returns a spec for a timestamp string can be parsed with the specified
   datetime formatter."
  (s/and string?
         (fn [timestamp-str]
             (f/parse formatter timestamp-str)
             (catch Exception _

(defn make-timestamp-gen
  "Returns a generator for a timestamp string between the minimum year and
   maximum year (exclusive) that can be parsed with the specified datetime
  [min-year max-year formatter]
  (fn []
    (let [year-gen (s/gen (s/int-in min-year max-year))
          month-gen (s/gen (s/int-in 1 12))
          day-gen (s/gen (s/int-in 1 28))
          hour-gen (s/gen (s/int-in 0 23))
          m-s-gen (s/gen (s/int-in 0 59))]
      (->> [year-gen month-gen day-gen hour-gen m-s-gen m-s-gen m-s-gen]
           (map #(gen/fmap vector %))
           (apply gen/cat)
           (gen/fmap #(->> (apply t/date-time %)
                           (f/unparse formatter)))))))

(def timestamp-formatter (f/formatters :date-time-no-ms))

(s/def ::timestamp
    (parseable-timestamp timestamp-formatter)
    (make-timestamp-gen 2000 2050 timestamp-formatter)))


asking again: I'd like to generate documentation from specs, is there a way to resolve/expand specs from (s/form ...) (or other) ?


I guess I can hack this walking the (s/registry) + (s/form ...), but that seems a bit "hairy"


(it's for front-end team, so I cant just spit keywords, i need to resolve the leafs of specs to stuff they understand)


might be a good fit for some specter juggling

Alex Miller (Clojure team)14:09:57

are you talking about a “deep” version of s/form?

Alex Miller (Clojure team)14:09:52

yeah, we’ve talked about providing that but it doesn’t exist right now


that'd be nice, it's should be easy enough to write, but that's something one would expect out of the box imho

Alex Miller (Clojure team)14:09:21

I have specs for spec forms (kind of, modulo a number of bugs in s/form) which could help a lot


sounds good

Alex Miller (Clojure team)14:09:49

because you could conform the spec and then pick the parts per form that you need

Alex Miller (Clojure team)14:09:01

I will probably get around to this soon-ish as I need it for other things


I didn't actually think about approaching it that way, neat

Alex Miller (Clojure team)14:09:35

having specs for specs opens up all sorts of things

Alex Miller (Clojure team)14:09:19

for example, automated testing of spec by generating specs from spec specs, then checking conformance on data generated for the generated spec


that's spec inception


oh since you're here, I have another question about the conformer + :clojure.spec/invalid issue I mentioned yesterday

Alex Miller (Clojure team)14:09:02

yes, it feels like that :)

Alex Miller (Clojure team)14:09:20

can you repeat? I can’t keep up the slacks


it used to compile (I think with alpha10)

Alex Miller (Clojure team)14:09:40

yeah, that’s been logged


probably a result of the recent optimisations

Alex Miller (Clojure team)14:09:50

would have changed as of alpha 11 or 12


oki, good to hear

Alex Miller (Clojure team)14:09:10

I mean changed as in “started failing”


I couldn't find a ticket about it in jira

Alex Miller (Clojure team)14:09:24

I’m not sure there is a good solution to it


oh, isn't this considered a bug?


it makes writing conformers a bit tricky, I basically have to (def invalid :clojure.spec/invalid) and use this in the body of the functions I am using

Alex Miller (Clojure team)14:09:02

yeah, but wait for a spec on def to break that too :)

Alex Miller (Clojure team)14:09:22

it’s a broader problem

Alex Miller (Clojure team)14:09:58

one possible solution would be to do something like is done in the reader where a reader is handed a token that it can return to indicate a special case

Alex Miller (Clojure team)14:09:45

so rather than being expected to return ::s/invalid, you are given a token that you can return (and that can be a per-instance (Object.))

Alex Miller (Clojure team)14:09:06

that still doesn’t solve the problems around core specs though

Alex Miller (Clojure team)14:09:23

it needs a longer conversation with Rich and he hasn’t had the time to have it


any hint of what might come in the next alpha?

Alex Miller (Clojure team)14:09:58

whatever we fix or add next :)

Alex Miller (Clojure team)14:09:41

CLJ-2024, CLJ-2027, and CLJ-2026 all have patches that are ready for Stu and Rich to look at so I expect those to be in the next alpha

Alex Miller (Clojure team)14:09:51

(presuming they like them)


Actually walking the specs isn't really possible in my case: if you do (s/def ::foo ::bar) (s/form ::foo) will have expanded ::bar to a predicate so I cannot check my stop point (since I don't want to expand down to the last bits, I have a set of specs that are the surface I want to expose to the front-end people)


(s/def ::foo any?)
(s/def ::bar ::foo)
(s/form ::bar)
=> clojure.core/any?


((s/registry) ::bar) works in that case actually


i’ve got a somewhat complex map spec that i’m trying to s/exercise, and i’m getting the dreaded "Couldn't satisfy such-that predicate after 100 tries.” error. what are the primary things i should be looking for when tracking down the root cause?


i’m familiar with s/and and how you should eg do int? first, followed by even?, etc


is naive s/and usage the primary trigger for this error, or are there other categories of spec misuse that often cause it?


(if it’s helpful, the spec i’m trying to exercise is )


i’ve only got one s/and in here, and it’s a (s/and number? pos?), so i feel like there must be some other category of thing i’m doing totally incorrectly


hm, seems to be specific to :component/input


the documentation on :kind says: "Note that if :kind is specified and :into is not, this pred must generate in order for every to generate.”; and adding :into #{} fixes the issue. guess i need to sit down and take some time to understand the semantics of :kind and :into. thanks all ❤️


(if you click on those links after coming back from lunch, pretend that the (s/coll-of) calls with :kind specified do not have a corresponding :into; that’s the state the file was in when i linked it, and that’s what the problem was 🙂 )


@jrheard Late to your spec party but, yeah, naïve s/and has been my primary trigger for that error — I’ve taken to using s/with-gen quite a lot to "help" clojure.spec exercise stuff.


I s/fdefed a function but the doc string doesn't contain my spec. Did I miss something?


Is this the expected behavior?

(require '[clojure.core.specs :as clj-specs])
(def c (s/conform ::clj-specs/defn-args '(t
                                           [x y]
=> #'boot.user/c
(s/unform ::clj-specs/defn-args c)
=> (t (x y) x)
Shouldn’t the args be unconformed to a vector, not a list?


(let [args '(t
              [x y]
      args-s ::clj-specs/defn-args
      c (s/conform args-s args)]
  (s/conform args-s (s/unform args-s c)))
=> :clojure.spec/invalid


::arg-list seems to use s/and not s/coll-of


still, wouldn't flowing back through vector? cause it to be turned into a vector? shrug