test-check

mfikes 2017-06-20T16:49:22.213176Z

There is an evidently breaking change in the 0.10.0-alpha1 release, somehow related to the introduction of the result protocol (https://github.com/clojure/test.check/commit/95112167af9636a48d3174ddd03409e8ac752179). Was it intentional that this be a breaking change (does it require clients to update?) Here is a related ticket in the ClojureScript compiler regarding it: https://dev.clojure.org/jira/browse/CLJS-2110

2017-06-20T16:52:45.291740Z

@mfikes the issue is spec returning an exception object?

mfikes 2017-06-20T16:53:11.301601Z

To be honest, I haven’t sorted out why things are regressing.

2017-06-20T16:53:53.318097Z

So spec used a hacky workaround that the result protocol was meant to obviate

mfikes 2017-06-20T16:53:58.320191Z

I only know empirically right now based on bisecting. I’ve read the code to at least attempt to grok why, but haven’t gained comprehension yet.

2017-06-20T16:54:36.335548Z

So ideally spec should change to the better version, but I don't think I meant it to break. Though I might have

mfikes 2017-06-20T16:55:06.347069Z

(I also don’t know if this affects Clojure—my experience with it is with JVM and self-hosted ClojureScript, where it breaks in the same way for both.)

2017-06-20T16:55:08.347931Z

I don't have a sense for what parts are changing at what speed 😕

2017-06-20T16:55:49.364021Z

And whether it would be bad for spec to require tc alpha

mfikes 2017-06-20T16:55:49.364210Z

Cool. If there was no clear intent to be a breaking change, I’ll dig further to see if can suss out what is going on.

2017-06-20T16:57:20.400492Z

I expect making it nonbreaking would consist of having t.c continue special-casing return values that are Exceptions

mfikes 2017-06-20T16:59:56.462955Z

That helps… I’ll dig deeper—t.c. 0.10.0 is still alpha, after all.

alexmiller 2017-06-20T17:05:34.603433Z

I would say it’s possible that spec.alpha could depend on an alpha

alexmiller 2017-06-20T17:05:46.608046Z

but that regardless of that, t.c shouldn’t introduce a breaking change

alexmiller 2017-06-20T17:06:09.616999Z

and if the semantics need to change, it should have a new name

mfikes 2017-06-20T17:36:22.330391Z

FWIW, t.c 0.10.0-alpha1 has a breaking change for Clojure 1.9.0-alpha17 for this case as well:

$ lein repl
nREPL server started on port 55971 on host 127.0.0.1 - <nrepl://127.0.0.1:55971>
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.9.0-alpha17
Java HotSpot(TM) 64-Bit Server VM 1.8.0_131-b11
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=&gt; (require '[clojure.spec.alpha :as s]
  #_=&gt;   '[clojure.spec.test.alpha :as st])
nil
user=&gt; (defn ranged-rand [start end] 0)
#'user/ranged-rand
user=&gt; (s/fdef ranged-rand
  #_=&gt;   :args (s/and (s/cat :start int? :end int?)
  #_=&gt;                #(&lt; (:start %) (:end %)))
  #_=&gt;   :ret int?
  #_=&gt;   :fn (s/and #(&gt;= (:ret %) (-&gt; % :args :start))
  #_=&gt;              #(&lt; (:ret %) (-&gt; % :args :end))))
user/ranged-rand
user=&gt; (st/check `ranged-rand)
({:spec #object[clojure.spec.alpha$fspec_impl$reify__1215 0x2af92bb9 "clojure.spec.alpha$fspec_impl$reify__1215@2af92bb9"], :clojure.spec.test.check/ret {:result true, :num-tests 1000, :seed 1497980057359}, :sym user/ranged-rand})

alexmiller 2017-06-20T17:38:41.385523Z

what’s the change?

alexmiller 2017-06-20T17:39:22.401885Z

at a glance I don’t see anything amiss there

mfikes 2017-06-20T17:40:11.422277Z

Right, it might not be a breaking change, but an internal bug. Here is what it used to do…

mfikes 2017-06-20T17:41:10.445678Z

Here is a gist… it is too big to really paste: https://gist.github.com/mfikes/1caa83751019495cfdda83e79ad7e3ed

mfikes 2017-06-20T17:43:17.496005Z

Internal bug because: It doesn’t return a falsey :result Breaking change because the :result used to be much richer than just a Boolean value, and clients might not know to look for :result-data

alexmiller 2017-06-20T17:44:14.518179Z

it just looks to me like it found a failing example in the old one but didn’t in the new one

alexmiller 2017-06-20T17:44:37.528027Z

which might just be due to what the random seed turned up, not a real difference

mfikes 2017-06-20T17:44:47.531898Z

Ahh. Right.

mfikes 2017-06-20T17:45:03.538547Z

I’ll increase the number of tests to see what occurs.

alexmiller 2017-06-20T17:46:47.580373Z

I guess maybe it’s surprising that t.c found 1000 successful cases with a hard-coded result in the new one

mfikes 2017-06-20T17:47:32.599094Z

True. Seems unlikely to have pulled that off 🙂

alexmiller 2017-06-20T17:48:26.620641Z

so maybe just looking at (gen/sample (s/gen (:args (s/get-spec user/ranged-rand))) 1000) might be interesting as a before / after

mfikes 2017-06-20T17:50:17.664292Z

user=&gt; (gen/sample (s/gen (:args (s/get-spec 'user/ranged-rand))) 10)
((0 1) (-1 0) (-1 2) (0 1) (-23 2) (1 3) (-16 -6) (-92 -10) (-1 2) (2 7))

mfikes 2017-06-20T17:50:38.672915Z

(2 7) should fail, right?

alexmiller 2017-06-20T17:50:50.677798Z

yeah

alexmiller 2017-06-20T17:51:17.689140Z

as should (-92 -10)

mfikes 2017-06-20T17:51:30.694445Z

Yep… many don’t include 0

alexmiller 2017-06-20T17:51:32.694990Z

or (1 3)

alexmiller 2017-06-20T17:51:44.699819Z

or (-1 0) - which is the failing example in the old one

alexmiller 2017-06-20T17:52:04.707747Z

so the generator seems fine

alexmiller 2017-06-20T17:52:14.712003Z

and next we should suspect the prop checking

mfikes 2017-06-20T17:59:41.895379Z

I revised my ranged-rand to prn its args, and also revised the :fn part of the fdef to print its argument, and at least those parts look correct

user=&gt; (st/check `ranged-rand {:clojure.spec.test.check/opts {:num-tests 2}})
-1 1
{:args {:start -1, :end 1}, :ret 0}
-4 -2
{:args {:start -4, :end -2}, :ret 0}
({:spec #object[clojure.spec.alpha$fspec_impl$reify__1215 0x77b18e9a "clojure.spec.alpha$fspec_impl$reify__1215@77b18e9a"], :clojure.spec.test.check/ret {:result true, :num-tests 2, :seed 1497981519034}, :sym user/ranged-rand})

2017-06-20T18:08:14.104442Z

hi I'm back

mfikes 2017-06-20T18:08:37.113559Z

TL;DR of the above is just reproing it in Clojure

mfikes 2017-06-20T18:10:30.157955Z

I suppose there is the twist that there might be an internal bug in t.c (disregarding any potential interface changes)

2017-06-20T18:11:01.169645Z

@mfikes regarding the first issue, it definitely looks like I didn't retain the old behavior; an extra clause here should fix it: https://github.com/clojure/test.check/blob/23e1fcc1bb7f3f2d0202ed99eee202a0b1f2c652/src/main/clojure/clojure/test/check/results.cljc#L18

2017-06-20T18:11:24.178542Z

I'm assuming cljs has something like Throwable for extending error types, but maybe that's not true?

mfikes 2017-06-20T18:11:48.188067Z

It might be possible to extend js/Error or somesuch

mfikes 2017-06-20T18:19:52.378382Z

@gfredericks Evidently you can. Not sure what the implications might be.

cljs.user=&gt; (defprotocol Result
       #_=&gt;   (passing? [result])
       #_=&gt;   (result-data [result] "A map of data about the trial."))
nil
cljs.user=&gt; (extend-protocol Result
       #_=&gt;   js/Error
       #_=&gt;   (passing? [this] false)
       #_=&gt;   (result-data [this] (ex-data this)))
#object[Function "function (this$){
var this$__$1 = this;
return cljs.core.ex_data.call(null,this$__$1);
}"]
cljs.user=&gt; (result-data (ex-info "hi" {:a 1}))
{:a 1}

mfikes 2017-06-20T18:20:10.385359Z

^ works in JVM and self-hosted ClojureScript

2017-06-20T18:20:16.387583Z

@mfikes does it work for (throw "some string")?

mfikes 2017-06-20T18:22:54.451146Z

@gfredericks If you extract using ex-message:

cljs.user=&gt; (extend-protocol Result
       #_=&gt;   js/Error
       #_=&gt;   (passing? [this] false)
       #_=&gt;   (result-data [this] (ex-message this)))
#object[Function "function (this$){
var this$__$1 = this;
return cljs.core.ex_message.call(null,this$__$1);
}"]
cljs.user=&gt; (result-data (js/Error. "some string"))
"some string"

mfikes 2017-06-20T18:24:43.494418Z

I’ll see what happens if I build a t.c that further extends Result to exceptions

2017-06-20T18:30:28.632279Z

I was just concerned that if you throw a string, the thrown thing is not a js/Error and so wouldn't match that clause

mfikes 2017-06-20T18:30:43.638261Z

Ahh

mfikes 2017-06-20T18:43:48.948732Z

At least for the exception thrown for the case above, further extending does the trick

mfikes 2017-06-20T18:44:40.968860Z

cljs.user=&gt; (st/check `ranged-rand)
[{:spec #object[cljs.spec.alpha.t_cljs$spec$alpha5600],
  :clojure.test.check/ret {:result false,
                           :result-data {:a 1},
                           :seed 1497984200982,
                           :failing-size 0,
                           :num-tests 1,
                           :fail [(-1 0)],
                           :shrunk {:total-nodes-visited 0,
                                    :depth 0,
                                    :result false,
                                    :result-data {:a 1},
                                    :smallest [(-1 0)]}},
  :sym cljs.user/ranged-rand,
  :failure false}]
With this kind of extension:
(passing? [this] false)
-  (result-data [this] {}))
+  (result-data [this] {})
+
+  #?(:clj Throwable :cljs js/Error)
+  (passing? [this] false)
+  (result-data [this] {:a 1}))

mfikes 2017-06-20T18:48:06.052038Z

Perhaps :failure false is not what you’d want. Hrm.

mfikes 2017-06-20T20:15:08.988135Z

Captured the above with https://dev.clojure.org/jira/browse/TCHECK-131

2017-06-20T21:34:44.633967Z

@mfikes great, thanks

2017-06-20T21:35:19.644144Z

I'll make sure to get another alpha out shortly

mfikes 2017-06-20T21:43:45.786820Z

Thanks @gfredericks !

👍 1