Fork me on GitHub
#clojure-spec
<
2017-09-29
>
johanatan00:09:09

s/gen (or s/with-gen) seems to unnecessarily use such-that

johanatan00:09:41

causing its associated spec's gen to be usable for only a few elements

johanatan00:09:52

(s/def ::kebab-str
  (s/with-gen
    #(= %1 (csk/->kebab-case %1))
    #(gen/fmap clojure.string/join (gen/list (gen/frequency [[1 (s/gen #{\-})] [9 gen/char-alpha]])))))

johanatan00:09:04

[`csk` is camel-snake-kebab]

johanatan00:09:26

core> (s/gen ::kebab-str)
#clojure.test.check.generators.Generator{:gen #function[clojure.test.check.generators/such-that/fn--17362]}

johanatan00:09:45

the such-that above is suspicious

johanatan00:09:08

and the result is usuable only for a few elements:

core> (gen/sample (s/gen ::kebab-str) 10)
("" "" "i" "rc" "sh" "s" "gh" "q" "" "od")

johanatan00:09:23

and not for many:

core> (gen/sample (s/gen ::kebab-str) 100)
ExceptionInfo Couldn't satisfy such-that predicate after 100 tries.  clojure.core/ex-info (core.clj:4725)

seancorfield00:09:39

@johanatan Generators always validate against the spec. Your generator probably generates values that do not satisfy your predicate.

seancorfield00:09:34

What does your predicate return for "-a" or "---"?

johanatan00:09:44

@seancorfield ah, good call. looks like first digit needs to be non-dash

johanatan00:09:13

@seancorfield interesting that I wasn't getting a more obvious exception though (which I did get with a more egregious disparity between the validator and generator)

johanatan00:09:41

FYI... this version works:

(s/def ::kebab-str
  (s/with-gen
    #(= %1 (csk/->kebab-case %1))
    #(gen/fmap (comp clojure.string/lower-case clojure.string/join)
               (gen/such-that (fn [c] (and (not-empty c) (not= \- (first c)) (not= \- (last c))))
                              (gen/list (gen/frequency [[1 (s/gen #{\-})] [9 gen/char-alpha]]))))))

seancorfield01:09:15

You probably ought to look at Gary Fredericks' test.chuck library -- it contains a generator for regex strings which would make this a lot simpler!

seancorfield01:09:14

Aside from anything else, you could then define ::kebab-str as a regex predicate and use test.chuck's regex generator directly on the same regex.

amar02:09:18

Hello! Any ideas on what I’m doing wrong here. (s/valid? ::bar-path ["foo" 1]) is ok, but not so in an s/fdef

(s/def ::foo-path
  (s/cat :foo (partial = "foo")))

(s/def ::bar-path
  (s/cat :foo ::foo-path :idx nat-int?))

(s/valid? ::foo-path ["foo"]) ; => true
(s/valid? ::bar-path ["foo" 1]) ; => true

(s/fdef my-fn
        :args (s/cat :path ::bar-path)
        :ret int?)

(defn my-fn
  [path]
  (second path))

(stest/instrument)
(my-fn ["foo" 1]) ; => my-fn did not conform to spec

seancorfield05:09:55

@amar The s/cat calls combine to make one sequence regex. I think you can say (s/spec ::bar-path) as the :path argument spec and it will work.

seancorfield05:09:45

You might also want to look at s/tuple for your ::bar-path spec instead of s/cat.

amar11:09:36

seancorfield: Thanks. The following worked.

(s/fdef my-fn
        :args (s/cat :path (s/spec ::bar-path))
        :ret int?)