Unofficial community-driven specs for clojure.core
.
Clojure 1.10.0
user=> (require '[speculative.instrument :as i])
nil
user=> (i/instrument)
[clojure.core/every-pred clojure.core/max clojure.string/join ...]
user=> (subs "foo" -1)
Execution error - invalid arguments to clojure.core/subs at (REPL:1).
-1 - failed: nat-int? at: [:start] spec: :speculative.specs/nat-int
By writing, using and maintaining core specs, and reflecting upon them, we get the following benefits:
- Better error messages during development and testing
- Discover where Clojure and ClojureScript functions behave differently and when possible fix it
- Discover the established usages of functions within the Clojure community
- Provide data for re-find, an app that helps you find functions using specs
These specs reflect what we currently know about the newest versions of Clojure and ClojureScript. These specs are in no way definitive or authoritative. They may evolve based on new insights and changes in Clojure. These specs have no official status, are not endorsed by Cognitect and are provided without warranty.
{:deps {speculative {:mvn/version "0.0.3"}}}
[speculative "0.0.3"]
Speculative specs correspond to the namespaces in Clojure:
speculative.core -> clojure.core
speculative.set -> clojure.set
speculative.string -> clojure.string
To load all specs at once, you can require speculative.instrument
which also
provides functions to only instrument speculative specs.
$ clj
Clojure 1.10.0
user=> (require '[speculative.instrument :as i])
nil
user=> (i/instrument)
[clojure.core/every-pred clojure.core/max clojure.string/join ...]
user=> (merge-with 1 {:a 2} {:a 3})
Execution error - invalid arguments to clojure.core/merge-with at (REPL:1).
1 - failed: ifn? at: [:f] spec: :speculative.specs/ifn
user=> (i/unstrument)
...
user=> (merge-with 1 {:a 2} {:a 3})
Execution error (ClassCastException) at user$eval344/invokeStatic (REPL:1).
java.lang.Long cannot be cast to clojure.lang.IFn
To instrument during testing, you can use the fixture
from
speculative.instrument
:
(require '[clojure.test :as t])
(require '[speculative.instrument :as i])
(t/use-fixtures :once i/fixture)
This will turn on instrumentation before the tests and turn it off after.
If you run tests with kaocha you can use the speculative kaocha plugin.
Speculative specs find, when instrumented, invalid or undefined usage of Clojure
core functions. If code is under your control, you can fix it. If the call was
made in a library not under your control, you can unstrument the spec using
clojure.spec.test.alpha/unstrument
, unload it using (s/def spec-symbol nil)
or disable it within the scope of a body using
respeced.test/with-unstrumentation
(see
respeced):
$ clj
Clojure 1.10.0
user=> (require '[respeced.test :refer [with-unstrumentation]])
nil
user=> (require '[speculative.instrument :as i])
nil
user=> (i/instrument)
[clojure.core/first clojure.core/apply clojure.core/assoc ...]
user=> (merge #{1 2 3} 4)
Execution error - invalid arguments to clojure.core/merge at (REPL:1).
#{1 3 2} - failed: map? at: [:maps :init-map :clojure.spec.alpha/pred]
#{1 3 2} - failed: nil? at: [:maps :init-map :clojure.spec.alpha/nil]
user=> (respeced.test/with-unstrumentation `merge (merge #{1 2 3} 4))
#{1 4 3 2}
If you believe the spec was insuffient, please create an issue.
To execute all tests, simply run script/test
. Running script/clean
before
running tests is recommended, especially for ClojureScript on Node. The script
script/test
automatically calls script/clean
for you.
To specify environments:
TEST_ENV=clj script/test-runner
TEST_ENV=cljs CLJS_ENV=node script/test-runner
TEST_ENV=cljs CLJS_ENV=planck script/test-runner
By default the number of generative tests is set to 50
, but this can be
overriden by setting the environment variable NUM_TESTS
:
NUM_TESTS=1001 script/test
clojure -A:test:clj-test-runner -v speculative.core-test/assoc-in-test
clojure -A:test:cljs-test-runner -v speculative.core-test/assoc-in-test
clojure -A:test:cljs-test-runner -x planck -v speculative.core-test/assoc-in-test
Coal-mine is a collection of 4clojure solutions. These can be used to verify speculative specs.
Run a random coal-mine problem:
script/coal-mine
Run a specific coal-mine problem:
script/coal-mine --problem 77
Run a range of coal-mine problems:
script/coal-mine --from 10 --to 15
Both from
and to
are inclusive.
Run with additional checks on ret
and fn
specs via
orchestra (EXPERIMENTAL):
script/coal-mine --from 10 --to 15 --ret-spec true
To skip an environment (CLJ or CLJS):
SKIP_CLJS=true script/coal-mine
To verify a spec against examples from ClojureDocs,
run (for e.g. update-in
):
clj -A:test:clojuredocs -v clojure.core/update-in
KLIPSE REPL with speculative and expound.
The project started based on two tweets. First @mfikes tweeted
I still hold the view that Clojure’s core fns should have specs.
— Mike Fikes (@mfikes) October 19, 2018
Ex: While
(merge-with + [0 3] {0 7 1 2} {0 3 2 32})
produces a reasonable result, it is not even a map. A spec would reject 2nd arg.
What if I conclude dot products are possible via
(merge-with + [0 3] [1 2])
?
Then @borkdude tweeted a couple of days later:
Or maybe have a development version with guards and a production version without guards (I think Stu said something like this)
— (λ. borkdude) (@borkdude) October 19, 2018
These issues were found while developing and using speculative.
These projects are known to use speculative.
In case this code will ever be useful to clojure.core
, any contributer to this
project needs to sign the Contributor
Agreement for Clojure so that any
code in speculative can be used in either Clojure or Clojurescript.
Please have look at the contributor guidelines before submitting a PR.
(Generated by Hall-Of-Fame)
Copyright © 2018 Erik Assum, Michiel Borkent and Mike Fikes
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.