├── deps.edn
├── .github
└── workflows
│ ├── test.yml
│ ├── snapshot.yml
│ └── release.yml
├── test-runners
├── run_tests_adv.html
├── run.js
└── run_tests_dev.html
├── script
├── test-self-host
└── test-self-host.clj
├── doc
├── api-docs-for-older-versions.md
├── migrating-from-simple-check.md
├── development.md
├── generator-examples.md
├── cheatsheet.md
├── intro.md
├── growth-and-shrinking.md
└── old-confluence-notes.md
├── .gitignore
├── Makefile
├── src
├── target
│ └── cljs
│ │ ├── browser
│ │ └── clojure
│ │ │ └── test
│ │ │ └── check
│ │ │ └── test
│ │ │ └── runner.cljs
│ │ ├── node
│ │ └── clojure
│ │ │ └── test
│ │ │ └── check
│ │ │ └── test
│ │ │ └── runner.cljs
│ │ └── self-host
│ │ └── clojure
│ │ └── test
│ │ └── check
│ │ └── test
│ │ ├── aux.cljs
│ │ └── runner.cljs
├── main
│ └── clojure
│ │ └── clojure
│ │ └── test
│ │ ├── check
│ │ ├── impl.cljc
│ │ ├── clojure_test
│ │ │ ├── assertions
│ │ │ │ └── cljs.cljc
│ │ │ └── assertions.cljc
│ │ ├── results.cljc
│ │ ├── random
│ │ │ ├── doubles.cljs
│ │ │ ├── longs
│ │ │ │ └── bit_count_impl.cljs
│ │ │ └── longs.cljs
│ │ ├── properties.cljc
│ │ ├── random.cljs
│ │ ├── random.clj
│ │ ├── rose_tree.cljc
│ │ └── clojure_test.cljc
│ │ └── check.cljc
└── test
│ └── clojure
│ └── clojure
│ └── test
│ └── check
│ ├── results_test.cljc
│ ├── rose_tree_test.cljc
│ ├── random_test.cljs
│ ├── test_specs.clj
│ ├── random_test.clj
│ └── clojure_test_test.cljc
├── CONTRIBUTING.md
├── pom.xml
├── project.clj
├── README.md
├── CHANGELOG.markdown
└── LICENSE
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src/main/clojure"]
2 | :deps {}}
3 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | call-test:
7 | uses: clojure/build.ci/.github/workflows/test.yml@master
8 |
--------------------------------------------------------------------------------
/test-runners/run_tests_adv.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Snapshot on demand
2 |
3 | on: [workflow_dispatch]
4 |
5 | jobs:
6 | call-snapshot:
7 | uses: clojure/build.ci/.github/workflows/snapshot.yml@master
8 | secrets: inherit
9 |
--------------------------------------------------------------------------------
/script/test-self-host:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rm -rf target/out-self-host
4 | mkdir -p target/out-self-host/cljs/analyzer
5 | lein with-profile self-host run script/test-self-host.clj
6 | node target/out-self-host/main.js
7 |
--------------------------------------------------------------------------------
/doc/api-docs-for-older-versions.md:
--------------------------------------------------------------------------------
1 | # API Docs for Older Versions
2 |
3 | - [0.9.0](https://clojure.github.io/test.check/0.9.0)
4 | - [0.8.2](https://clojure.github.io/test.check/0.8.2)
5 | - [0.6.2](https://clojure.github.io/test.check/0.6.2)
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /lib
3 | /classes
4 | /checkouts
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | .lein-deps-sum
9 | .lein-failures
10 | .lein-plugins
11 | .lein-repl-history
12 | .nrepl-port
13 | /bin/
14 | /.classpath
15 | /.project
16 | /.settings/
17 | .idea/
18 | *.iml
19 | .cpcache/
20 |
--------------------------------------------------------------------------------
/test-runners/run.js:
--------------------------------------------------------------------------------
1 | try {
2 | require("source-map-support").install();
3 | } catch(err) {
4 | }
5 | require("../target/cljs/node_dev/out/goog/bootstrap/nodejs.js");
6 | require("../target/cljs/node_dev/tests.js");
7 | goog.require("clojure.test.check.test.runner");
8 | goog.require("cljs.nodejscli");
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: pages docs
2 |
3 | ##
4 | ## Doc targets
5 | ##
6 |
7 | docs:
8 | lein doc
9 |
10 | pages: docs
11 | rm -rf /tmp/org.clojure-test.check-docs
12 | mkdir -p /tmp/org.clojure-test.check-docs
13 | cp -R doc/ /tmp/org.clojure-test.check-docs
14 | git checkout gh-pages
15 | cp -R /tmp/org.clojure-test.check-docs/* .
16 | git add .
17 | git add -u
18 | git commit
19 | git push origin gh-pages
20 | git checkout master
21 |
--------------------------------------------------------------------------------
/test-runners/run_tests_dev.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release on demand
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | releaseVersion:
7 | description: "Version to release"
8 | required: true
9 | snapshotVersion:
10 | description: "Snapshot version after release"
11 | required: true
12 |
13 | jobs:
14 | call-release:
15 | uses: clojure/build.ci/.github/workflows/release.yml@master
16 | with:
17 | releaseVersion: ${{ github.event.inputs.releaseVersion }}
18 | snapshotVersion: ${{ github.event.inputs.snapshotVersion }}
19 | secrets: inherit
--------------------------------------------------------------------------------
/src/target/cljs/browser/clojure/test/check/test/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.test.runner
2 | (:require [cljs.test :as test :refer-macros [run-tests]]
3 | [clojure.test.check.generators :as gen]
4 | [clojure.test.check.test]
5 | [clojure.test.check.random-test]
6 | [clojure.test.check.rose-tree-test]
7 | [clojure.test.check.clojure-test-test]))
8 |
9 | (enable-console-print!)
10 |
11 | (run-tests
12 | 'clojure.test.check.test
13 | 'clojure.test.check.random-test
14 | 'clojure.test.check.rose-tree-test
15 | 'clojure.test.check.clojure-test-test)
16 |
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This is a [Clojure contrib] project.
2 |
3 | __We cannot accept pull requests.__
4 |
5 | * Issues can be filed in [JIRA]. No [Clojure CA] is required for this.
6 | * Patches/contributions can be attached to issues in [Jira]. You can [read more
7 | about that process](https://clojure.org/community/contributing).
8 | Contributions need a [signed CA (Contributor
9 | Agreement)](https://clojure.org/community/contributing).
10 |
11 | Don't hesitate to ask if you have questions about this.
12 |
13 | [JIRA]: https://clojure.atlassian.net/browse/TCHECK
14 | [Clojure CA]: https://clojure.org/community/contributing
15 | [Clojure contrib]: https://clojure.org/community/contrib_libs
16 |
--------------------------------------------------------------------------------
/script/test-self-host.clj:
--------------------------------------------------------------------------------
1 | (require '[cljs.build.api]
2 | '[clojure.java.io :as io])
3 |
4 | (cljs.build.api/build "src/target/cljs/self-host"
5 | {:main 'clojure.test.check.test.runner
6 | :output-to "target/out-self-host/main.js"
7 | :output-dir "target/out-self-host"
8 | :target :nodejs
9 | :cache-analysis-format :edn})
10 |
11 | (defn copy-source
12 | [filename]
13 | (spit (str "target/out-self-host/" filename)
14 | (slurp (io/resource filename))))
15 |
16 | (copy-source "cljs/test.cljc")
17 | (copy-source "cljs/analyzer/api.cljc")
18 | (copy-source "cljs/reader.clj")
19 | (copy-source "clojure/template.clj")
20 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/impl.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.impl)
11 |
12 | (defn get-current-time-millis []
13 | #?(:clj (System/currentTimeMillis)
14 | :cljs (.valueOf (js/Date.))))
15 |
--------------------------------------------------------------------------------
/src/target/cljs/node/clojure/test/check/test/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.test.runner
2 | (:require [cljs.nodejs :as nodejs]
3 | [cljs.test :as test :refer-macros [run-tests]]
4 | [clojure.test.check.test]
5 | [clojure.test.check.random-test]
6 | [clojure.test.check.rose-tree-test]
7 | [clojure.test.check.clojure-test-test]
8 | [clojure.test.check.generators :as gen]))
9 |
10 | (nodejs/enable-util-print!)
11 |
12 | (defn -main []
13 | (run-tests
14 | 'clojure.test.check.test
15 | 'clojure.test.check.random-test
16 | 'clojure.test.check.rose-tree-test
17 | 'clojure.test.check.clojure-test-test))
18 |
19 | (set! *main-cli-fn* -main)
20 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/clojure_test/assertions/cljs.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey and contributors. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.test.check.clojure-test.assertions.cljs)
10 |
11 | #?(:clj
12 | (try
13 | (require 'cljs.test
14 | '[clojure.test.check.clojure-test.assertions :as assertions])
15 |
16 | (eval
17 | '(defmethod cljs.test/assert-expr 'clojure.test.check.clojure-test/check?
18 | [_ msg form]
19 | (assertions/check? msg form)))
20 | (catch java.io.FileNotFoundException e)))
21 |
--------------------------------------------------------------------------------
/doc/migrating-from-simple-check.md:
--------------------------------------------------------------------------------
1 | # Migrating from simple-check
2 |
3 | _test.check_ used to be called
4 | [_simple-check_](https://github.com/reiddraper/simple-check).
5 |
6 | In order to migrate from _simple-check_ to _test.check_, you'll need to do two
7 | things:
8 |
9 | * Update project.clj
10 |
11 | In your `project.clj` replace `[reiddraper/simple-check "0.5.6"]` with
12 | `[org.clojure/test.check "0.6.2"]` (note: your version numbers may be
13 | different).
14 |
15 | * Update namespace declarations
16 |
17 | Update your namespaces: `simple-check.core` becomes `clojure.test.check` (note
18 | the dropping of 'core'). For everything else you can simply replace `simple-check`
19 | with `clojure.test.check`. Let's make it easy:
20 |
21 | ```shell
22 | find test -name '*.clj' -print0 | xargs -0 sed -i.bak \
23 | -e 's/simple-check.core/clojure.test.check/' \
24 | -e 's/simple-check/clojure.test.check/'
25 | ```
26 |
27 | Review the updates.
28 |
--------------------------------------------------------------------------------
/doc/development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | _(i.e., working on test.check itself)_
4 |
5 | ## Links
6 |
7 | * [GitHub project](https://github.com/clojure/test.check)
8 | * [Bug Tracker](https://clojure.atlassian.net/browse/TCHECK)
9 | * [Continuous Integration](https://build.clojure.org/job/test.check/)
10 | * [Compatibility Test Matrix](https://build.clojure.org/job/test.check-test-matrix/)
11 |
12 | ## Tests
13 |
14 | test.check runs in both jvm-clojure and clojurescript, so testing
15 | comprehensively requires several steps:
16 |
17 | * Run `lein test` to run the JVM tests (requires [Leiningen](https://leiningen.org))
18 | * Run `lein cljsbuild once` to run the ClojureScript tests (also requires [node.js](https://nodejs.org))
19 | * To run the same tests in a web browser, open (after running the above command)
20 | `test-runners/run_tests_dev.html` and `test-runners/run_tests_adv.html` and watch the
21 | javascript console for output
22 | * Run `script/test-self-host` to run the self-hosted ClojureScript tests (also requires [node.js](https://nodejs.org))
23 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/results_test.cljc:
--------------------------------------------------------------------------------
1 | ; All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.test.check.results-test
10 | (:require #?(:cljs
11 | [cljs.test :as test :refer-macros [are deftest testing is]])
12 | #?(:clj
13 | [clojure.test :refer :all])
14 | [clojure.test.check.results :as results]))
15 |
16 | (deftest default-passing-values
17 | (is (not (results/pass? nil)))
18 | (is (not (results/pass? false)))
19 | (are [x] (results/pass? x)
20 | :keyword
21 | 'symbol
22 | "string"
23 | []
24 | {}
25 | #{}
26 | ()
27 | 42
28 | 42.0
29 | true))
30 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/results.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"
11 | :doc "A protocol and helper functions for trial results."}
12 | clojure.test.check.results)
13 |
14 | (defprotocol Result
15 | (pass? [result] "A boolean indicating if the result passed.")
16 | (result-data [result] "A map of data about the trial."))
17 |
18 | (extend-protocol Result
19 | #?(:clj Object :cljs default)
20 | (pass? [this] (boolean this))
21 | (result-data [this] nil)
22 |
23 | nil
24 | (pass? [this] false)
25 | (result-data [this] nil))
26 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/random/doubles.cljs:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"}
11 | clojure.test.check.random.doubles
12 | (:require [clojure.test.check.random.longs :as longs]))
13 |
14 | (def ^:private double-unit
15 | (loop [i 53 x 1]
16 | (if (zero? i)
17 | x
18 | (recur (dec i) (/ x 2)))))
19 |
20 | (def ^:private big-double-unit
21 | ;; (* double-unit 0x100000000)
22 | (* double-unit 4294967296))
23 |
24 | (defn rand-long->rand-double
25 | "Given a uniformly distributed random long, returns a uniformly
26 | distributed double between 0.0 (inclusive) and 1.0 (exclusive)."
27 | [long]
28 | (let [x (longs/unsigned-bit-shift-right long 11)
29 | low-bits (.getLowBitsUnsigned x)
30 | high-bits (.getHighBits x)]
31 | (+ (* double-unit low-bits)
32 | (* big-double-unit high-bits))))
33 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/rose_tree_test.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.rose-tree-test
11 | (:require [clojure.test.check.generators :as gen]
12 | [clojure.test.check.properties :as prop]
13 | [clojure.test.check.rose-tree :as rose]
14 | [clojure.test.check.clojure-test :as ct :refer [defspec]]))
15 |
16 | (defn depth-one-children
17 | [rose]
18 | (into [] (map rose/root (rose/children rose))))
19 |
20 | (defn depth-one-and-two-children
21 | [rose]
22 | (let [the-children (rose/children rose)]
23 | (into []
24 | (concat
25 | (map rose/root the-children)
26 | (map rose/root (mapcat rose/children the-children))))))
27 |
28 | (defspec test-collapse-rose
29 | 100
30 | (prop/for-all [i gen/small-integer]
31 | (let [tree (#'gen/int-rose-tree i)]
32 | (= (depth-one-and-two-children tree)
33 | (depth-one-children (rose/collapse tree))))))
34 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/random/longs/bit_count_impl.cljs:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"}
11 | clojure.test.check.random.longs.bit-count-impl
12 | (:refer-clojure :exclude [bit-count]))
13 |
14 | ;; Had to put this in a separate namespace because the + doesn't get
15 | ;; inlined if it's called as (core/+ ...). This might change in future
16 | ;; CLJS versions?
17 |
18 | (def ^:private lookup
19 | (let [arr (make-array 256)]
20 | (aset arr 0 0)
21 | (dotimes [i 256]
22 | (aset arr i (+ (aget arr (bit-shift-right i 1))
23 | (bit-and i 1))))
24 | arr))
25 |
26 | (defn bit-count
27 | "Returns a JS number (not a Long), the number of set bits in the
28 | given Long."
29 | [x]
30 | (let [low (.-low_ x)
31 | high (.-high_ x)]
32 | (+ (aget lookup (-> low (bit-and 255)))
33 | (aget lookup (-> low (bit-shift-right 8) (bit-and 255)))
34 | (aget lookup (-> low (bit-shift-right 16) (bit-and 255)))
35 | (aget lookup (-> low (bit-shift-right 24) (bit-and 255)))
36 | (aget lookup (-> high (bit-and 255)))
37 | (aget lookup (-> high (bit-shift-right 8) (bit-and 255)))
38 | (aget lookup (-> high (bit-shift-right 16) (bit-and 255)))
39 | (aget lookup (-> high (bit-shift-right 24) (bit-and 255))))))
40 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | test.check
5 | 1.1.3-SNAPSHOT
6 | test.check
7 | A QuickCheck inspired property-based testing library
8 | https://github.com/clojure/test.check
9 |
10 |
11 | org.clojure
12 | pom.contrib
13 | 1.3.0
14 |
15 |
16 |
17 |
18 | Eclipse Public License 1.0
19 | https://opensource.org/license/epl-1-0/
20 | repo
21 |
22 |
23 |
24 |
25 |
26 | reiddraper
27 | Reid Draper
28 | https://github.com/reiddraper
29 |
30 |
31 |
32 |
33 | 1.9.0
34 | true
35 |
36 |
37 |
38 |
39 | org.clojure
40 | clojurescript
41 | 1.10.520
42 | test
43 |
44 |
45 |
46 |
47 | scm:git:git://github.com/clojure/test.check.git
48 | scm:git:git://github.com/clojure/test.check.git
49 | https://github.com/clojure/test.check
50 | HEAD
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/random_test.cljs:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.random-test
2 | "Testing that the cljs impl matches the clojure impl."
3 | (:require [cljs.test :refer-macros [deftest is]]
4 | [clojure.test.check.random :as random]))
5 |
6 | (deftest longs-test
7 |
8 | ;; comparing with this code run on clj-jvm:
9 | (comment
10 | (-> 42
11 | (random/make-java-util-splittable-random)
12 | (random/split-n 17)
13 | (->> (mapcat random/split)
14 | (map random/rand-long)
15 | (reduce bit-xor))
16 | (str))
17 | =>
18 | "5298131359241775269")
19 |
20 | (is (= "5298131359241775269"
21 | (-> 42
22 | (random/make-java-util-splittable-random)
23 | (random/split-n 17)
24 | (->> (mapcat random/split)
25 | (map random/rand-long)
26 | (reduce #(.xor %1 %2)))
27 | (str)))))
28 |
29 | (deftest doubles-test
30 |
31 | ;; comparing with this code run on clj-jvm:
32 | (comment
33 |
34 | (-> -42
35 | (random/make-java-util-splittable-random)
36 | (random/split-n 17)
37 | (->> (mapcat random/split)
38 | (map random/rand-double)
39 | (reduce +))
40 | (str))
41 | =>
42 | "17.39141655134964")
43 |
44 | (is (= "17.39141655134964"
45 | (-> -42
46 | (random/make-java-util-splittable-random)
47 | (random/split-n 17)
48 | (->> (mapcat random/split)
49 | (map random/rand-double)
50 | (reduce +))
51 | (str)))))
52 |
53 | (deftest auto-seeding-test
54 | (is (distinct? (random/rand-double (random/make-random))
55 | (random/rand-double (random/make-random))
56 | (random/rand-double (random/make-random))
57 | (random/rand-double (random/make-random)))
58 | "Each call to make-random should return a different RNG."))
59 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/clojure_test/assertions.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey and contributors. All rights reserved.
2 | ; The use and distribution terms for this software are covered by the
3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ; which can be found in the file epl-v10.html at the root of this distribution.
5 | ; By using this software in any fashion, you are agreeing to be bound by
6 | ; the terms of this license.
7 | ; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.test.check.clojure-test.assertions
10 | #?(:cljs (:require-macros [clojure.test.check.clojure-test.assertions.cljs]))
11 | (:require #?(:clj [clojure.test :as t]
12 | :cljs [cljs.test :as t])))
13 |
14 | #?(:clj
15 | (defn test-context-stacktrace [st]
16 | (drop-while
17 | #(let [class-name (.getClassName ^StackTraceElement %)]
18 | (or (.startsWith class-name "java.lang")
19 | (.startsWith class-name "clojure.test$")
20 | (.startsWith class-name "clojure.test.check.clojure_test$")
21 | (.startsWith class-name "clojure.test.check.clojure_test.assertions")))
22 | st)))
23 |
24 | #?(:clj
25 | (defn file-and-line*
26 | [stacktrace]
27 | (if (seq stacktrace)
28 | (let [^StackTraceElement s (first stacktrace)]
29 | {:file (.getFileName s) :line (.getLineNumber s)})
30 | {:file nil :line nil})))
31 |
32 | (defn check-results [m]
33 | (if (:pass? m)
34 | (t/do-report
35 | {:type :pass
36 | :message (dissoc m :result)})
37 | (t/do-report
38 | (merge {:type :fail
39 | :expected {:result true}
40 | :actual m}
41 | #?(:clj (file-and-line*
42 | (test-context-stacktrace (.getStackTrace (Thread/currentThread))))
43 | :cljs (t/file-and-line (js/Error.) 4))))))
44 |
45 | (defn check?
46 | [_ form]
47 | `(let [m# ~(nth form 1)]
48 | (check-results m#)))
49 |
50 |
51 | #?(:clj
52 | (defmethod t/assert-expr 'clojure.test.check.clojure-test/check?
53 | [_ form]
54 | (check? _ form))
55 | :cljs
56 | (when (exists? js/cljs.test$macros)
57 | (defmethod js/cljs.test$macros.assert_expr 'clojure.test.check.clojure-test/check?
58 | [_ msg form]
59 | (clojure.test.check.clojure-test.assertions/check? msg form))))
60 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/test_specs.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.test-specs
2 | )
3 |
4 | (if (let [{:keys [major minor]} *clojure-version*]
5 | (and (= 1 major) (< minor 9)))
6 | ;; don't bother testing this on older clojures
7 | (def valid-reporter-fn-call? (constantly true))
8 |
9 | (do
10 | (require '[clojure.spec.alpha :as s])
11 | (eval
12 | '(do
13 |
14 | (s/def ::base
15 | (s/keys :req-un [::type ::seed ::num-tests
16 | ::property]))
17 |
18 | (defmulti type->spec :type)
19 |
20 | (defmethod type->spec :trial
21 | [_]
22 | (s/merge ::base
23 | (s/keys :req-un [::args
24 | ::result
25 | ::result-data])))
26 |
27 | (defmethod type->spec :failure
28 | [_]
29 | (s/merge ::base
30 | (s/keys :req-un [::fail
31 | ::failing-size
32 | ::result
33 | ::result-data])))
34 |
35 | (s/def ::shrunk
36 | (s/keys :req-un [::depth ::result ::result-data ::smallest ::total-nodes-visited]))
37 |
38 | (s/def ::shrinking
39 | (s/merge ::shrunk (s/keys :req-un [::args])))
40 |
41 | (defmethod type->spec :shrink-step
42 | [_]
43 | (s/merge ::base
44 | (s/keys :req-un [::fail
45 | ::failing-size
46 | ::result
47 | ::result-data
48 | ::shrinking])))
49 |
50 | (defmethod type->spec :shrunk
51 | [_]
52 | (s/merge ::base
53 | (s/keys :req-un [::fail
54 | ::failing-size
55 | ::result
56 | ::result-data
57 | ::shrunk])))
58 |
59 | (defmethod type->spec :complete
60 | [_]
61 | (s/merge ::base
62 | (s/keys :req-un [::result])))
63 |
64 | (s/def ::value (s/multi-spec type->spec :type))
65 |
66 | (defn valid-reporter-fn-call?
67 | [m]
68 | (or
69 | (s/valid? ::value m)
70 | (s/explain ::value m)))))))
71 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/properties.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.properties
11 | (:require [clojure.test.check.generators :as gen]
12 | [clojure.test.check.results :as results])
13 | #?(:cljs (:require-macros [clojure.test.check.properties :refer [for-all]])))
14 |
15 | (defrecord ErrorResult [error]
16 | results/Result
17 | (pass? [_] false)
18 | (result-data [_]
19 | ;; spelling out the whole keyword here since `::error` is
20 | ;; different in self-hosted cljs.
21 | {:clojure.test.check.properties/error error}))
22 |
23 | (defn ^:private exception?
24 | [x]
25 | (instance? #?(:clj Throwable :cljs js/Error) x))
26 |
27 | (defn ^:private apply-gen
28 | [function]
29 | (fn [args]
30 | (let [result (try
31 | (let [ret (apply function args)]
32 | ;; TCHECK-131: for backwards compatibility (mainly
33 | ;; for spec), treat returned exceptions like thrown
34 | ;; exceptions
35 | (if (exception? ret)
36 | (throw ret)
37 | ret))
38 | #?(:clj (catch java.lang.ThreadDeath t (throw t)))
39 | (catch #?(:clj Throwable :cljs :default) ex
40 | (->ErrorResult ex)))]
41 | {:result result
42 | :function function
43 | :args args})))
44 |
45 | (defn for-all*
46 | "A function version of `for-all`. Takes a sequence of N generators
47 | and a function of N args, and returns a property that calls the
48 | function with generated values and tests the return value for
49 | truthiness, like with `for-all`.
50 |
51 | Example:
52 |
53 | (for-all* [gen/large-integer gen/large-integer]
54 | (fn [a b] (>= (+ a b) a)))"
55 | [args function]
56 | (gen/fmap
57 | (apply-gen function)
58 | (apply gen/tuple args)))
59 |
60 | (defn- binding-vars
61 | [bindings]
62 | (map first (partition 2 bindings)))
63 |
64 | (defn- binding-gens
65 | [bindings]
66 | (map second (partition 2 bindings)))
67 |
68 | (defmacro for-all
69 | "Returns a property, which is the combination of some generators and
70 | an assertion that should be true for all generated values. Properties
71 | can be used with `quick-check` or `defspec`.
72 |
73 | `for-all` takes a `let`-style bindings vector, where the right-hand
74 | side of each binding is a generator.
75 |
76 | The body should be an expression of the generated values that will
77 | be tested for truthiness, unless it is a special implementation of
78 | the clojure.test.check.results/Result protocol. Exceptions in the
79 | body will be caught and treated as failures.
80 |
81 | When there are multiple binding pairs, the earlier pairs are not
82 | visible to the later pairs.
83 |
84 | If there are multiple body expressions, all but the last one are
85 | executed for side effects, as with `do`.
86 |
87 | Example:
88 |
89 | (for-all [a gen/large-integer
90 | b gen/large-integer]
91 | (>= (+ a b) a))"
92 | [bindings & body]
93 | `(for-all* ~(vec (binding-gens bindings))
94 | (fn [~@(binding-vars bindings)]
95 | ~@body)))
96 |
--------------------------------------------------------------------------------
/doc/generator-examples.md:
--------------------------------------------------------------------------------
1 | # Generator Examples
2 |
3 | The following examples assume you have the following namespace alias:
4 |
5 | ```clojure
6 | (require '[clojure.test.check.generators :as gen])
7 | ```
8 |
9 | For the most part, these are in order of simplest to most complex. They also
10 | skip over some of the built-in, basic generators.
11 |
12 | ## Integers 5 through 9, inclusive
13 |
14 | ```clojure
15 | (def five-through-nine (gen/choose 5 9))
16 | (gen/sample five-through-nine)
17 | ;; => (6 5 9 5 7 7 6 9 7 9)
18 | ```
19 |
20 | ## A random element from a vector
21 |
22 | ```clojure
23 | (def languages (gen/elements ["clojure" "haskell" "erlang" "scala" "python"]))
24 | (gen/sample languages)
25 | ;; => ("clojure" "scala" "clojure" "haskell" "clojure" "erlang" "erlang"
26 | ;; => "erlang" "haskell" "python")
27 | ```
28 |
29 | ## An integer or nil
30 |
31 | ```clojure
32 | (def int-or-nil (gen/one-of [gen/small-integer (gen/return nil)]))
33 | (gen/sample int-or-nil)
34 | ;; => (nil 0 -2 nil nil 3 nil nil 4 2)
35 | ```
36 |
37 | ## An integer 90% of the time, nil 10%
38 |
39 | ```clojure
40 | (def mostly-ints (gen/frequency [[9 gen/small-integer] [1 (gen/return nil)]]))
41 | (gen/sample mostly-ints)
42 | ;; => (0 -1 nil 0 -2 0 6 -6 8 7)
43 | ```
44 |
45 | ## Even, positive integers
46 |
47 | ```clojure
48 | (def even-and-positive (gen/fmap #(* 2 %) gen/nat))
49 | (gen/sample even-and-positive 20)
50 | ;; => (0 0 2 0 8 6 4 12 4 18 10 0 8 2 16 16 6 4 10 4)
51 | ```
52 |
53 | ## Powers of two
54 |
55 | ```clojure
56 | ;; generate exponents with gen/nat,
57 | ;; and then apply the function to them
58 | (def powers-of-two (gen/fmap #(int (Math/pow 2 %)) gen/nat))
59 | (gen/sample powers-of-two)
60 | ;; => (1 1 4 8 8 32 8 1 2 2)
61 | ```
62 |
63 | ## Sorted seq of integers
64 |
65 | ```clojure
66 | ;; apply the sort function to each generated vector
67 | (def sorted-vec (gen/fmap sort (gen/vector gen/small-integer)))
68 | (gen/sample sorted-vec)
69 | ;; => (() (-1) (-2 -2) (-1 2 3) (-1 2 4) (-3 2 3 3 4) (1)
70 | ;; => (-4 0 1 3 4 6) (-5 -4 -1 0 2 8) (1))
71 | ```
72 |
73 | ## An integer and a boolean
74 |
75 | ```clojure
76 | (def int-and-boolean (gen/tuple gen/small-integer gen/boolean))
77 | (gen/sample int-and-boolean)
78 | ;; => ([0 false] [0 true] [0 true] [3 true] [-3 false]
79 | ;; => [0 true] [4 true] [0 true] [-2 true] [-9 false])
80 | ```
81 |
82 | ## Any number but 5
83 |
84 | ```clojure
85 | (def anything-but-five (gen/such-that #(not= % 5) gen/small-integer))
86 | (gen/sample anything-but-five)
87 | ;; => (0 0 -2 1 -3 1 -4 7 -1 6)
88 | ```
89 |
90 | It's important to note that `such-that` should only be used for predicates that
91 | are _very_ likely to match. For example, you should _not_ use `such-that` to
92 | filter out random vectors that are not sorted, as is this is exceedingly
93 | unlikely to happen randomly. If you want sorted vectors, just sort them using
94 | `gen/fmap` and `sort`.
95 |
96 | ## A vector and a random element from it
97 |
98 | ```clojure
99 | (def vector-and-elem (gen/bind (gen/not-empty (gen/vector gen/small-integer))
100 | #(gen/tuple (gen/return %) (gen/elements %))))
101 | (gen/sample vector-and-elem)
102 | ;; =>([[-1] -1]
103 | ;; => [[0] 0]
104 | ;; => [[-1 -1] -1]
105 | ;; => [[2 0 -2] 2]
106 | ;; => [[0 1 1] 0]
107 | ;; => [[-2 -3 -1 1] -1]
108 | ;; => [[-1 2 -5] -5]
109 | ;; => [[5 -7 -3 7] 5]
110 | ;; => [[-1 2 2] 2]
111 | ;; => [[-8 7 -3 -2 -6] -3])
112 | ```
113 |
114 | `gen/bind` and `gen/fmap` are similar: they're both binary functions that take
115 | a generator and a function as arguments (though their argument order is
116 | reversed). They differ in what the provided function's return value should be.
117 | The function provided to `gen/fmap` should return a _value_. We saw that
118 | earlier when we used `gen/fmap` to sort a vector. `sort` returns a normal
119 | value. The function provided to `gen/bind` should return a _generator_. Notice
120 | how above we're providing a function that returns a `gen/tuple` generator? The
121 | decision of which to use depends on whether you want to simply transform the
122 | _value_ of a generator (sort it, multiply it by two, etc.), or create an
123 | entirely new generator out of it.
124 |
125 | ---
126 |
127 | Go [back](intro.md) to the intro.
128 |
--------------------------------------------------------------------------------
/src/target/cljs/self-host/clojure/test/check/test/aux.cljs:
--------------------------------------------------------------------------------
1 | (ns ^{:doc "This auxiliary namespace is not actually loaded.
2 | Its mere presence cause it to be compiled and thus causes
3 | the libs listed here to be dumped into the compiler output
4 | directory where they can be loaded on demand when running
5 | the tests in self-host mode."}
6 | clojure.test.check.test.aux
7 | (:require
8 | cljs.test
9 | goog.Delay
10 | goog.Disposable
11 | goog.Promise
12 | goog.Throttle
13 | goog.Timer
14 | goog.Uri
15 | goog.color
16 | goog.color.Hsl
17 | goog.color.Hsv
18 | goog.color.Rgb
19 | goog.color.alpha
20 | goog.color.names
21 | goog.crypt
22 | goog.crypt.Aes
23 | goog.crypt.Arc4
24 | goog.crypt.BlobHasher
25 | goog.crypt.Cbc
26 | goog.crypt.Hash
27 | goog.crypt.Hmac
28 | goog.crypt.Md5
29 | goog.crypt.Sha1
30 | goog.crypt.Sha2
31 | goog.crypt.Sha224
32 | goog.crypt.Sha256
33 | goog.crypt.Sha2_64bit
34 | goog.crypt.Sha512
35 | goog.crypt.Sha512_256
36 | goog.crypt.base64
37 | goog.crypt.baseN
38 | goog.crypt.hash32
39 | goog.crypt.hashTester
40 | goog.crypt.pbkdf2
41 | goog.date.Date
42 | goog.date.DateLike
43 | goog.date.DateRange
44 | goog.date.DateTime
45 | goog.date.Interval
46 | goog.date.UtcDateTime
47 | goog.date.duration
48 | goog.date.month
49 | goog.date.relative.TimeDeltaFormatter
50 | goog.date.relative.Unit
51 | goog.date.relativeWithPlurals
52 | goog.date.weekDay
53 | goog.format
54 | goog.format.EmailAddress
55 | goog.format.HtmlPrettyPrinter
56 | goog.format.InternationalizedEmailAddress
57 | goog.format.JsonPrettyPrinter
58 | goog.i18n.BidiFormatter
59 | goog.i18n.CharListDecompressor
60 | goog.i18n.CharPickerData
61 | goog.i18n.DateTimeFormat
62 | goog.i18n.DateTimeParse
63 | goog.i18n.GraphemeBreak
64 | goog.i18n.MessageFormat
65 | goog.i18n.NumberFormat
66 | goog.i18n.TimeZone
67 | goog.i18n.bidi
68 | goog.i18n.bidi.Dir
69 | goog.i18n.bidi.Format
70 | goog.i18n.collation
71 | goog.i18n.currency
72 | goog.i18n.mime
73 | goog.i18n.ordinalRules
74 | goog.i18n.pluralRules
75 | goog.i18n.uChar
76 | goog.i18n.uChar.LocalNameFetcher
77 | goog.i18n.uChar.RemoteNameFetcher
78 | goog.i18n.uCharNames
79 | goog.iter
80 | goog.iter.Iterable
81 | goog.iter.Iterator
82 | goog.json
83 | goog.json.EvalJsonProcessor
84 | goog.json.NativeJsonProcessor
85 | goog.json.Replacer
86 | goog.json.Reviver
87 | goog.json.Serializer
88 | goog.json.hybrid
89 | goog.locale
90 | goog.locale.TimeZoneFingerprint
91 | goog.locale.defaultLocaleNameConstants
92 | goog.locale.genericFontNames
93 | goog.locale.timeZoneDetection
94 | goog.math
95 | goog.math.AffineTransform
96 | goog.math.Bezier
97 | goog.math.Box
98 | goog.math.Coordinate
99 | goog.math.Coordinate3
100 | goog.math.ExponentialBackoff
101 | goog.math.Integer
102 | goog.math.Line
103 | goog.math.Long
104 | goog.math.Matrix
105 | goog.math.Path
106 | goog.math.Path.Segment
107 | goog.math.Range
108 | goog.math.RangeSet
109 | goog.math.Rect
110 | goog.math.Size
111 | goog.math.Vec2
112 | goog.math.Vec3
113 | goog.math.interpolator.Linear1
114 | goog.math.interpolator.Pchip1
115 | goog.math.interpolator.Spline1
116 | goog.math.paths
117 | goog.math.tdma
118 | goog.spell.SpellCheck
119 | goog.string
120 | goog.string.Const
121 | goog.string.StringBuffer
122 | goog.string.Unicode
123 | goog.string.format
124 | goog.string.newlines
125 | goog.string.newlines.Line
126 | goog.structs
127 | goog.structs.AvlTree
128 | goog.structs.AvlTree.Node
129 | goog.structs.CircularBuffer
130 | goog.structs.Heap
131 | goog.structs.InversionMap
132 | goog.structs.LinkedMap
133 | goog.structs.Map
134 | goog.structs.Node
135 | goog.structs.Pool
136 | goog.structs.PriorityPool
137 | goog.structs.PriorityQueue
138 | goog.structs.QuadTree
139 | goog.structs.QuadTree.Node
140 | goog.structs.QuadTree.Point
141 | goog.structs.Queue
142 | goog.structs.Set
143 | goog.structs.SimplePool
144 | goog.structs.StringSet
145 | goog.structs.TreeNode
146 | goog.structs.Trie
147 | goog.text.LoremIpsum))
148 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/random/longs.cljs:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"
11 | :doc "Internal namespace, wrapping some goog.math.Long functionality."}
12 | clojure.test.check.random.longs
13 | (:refer-clojure :exclude [+ * bit-xor bit-or bit-count
14 | unsigned-bit-shift-right])
15 | (:require [clojure.test.check.random.longs.bit-count-impl :as bit-count]
16 | [goog.math.Long :as long]
17 | [clojure.core :as core]))
18 |
19 | (defn unsigned-bit-shift-right
20 | [x n]
21 | (.shiftRightUnsigned x n))
22 |
23 | (defn +
24 | [x y]
25 | (.add x y))
26 |
27 | (defn *
28 | [x y]
29 | (let [a48 (bit-shift-right-zero-fill (.-high_ x) 16)
30 | a32 (bit-and (.-high_ x) 0xFFFF)
31 | a16 (bit-shift-right-zero-fill (.-low_ x) 16)
32 | a00 (bit-and (.-low_ x) 0xFFFF)
33 |
34 | b48 (bit-shift-right-zero-fill (.-high_ y) 16)
35 | b32 (bit-and (.-high_ y) 0xFFFF)
36 | b16 (bit-shift-right-zero-fill (.-low_ y) 16)
37 | b00 (bit-and (.-low_ y) 0xFFFF)
38 |
39 | arr (array 0 0 0 0)] ;[c00 c16 c32 c48]
40 | (aset arr 0 (core/* a00 b00)) ;c00 += a00 * b00;
41 | (aset arr 1 (bit-shift-right-zero-fill (aget arr 0) 16)) ;c16 += c00 >>> 16
42 | (aset arr 0 (bit-and (aget arr 0) 0xFFFF)) ;c00 &= 0xFFFF;
43 | (aset arr 1 (core/+ (aget arr 1) (core/* a16 b00))) ;c16 += a16 * b00;
44 | (aset arr 2 (bit-shift-right-zero-fill (aget arr 1) 16)) ;c32 += c16 >>> 16;
45 | (aset arr 1 (bit-and (aget arr 1) 0xFFFF)) ;c16 &= 0xFFFF;
46 | (aset arr 1 (core/+ (aget arr 1) (core/* a00 b16))) ;c16 += a00 * b16;
47 | (aset arr 2 (core/+ (aget arr 2) (bit-shift-right-zero-fill (aget arr 1) 16))) ;c32 += c16 >>> 16;
48 | (aset arr 1 (bit-and (aget arr 1) 0xFFFF)) ;c16 &= 0xFFFF;
49 | (aset arr 2 (core/+ (aget arr 2) (core/* a32 b00))) ;c32 += a32 * b00;
50 | (aset arr 3 (bit-shift-right-zero-fill (aget arr 2) 16)) ;c48 += c32 >>> 16;
51 | (aset arr 2 (bit-and (aget arr 2) 0xFFFF)) ;c32 &= 0xFFFF;
52 | (aset arr 2 (core/+ (aget arr 2) (core/* a16 b16))) ;c32 += a16 * b16;
53 | (aset arr 3 (core/+ (aget arr 3) (bit-shift-right-zero-fill (aget arr 2) 16))) ;c48 += c32 >>> 16;
54 | (aset arr 2 (bit-and (aget arr 2) 0xFFFF)) ;c32 &= 0xFFFF;
55 | (aset arr 2 (core/+ (aget arr 2) (core/* a00 b32))) ;c32 += a00 * b32;
56 | (aset arr 3 (core/+ (aget arr 3) (bit-shift-right-zero-fill (aget arr 2) 16))) ;c48 += c32 >>> 16;
57 | (aset arr 2 (bit-and (aget arr 2) 0xFFFF)) ;c32 &= 0xFFFF;
58 |
59 | (aset arr 3 (core/+ (aget arr 3) (core/* a48 b00) (core/* a32 b16) (core/* a16 b32) (core/* a00 b48)))
60 | ;c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
61 | (aset arr 3 (bit-and (aget arr 3) 0xFFFF)) ;c48 &= 0xFFFF;
62 |
63 | ;(c16 << 16) | c00, (c48 << 16) | c32
64 | (long/fromBits (core/bit-or (bit-shift-left (aget arr 1) 16) (aget arr 0))
65 | (core/bit-or (bit-shift-left (aget arr 3) 16) (aget arr 2)))))
66 |
67 | (defn bit-xor
68 | [x y]
69 | (.xor x y))
70 |
71 | (defn bit-or
72 | [x y]
73 | (.or x y))
74 |
75 | (defn from-string
76 | [s radix]
77 | (long/fromString s radix))
78 |
79 | (defn from-number
80 | [x]
81 | (long/fromNumber x))
82 |
83 | (defn ->long
84 | "Coerces to long, or returns nil if not possible."
85 | [x]
86 | (cond (number? x)
87 | (long/fromNumber x)
88 |
89 | (instance? goog.math.Long x)
90 | x))
91 |
92 | (def ONE (long/getOne))
93 |
94 | (def bit-count bit-count/bit-count)
95 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject org.clojure/test.check "0.11.0-SNAPSHOT"
2 | :description "A QuickCheck inspired property-based testing library."
3 | :url "https://github.com/clojure/test.check"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies []
7 | :source-paths ["src/main/clojure"]
8 | :test-paths ["src/test/clojure"]
9 | :jvm-opts ^:replace ["-Xmx512m" "-server"]
10 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.10.1"]
11 | [org.clojure/clojurescript "1.10.520"]]}
12 | :self-host {:dependencies [[org.clojure/clojure "1.10.1"]
13 | [org.clojure/clojurescript "1.10.520"]]
14 | :main clojure.main
15 | :global-vars {*warn-on-reflection* false}}}
16 | :global-vars {*warn-on-reflection* true}
17 | :plugins [[lein-codox "0.10.8"]
18 | [lein-cljsbuild "1.1.5"]]
19 | ;; To generate codox files (which are hosted on the gh-pages branch)
20 | ;; for a release:
21 | ;;
22 | ;; 1) if the current content should be preserved as documentation
23 | ;; for an old release, first copy it to a subdirectory and
24 | ;; add a link in doc/api-docs-for-older-versions.md (on the
25 | ;; master branch, I guess, though that won't help when building
26 | ;; from an older tag, so you may also manually add that change
27 | ;; to the api-docs-for-older-versions.html file on the gh-pages
28 | ;; branch as well, which is no fun)
29 | ;; 2) checkout the tagged git commit
30 | ;; 3) tweak the project.clj version to match, since
31 | ;; jenkins only updates the pom.xml
32 | ;; 4) tweak the :source-uri entry below, replacing "master"
33 | ;; with the appropriate tag
34 | ;; 5) run `lein codox`
35 | ;; 6) copy target/doc into the gh-pages branch source tree, commit
36 | ;; and push; e.g., if you have the gh-pages branch open on a
37 | ;; worktree at gitignored/gh-pages, then this might work:
38 | ;; D1=target/doc
39 | ;; D2=gitignored/gh-pages
40 | ;; for file in $(ls $D1); do
41 | ;; if [[ -e $D2/$file ]]; then
42 | ;; rm -rf $D2/$file
43 | ;; fi
44 | ;; cp -r {$D1,$D2}/$file
45 | ;; done
46 | ;; 7) check the result at http://clojure.github.io/test.check/ ;
47 | ;; the source links should go to the correct tag on github
48 | :codox {:namespaces [clojure.test.check
49 | clojure.test.check.clojure-test
50 | clojure.test.check.generators
51 | clojure.test.check.properties
52 | clojure.test.check.results]
53 | :source-uri {#".*" "https://github.com/clojure/test.check/blob/master/{filepath}#L{line}"}
54 | :doc-files ["doc/api-docs-for-older-versions.md"
55 | "doc/cheatsheet.md"
56 | "doc/generator-examples.md"
57 | "doc/growth-and-shrinking.md"
58 | "doc/intro.md"]}
59 | :cljsbuild
60 | {:builds
61 | [{:id "node-dev"
62 | :source-paths ["src/main/clojure" "src/test/clojure"
63 | "src/target/cljs/node"]
64 | :notify-command ["node" "test-runners/run.js"]
65 | :compiler {:optimizations :none
66 | :main clojure.test.check.test.runner
67 | :static-fns true
68 | :target :nodejs
69 | :output-to "target/cljs/node_dev/tests.js"
70 | :output-dir "target/cljs/node_dev/out"
71 | :source-map true}}
72 | {:id "browser-dev"
73 | :source-paths ["src/main/clojure" "src/test/clojure"
74 | "src/target/cljs/browser"]
75 | :compiler {:optimizations :none
76 | :static-fns true
77 | :output-to "target/cljs/browser_dev/tests.js"
78 | :output-dir "target/cljs/browser_dev/out"
79 | :source-map true}}
80 | {:id "node-adv"
81 | :source-paths ["src/main/clojure" "src/test/clojure"
82 | "src/target/cljs/node"]
83 | :notify-command ["node" "target/cljs/node_adv/tests.js"]
84 | :compiler {:optimizations :advanced
85 | :main clojure.test.check.test.runner
86 | :target :nodejs
87 | :pretty-print false
88 | :output-to "target/cljs/node_adv/tests.js"
89 | :output-dir "target/cljs/node_adv/out"}}
90 | {:id "browser-adv"
91 | :source-paths ["src/main/clojure" "src/test/clojure"
92 | "src/target/cljs/browser"]
93 | :compiler {:optimizations :advanced
94 | :pretty-print false
95 | :output-to "target/cljs/browser_adv/tests.js"
96 | :output-dir "target/cljs/browser_adv/out"}}]})
97 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/random_test.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.random-test
2 | "Tests of the custom RNG. This is a little weird since the subject
3 | of the tests (the random number generator) is also the primary
4 | internal driver of the tests, but hopefully it will still be
5 | meaningful."
6 | (:require [clojure.test.check.clojure-test :refer [defspec]]
7 | [clojure.test.check.generators :as gen]
8 | [clojure.test.check.properties :as prop]
9 | [clojure.test.check.random :as random]))
10 |
11 | (def gen-split-steps
12 | (gen/list (gen/elements [:left :right])))
13 |
14 | (defn apply-split-steps
15 | [rng steps]
16 | (reduce (fn [rng step]
17 | (let [[rng1 rng2] (random/split rng)]
18 | (case step :left rng1 :right rng2)))
19 | rng
20 | steps))
21 |
22 | (def gen-seed
23 | (let [gen-int (gen/choose 0 Integer/MAX_VALUE)]
24 | (gen/fmap (fn [[s1 s2]]
25 | (bit-or s1 (bit-shift-left s2 32)))
26 | (gen/tuple gen-int gen-int))))
27 |
28 | (defspec determinism-spec
29 | (prop/for-all [seed gen-seed
30 | steps gen-split-steps]
31 | (let [r1 (random/make-random seed)
32 | r2 (random/make-random seed)]
33 | (= (-> r1 (apply-split-steps steps) (random/rand-long))
34 | (-> r2 (apply-split-steps steps) (random/rand-long))))))
35 |
36 | (defn get-256-longs
37 | [rng]
38 | (map random/rand-long
39 | (nth (iterate #(mapcat random/split %) [rng]) 8)))
40 |
41 | ;; this spec is only statistically certain to pass, not logically
42 | ;; certain. The probability of a false failure (1/2^16384 or so) is
43 | ;; low enough to ignore.
44 | (defspec different-states-spec
45 | (prop/for-all [seed gen-seed
46 | pre-steps gen-split-steps
47 | post-steps-1 gen-split-steps
48 | post-steps-2 gen-split-steps]
49 | (let [r (random/make-random seed)
50 | r' (apply-split-steps r pre-steps)
51 | [r1 r2] (random/split r')
52 | r1' (apply-split-steps r1 post-steps-1)
53 | r2' (apply-split-steps r2 post-steps-2)]
54 | ;; r1' and r2' should not somehow be in the same state
55 | (not= (get-256-longs r1')
56 | (get-256-longs r2')))))
57 |
58 | ;; Tests of the particular JavaUtilSplittableRandom impl, by
59 | ;; comparing with java.util.SplittableRandom on Java 8
60 | (when (try (Class/forName "java.util.SplittableRandom")
61 | (catch ClassNotFoundException e))
62 | (eval
63 | '(defspec java-util-splittable-random-spec
64 | (prop/for-all [seed gen-seed
65 | steps gen-split-steps]
66 | (let [immutable-rng (apply-split-steps
67 | (random/make-java-util-splittable-random seed)
68 | steps)
69 | mutable-rng
70 | ^java.util.SplittableRandom
71 | (reduce (fn [^java.util.SplittableRandom rng step]
72 | (let [rng2 (.split rng)]
73 | (case step :left rng :right rng2)))
74 | (java.util.SplittableRandom. seed)
75 | steps)]
76 | (= (random/rand-long immutable-rng)
77 | (.nextLong mutable-rng))))))
78 |
79 | ;; same test but for rand-double
80 | (eval
81 | '(defspec java-util-splittable-random-spec-double
82 | (prop/for-all [seed gen-seed
83 | steps gen-split-steps]
84 | (let [immutable-rng (apply-split-steps
85 | (random/make-java-util-splittable-random seed)
86 | steps)
87 | mutable-rng
88 | ^java.util.SplittableRandom
89 | (reduce (fn [^java.util.SplittableRandom rng step]
90 | (let [rng2 (.split rng)]
91 | (case step :left rng :right rng2)))
92 | (java.util.SplittableRandom. seed)
93 | steps)]
94 | (= (random/rand-double immutable-rng)
95 | (.nextDouble mutable-rng)))))))
96 |
97 | (defspec split-n-spec 40
98 | (prop/for-all [seed gen-seed
99 | n gen/nat]
100 | (let [rng (random/make-random seed)]
101 | ;; checking that split-n returns the same generators that we
102 | ;; would get by doing a particular series of splits manually
103 | (= (map random/rand-long (random/split-n rng n))
104 | (map random/rand-long
105 | (if (zero? n)
106 | []
107 | (loop [v [], rng rng]
108 | (if (= (dec n) (count v))
109 | (conj v rng)
110 | (let [[rng1 rng2] (random/split rng)]
111 | (recur (conj v rng2) rng1))))))))))
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # test.check
2 |
3 | _test.check_ is a Clojure property-based testing tool inspired by QuickCheck.
4 | The core idea of _test.check_ is that instead of enumerating expected input
5 | and output for unit tests, you write properties about your function that should
6 | hold true for all inputs. This lets you write concise, powerful tests.
7 |
8 | * Release Info
9 | * [Latest Releases](#latest-releases)
10 | * [Changelog](CHANGELOG.markdown)
11 | * [Introduction](doc/intro.md)
12 | * Basic Docs
13 | * [API Docs](https://clojure.github.io/test.check/)
14 | * [Cheatsheet](doc/cheatsheet.md)
15 | * [Generator Examples](doc/generator-examples.md)
16 | * [Migrating from SimpleCheck](doc/migrating-from-simple-check.md)
17 | * Useful Libraries
18 | * [test.chuck](https://github.com/gfredericks/test.chuck)
19 | * [collection-check](https://github.com/ztellman/collection-check)
20 | * [herbert](https://github.com/miner/herbert)
21 | * Examples (some of these may refer to simple-check):
22 | * [core.matrix](https://github.com/mikera/core.matrix/blob/c45ee6b551a50a509e668f46a1ae52ade2c52a82/src/test/clojure/clojure/core/matrix/properties.clj)
23 | * [byte-streams](https://github.com/ztellman/byte-streams/blob/b5f50a20c6237ae4e45046f72367ad658090c591/test/byte_streams_simple_check.clj)
24 | * [byte-transforms](https://github.com/ztellman/byte-transforms/blob/c5b9613eebac722447593530531b9aa7976a0592/test/byte_transforms_simple_check.clj)
25 | * [collection-check](https://github.com/ztellman/collection-check)
26 | * Blog posts and videos (some of these may refer to simple-check):
27 | * [Powerful Testing with test.check - Clojure/West 2014](https://www.youtube.com/watch?v=JMhNINPo__g) -- [Slides](https://speakerdeck.com/reiddraper/powerful-testing-with-test-dot-check)
28 | * [Purely Random - Clojure/west 2015](https://www.youtube.com/watch?v=u0t-6lUvXHo)
29 | * [Building test.check Generators - Clojure/Conj 2017](https://www.youtube.com/watch?v=F4VZPxLZUdA) - [Slides](https://gfredericks.com/speaking/2017-10-12-generators.pdf)
30 | * [Check your work - 8th Light](http://blog.8thlight.com/connor-mendenhall/2013/10/31/check-your-work.html)
31 | * [Writing simple-check - Reid Draper](https://reiddraper.com/writing-simple-check/)
32 | * [Generative testing in Clojure - Youtube](https://www.youtube.com/watch?v=u0TkAw8QqrQ)
33 | * Advanced Docs
34 | * [Growth and Shrinking](doc/growth-and-shrinking.md)
35 | * Other Implementations
36 | * [QC for Haskell](https://hackage.haskell.org/package/QuickCheck)
37 | * [The significantly more advanced QC for Erlang](http://www.quviq.com/index.html)
38 | * Papers
39 | * [QuickCheck: A Lightweight Tool for Random Testing of Haskell
40 | Programs](https://www.eecs.northwestern.edu/~robby/courses/395-495-2009-fall/quick.pdf)
41 | * Developer Docs
42 | * [Contributing](CONTRIBUTING.md)
43 | * [Developer Information](doc/development.md)
44 | * [Miscellaneous](#miscellaneous)
45 |
46 | ## Latest Releases
47 |
48 | * Release notes for each version are available in [`CHANGELOG.markdown`](CHANGELOG.markdown)
49 | * Remember that prior to version 0.5.7, _test.check_ was called _simple-check_
50 | * As of version `0.9.0`, test.check requires Clojure >= `1.7.0`
51 | * Please note a [breaking change for ClojureScript](https://github.com/clojure/test.check/blob/master/CHANGELOG.markdown#080)
52 | in the `0.8.*` releases.
53 |
54 | ### Latest Version
55 |
56 | #### [CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information:
57 |
58 | ```clojure
59 | org.clojure/test.check {:mvn/version "1.1.2"}
60 | ```
61 |
62 | #### Leiningen
63 |
64 | ```clojure
65 | [org.clojure/test.check "1.1.2"]
66 | ```
67 |
68 | #### Maven
69 |
70 | ```xml
71 |
72 | org.clojure
73 | test.check
74 | 1.1.2
75 |
76 | ```
77 |
78 | ### Stable Version
79 |
80 | #### Leiningen
81 |
82 | ```clojure
83 | [org.clojure/test.check "1.1.2"]
84 | ```
85 |
86 | #### Maven
87 |
88 | ```xml
89 |
90 | org.clojure
91 | test.check
92 | 1.1.2
93 |
94 | ```
95 |
96 | If you'd like to try a SNAPSHOT version, [add the sonatype repository to your
97 | project](https://clojure.org/community/downloads#_using_clojure_and_contrib_snapshot_releases).
98 |
99 | ## Miscellaneous
100 |
101 | ### YourKit
102 |
103 | 
104 |
105 | YourKit is kindly supporting test.check and other open source projects with its
106 | full-featured Java Profiler. YourKit, LLC is the creator of innovative and
107 | intelligent tools for profiling Java and .NET applications. Take a look at
108 | YourKit's leading software products:
109 |
110 | * YourKit Java Profiler and
111 | * YourKit .NET Profiler
112 |
113 | ### License
114 |
115 | Copyright © Rich Hickey, Reid Draper and contributors
116 |
117 | Distributed under the Eclipse Public License, the same as Clojure.
118 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/random.cljs:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"
11 | :doc "Purely functional and splittable pseudo-random number generators."}
12 | clojure.test.check.random
13 | (:refer-clojure :exclude [+ * bit-xor bit-or bit-count
14 | unsigned-bit-shift-right])
15 | (:require [clojure.test.check.random.doubles :as doubles]
16 | [clojure.test.check.random.longs :as longs
17 | :refer [+ * bit-xor bit-or bit-count unsigned-bit-shift-right]]))
18 |
19 | (defprotocol IRandom
20 | (rand-long [rng]
21 | "Returns a random goog.math.Long based on the given immutable RNG.
22 |
23 | Note: to maintain independence you should not call more than one
24 | function in the IRandom protocol with the same argument")
25 | (rand-double [rng]
26 | "Returns a random double between 0.0 (inclusive) and 1.0 (exclusive)
27 | based on the given immutable RNG.
28 |
29 | Note: to maintain independence you should not call more than one
30 | function in the IRandom protocol with the same argument")
31 | (split [rng]
32 | "Returns two new RNGs [rng1 rng2], which should generate
33 | sufficiently independent random data.
34 |
35 | Note: to maintain independence you should not call more than one
36 | function in the IRandom protocol with the same argument")
37 | (split-n [rng n]
38 | "Returns a collection of `n` RNGs, which should generate
39 | sufficiently independent random data.
40 |
41 | Note: to maintain independence you should not call more than one
42 | function in the IRandom protocol with the same argument"))
43 |
44 | ;;
45 | ;; This is a port of the clojure-jvm port of
46 | ;; java.util.SplittableRandom, and should give identical results.
47 | ;;
48 |
49 | (defn ^:private hex-long
50 | "Helper for defining constants."
51 | [s]
52 | (longs/from-string s 16))
53 |
54 | (defn ^:private bxoubsr
55 | "Performs (-> x (unsigned-bit-shift-right n) (bit-xor x))."
56 | [x n]
57 | (-> x (unsigned-bit-shift-right n) (bit-xor x)))
58 |
59 | (def ^:private mix-64-const-1 (hex-long "bf58476d1ce4e5b9"))
60 | (def ^:private mix-64-const-2 (hex-long "94d049bb133111eb"))
61 |
62 | (defn ^:private mix-64
63 | [n]
64 | (-> n
65 | (bxoubsr 30)
66 | (* mix-64-const-1)
67 | (bxoubsr 27)
68 | (* mix-64-const-2)
69 | (bxoubsr 31)))
70 |
71 | (def ^:private mix-gamma-const-1 (hex-long "ff51afd7ed558ccd"))
72 | (def ^:private mix-gamma-const-2 (hex-long "c4ceb9fe1a85ec53"))
73 | (def ^:private mix-gamma-const-3 (hex-long "aaaaaaaaaaaaaaaa"))
74 |
75 | (defn ^:private mix-gamma
76 | [n]
77 | (-> n
78 | (bxoubsr 33)
79 | (* mix-gamma-const-1)
80 | (bxoubsr 33)
81 | (* mix-gamma-const-2)
82 | (bxoubsr 33)
83 | (bit-or longs/ONE)
84 | (as-> z
85 | (cond-> z
86 | (> 24 (-> z
87 | (bxoubsr 1)
88 | (bit-count)))
89 | (bit-xor mix-gamma-const-3)))))
90 |
91 | (deftype JavaUtilSplittableRandom [gamma state]
92 | IRandom
93 | (rand-long [_]
94 | (-> state (+ gamma) (mix-64)))
95 | (rand-double [this]
96 | (-> this rand-long doubles/rand-long->rand-double))
97 | (split [this]
98 | (let [state' (+ gamma state)
99 | state'' (+ gamma state')
100 | gamma' (mix-gamma state'')]
101 | [(JavaUtilSplittableRandom. gamma state'')
102 | (JavaUtilSplittableRandom. gamma' (mix-64 state'))]))
103 | (split-n [this n]
104 | (case n
105 | 0 []
106 | 1 [this]
107 | (let [n-dec (dec n)]
108 | (loop [state state
109 | ret (transient [])]
110 | (if (= n-dec (count ret))
111 | (-> ret
112 | (conj! (JavaUtilSplittableRandom. gamma state))
113 | (persistent!))
114 | (let [state' (+ gamma state)
115 | state'' (+ gamma state')
116 | gamma' (mix-gamma state'')
117 | new-rng (JavaUtilSplittableRandom. gamma' (mix-64 state'))]
118 | (recur state'' (conj! ret new-rng)))))))))
119 |
120 | (def ^:private golden-gamma
121 | (hex-long "9e3779b97f4a7c15"))
122 |
123 | (defn make-java-util-splittable-random
124 | [seed]
125 | (JavaUtilSplittableRandom. golden-gamma
126 | (or (longs/->long seed)
127 | (throw (ex-info "Bad random seed!"
128 | {:seed seed})))))
129 |
130 | (def ^:private next-rng
131 | (let [a (atom {:state
132 | (make-java-util-splittable-random (.valueOf (js/Date.)))})]
133 | (fn []
134 | (:returned
135 | (swap! a (fn [{:keys [state]}]
136 | (let [[r1 r2] (split state)]
137 | {:state r1 :returned r2})))))))
138 |
139 | (defn make-random
140 | "Given an optional integer (or goog.math.Long) seed, returns an
141 | implementation of the IRandom protocol."
142 | ([] (next-rng))
143 | ([seed]
144 | (make-java-util-splittable-random seed)))
145 |
--------------------------------------------------------------------------------
/doc/cheatsheet.md:
--------------------------------------------------------------------------------
1 | # test.check cheatsheet
2 |
3 | So far this only documents functions in the generators namespace.
4 |
5 | ## Dev utilities, misc
6 |
7 | - `(gen/sample g)` — returns 10 smallish samples from `g`
8 | - `(gen/sample g n)` — generates `n` samples from `g`
9 | - `(gen/generate g)` — generates a single moderately sized value from `g`
10 | - `(gen/generate g size)` — generates a value from `g` with the given
11 | `size` (normally sizes range from 0 to 200)
12 | - `(gen/generator? g)` — checks if `g` is a generator
13 |
14 | ## Simple Generators
15 |
16 | - `(gen/return x)` — A constant generator that always generates `x`
17 | - `gen/boolean` — generates booleans (`true` and `false`)
18 | - `gen/uuid` — generates uniformly random UUIDs, does not shrink
19 | - `(gen/elements coll)` — generates elements from `coll` (which must be non-empty)
20 | - `(gen/shuffle coll)` — generates vectors with the elements of `coll`
21 | in random orders
22 | - `gen/any` — generates any clojure value
23 | - `gen/any-printable` — generates any printable clojure value
24 | - `gen/any-equatable` — generates any clojure value that can be equal to another
25 | - `gen/any-printable-equatable` — generates any printable clojure value that can be equal to another
26 | - `gen/simple-type` — like `gen/any` but does not generate collections
27 | - `gen/simple-type-printable` — like `gen/any-printable` but does not
28 | - `gen/simple-type-equatable` — like `gen/any-equatable` but does not generate collections
29 | - `gen/simple-type-printable-equatable` — like `gen/any-printable-equatable` but does not
30 | generate collections
31 |
32 | ### Numbers
33 |
34 | - `gen/nat` — generates small non-negative integers (useful for generating sizes of things)
35 | - `gen/small-integer` — generates small integers, like `gen/nat` but also negative
36 | - `gen/large-integer` — generates a large range of integers
37 | - variant with options: `(gen/large-integer* {:min x, :max y})`
38 | - `gen/size-bounded-bigint` — generates bigints, up to `2^(6*size)`
39 | - `gen/double` — generates a large range of doubles (w/ infinities & `NaN`)
40 | - variant with options: `(gen/double* {:min x, :max y, :infinite? true, :NaN? true})`
41 | - `gen/ratio` — generates ratios (sometimes integers) using gen/small-integer
42 | - `gen/big-ratio` — generates ratios (sometimes integers) using gen/size-bounded-bigint
43 | - `gen/byte` — generates a `Byte`
44 | - `gen/choose` — generates *uniformly distributed* integers between two (inclusive) values
45 |
46 | ### Characters & Strings & Things
47 |
48 | - `gen/char` — generates characters
49 | - `gen/char-ascii` — generates printable ASCII characters
50 | - `gen/char-alphanumeric` — generates alphanumeric ASCII characters
51 | - `gen/char-alpha` — generates alphabetic ASCII characters
52 | - `gen/string` — generates a string
53 | - `gen/string-ascii` — generates a string using `gen/char-ascii`
54 | - `gen/string-alphanumeric` — generates a string using `gen/char-alphanumeric`
55 | - `gen/keyword` — generates keywords
56 | - `gen/keyword-ns` — generates namespaced keywords
57 | - `gen/symbol` — generates symbols
58 | - `gen/symbol-ns` — generates namespaced symbols
59 |
60 | ## Heterogeneous Collections
61 |
62 | - `(gen/tuple g1 g2 ...)` — generates vectors `[x1 x2 ...]` where `x1`
63 | is drawn from `g1`, `x2` from `g2`, etc.
64 | - `(gen/hash-map k1 g1, k2 g2, ...)` — generates maps `{k1 v1, k2 v2, ...}`
65 | where `v1` is drawn from `g1`, `v2` from `g2`, etc.
66 |
67 |
68 | ## Homogeneous Collections
69 |
70 | - `(gen/vector g)` — generates vectors of elements from `g`
71 | - Variants:
72 | - `(gen/vector g num-elements)`
73 | - `(gen/vector g min-elements max-elements)`
74 | - `(gen/list g)` — generates lists of elements from `g`
75 | - `(gen/set g)` — generates sets of elements from `g`
76 | - Variants:
77 | - `(gen/set g {:num-elements x, :max-tries 20})`
78 | - `(gen/set g {:min-elements x, :max-elements y, :max-tries 20})`
79 | - `(gen/map key-gen val-gen)` — generates a map with keys from `key-gen`
80 | and vals from `val-gen`
81 | - same opts as `gen/set`u
82 | - `(gen/sorted-set g)` — just like `gen/set`, but generates sorted-sets
83 | - `(gen/vector-distinct g)` — same signature as `gen/set`, but generates
84 | vectors of distinct elements
85 | - `(gen/list-distinct g)` — same signature as `gen/set`, but generates
86 | lists of distinct elements
87 | - `(gen/vector-distinct-by key-fn g)` — generates vectors of elements
88 | where `(apply distinct? (map key-fn the-vector))`
89 | - same opts as `gen/set`
90 | - `(gen/list-distinct-by key-fn g)` — generates list of elements
91 | where `(apply distinct? (map key-fn the-list))`
92 | - same opts as `gen/set`
93 | - `gen/bytes` — generates a byte array
94 |
95 | ## Combinators
96 |
97 | - `(gen/let [x g] y)` — **macro**, like `clojure.core/let`, where
98 | the right-hand bindings are generators and the left-hand are
99 | generated values; creates a generator
100 | - same functionality as `gen/fmap` and `gen/bind`
101 | - `(gen/fmap f g)` — creates a generator that generates `(f x)` for
102 | `x` generated from `g`
103 | - `(gen/bind g f)` — similar to `gen/fmap`, but where `(f x)` is itself
104 | a generator and `(gen/bind g f)` generates values from `(f x)`
105 | - `(gen/such-that pred g)` — returns a new generator that generates
106 | only elements from `g` that match `pred`
107 | - Variants: `(gen/such-that pred g max-tries)`
108 | - `(gen/one-of [g1 g2 ...])` — generates elements from the given
109 | generators, picking generators at random
110 | - `(gen/frequency [[2 g1] [7 g2] ...])` — generates elements from the
111 | given generators, using the given weights to determine the
112 | probability of picking any particular generator
113 | - `(gen/not-empty g)` — given a generator that generates collections,
114 | returns a modified generator that never generates empty collections
115 | - `(gen/recursive-gen container-gen scalar-gen)` — generates a tree of
116 | values, using `container-gen` (which is a function like `gen/list`
117 | which takes and returns a generator) and `scalar-gen` (a generator
118 | for the leaf values)
119 |
120 | ## Sizing & shrinking control
121 |
122 | - `(gen/resize n g)` — creates a variant of `g` whose `size` parameter
123 | is always `n`
124 | - `(gen/scale f g)` — creates a variant of `g` whose `size` parameter
125 | is `(f size)`
126 | - `(gen/no-shrink g)` — creates a variant of `g` that does not shrink
127 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/random.clj:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns ^{:author "Gary Fredericks"
11 | :doc "Purely functional and splittable pseudo-random number generators."}
12 | clojure.test.check.random
13 | (:refer-clojure :exclude [unsigned-bit-shift-right]))
14 |
15 | (defprotocol IRandom
16 | (rand-long [rng]
17 | "Returns a random long based on the given immutable RNG.
18 |
19 | Note: to maintain independence you should not call more than one
20 | function in the IRandom protocol with the same argument")
21 | (rand-double [rng]
22 | "Returns a random double between 0.0 (inclusive) and 1.0 (exclusive)
23 | based on the given immutable RNG.
24 |
25 | Note: to maintain independence you should not call more than one
26 | function in the IRandom protocol with the same argument")
27 | (split [rng]
28 | "Returns two new RNGs [rng1 rng2], which should generate
29 | sufficiently independent random data.
30 |
31 | Note: to maintain independence you should not call more than one
32 | function in the IRandom protocol with the same argument")
33 | (split-n [rng n]
34 | "Returns a collection of `n` RNGs, which should generate
35 | sufficiently independent random data.
36 |
37 | Note: to maintain independence you should not call more than one
38 | function in the IRandom protocol with the same argument"))
39 |
40 | ;; Immutable version of Java 8's java.util.SplittableRandom
41 | ;;
42 | ;; Meant to give the same results as similar uses of
43 | ;; java.util.SplittableRandom, in particular:
44 | ;;
45 | ;; (= (-> (make-java-util-splittable-random 42)
46 | ;; (rand-long))
47 | ;; (.nextLong (SplittableRandom. 42)))
48 | ;;
49 | ;; (= (-> (make-java-util-splittable-random 42)
50 | ;; (split)
51 | ;; (first)
52 | ;; (rand-long))
53 | ;; (.nextLong (doto (SplittableRandom. 42)
54 | ;; (.split))))
55 | ;;
56 | ;; (= (-> (make-java-util-splittable-random 42)
57 | ;; (split)
58 | ;; (second)
59 | ;; (rand-long))
60 | ;; (.nextLong (.split (SplittableRandom. 42))))
61 | ;;
62 | ;; Also see the spec that checks this equivalency.
63 |
64 |
65 | ;; backwards compatibility for clojure 1.5
66 | (def ^:private old-clojure?
67 | (not (resolve 'clojure.core/unsigned-bit-shift-right)))
68 | (defmacro ^:private unsigned-bit-shift-right
69 | [x n]
70 | {:pre [(<= 1 n 63)]}
71 | (if old-clojure?
72 | (let [mask (-> Long/MIN_VALUE
73 | (bit-shift-right (dec n))
74 | (bit-not))]
75 | `(-> ~x
76 | (bit-shift-right ~n)
77 | (bit-and ~mask)))
78 | `(clojure.core/unsigned-bit-shift-right ~x ~n)))
79 |
80 | (defmacro ^:private longify
81 | "Macro for writing arbitrary longs in the java 0x syntax. E.g.
82 | 0x9e3779b97f4a7c15 (which is read as a bigint because it's out
83 | of range) becomes -7046029254386353131."
84 | [num]
85 | (if (> num Long/MAX_VALUE)
86 | (-> num
87 | (- 18446744073709551616N)
88 | (long)
89 | (bit-or -9223372036854775808))
90 | num))
91 |
92 | (set! *unchecked-math* :warn-on-boxed)
93 |
94 | (defmacro ^:private bxoubsr
95 | "Performs (-> x (unsigned-bit-shift-right n) (bit-xor x))."
96 | [x n]
97 | (vary-meta
98 | `(let [x# ~x]
99 | (-> x# (unsigned-bit-shift-right ~n) (bit-xor x#)))
100 | assoc :tag 'long))
101 |
102 | (defmacro ^:private mix-64
103 | [n]
104 | `(-> ~n
105 | (bxoubsr 30)
106 | (* (longify 0xbf58476d1ce4e5b9))
107 | (bxoubsr 27)
108 | (* (longify 0x94d049bb133111eb))
109 | (bxoubsr 31)))
110 |
111 | (defmacro ^:private mix-gamma
112 | [n]
113 | `(-> ~n
114 | (bxoubsr 33)
115 | (* (longify 0xff51afd7ed558ccd))
116 | (bxoubsr 33)
117 | (* (longify 0xc4ceb9fe1a85ec53))
118 | (bxoubsr 33)
119 | (bit-or 1)
120 | (as-> z#
121 | (cond-> z#
122 | (> 24 (-> z#
123 | (bxoubsr 1)
124 | (Long/bitCount)))
125 | (bit-xor (longify 0xaaaaaaaaaaaaaaaa))))))
126 |
127 | (def ^:private ^:const double-unit (/ 1.0 (double (bit-set 0 53))))
128 | ;; Java: 0x1.0p-53 or (1.0 / (1L << 53))
129 |
130 | (deftype JavaUtilSplittableRandom [^long gamma ^long state]
131 | IRandom
132 | (rand-long [_]
133 | (-> state (+ gamma) (mix-64)))
134 | (rand-double [this]
135 | (* double-unit (unsigned-bit-shift-right (long (rand-long this)) 11)))
136 | (split [this]
137 | (let [state' (+ gamma state)
138 | state'' (+ gamma state')
139 | gamma' (mix-gamma state'')]
140 | [(JavaUtilSplittableRandom. gamma state'')
141 | (JavaUtilSplittableRandom. gamma' (mix-64 state'))]))
142 | (split-n [this n]
143 | ;; immitates a particular series of 2-way splits, but avoids the
144 | ;; intermediate allocation. See the `split-n-spec` for a test of
145 | ;; the equivalence to 2-way splits.
146 | (let [n (long n)]
147 | (case n
148 | 0 []
149 | 1 [this]
150 | (let [n-dec (dec n)]
151 | (loop [state state
152 | ret (transient [])]
153 | (if (= n-dec (count ret))
154 | (-> ret
155 | (conj! (JavaUtilSplittableRandom. gamma state))
156 | (persistent!))
157 | (let [state' (+ gamma state)
158 | state'' (+ gamma state')
159 | gamma' (mix-gamma state'')
160 | new-rng (JavaUtilSplittableRandom. gamma' (mix-64 state'))]
161 | (recur state'' (conj! ret new-rng))))))))))
162 |
163 | (def ^:private golden-gamma
164 | (longify 0x9e3779b97f4a7c15))
165 |
166 | (defn make-java-util-splittable-random
167 | [^long seed]
168 | (JavaUtilSplittableRandom. golden-gamma seed))
169 |
170 | ;; some global state to make sure that seedless calls to make-random
171 | ;; return independent results
172 | (def ^:private next-rng
173 | "Returns a random-number generator. Successive calls should return
174 | independent results."
175 | (let [a (atom (make-java-util-splittable-random (System/currentTimeMillis)))
176 |
177 | thread-local
178 | (proxy [ThreadLocal] []
179 | (initialValue []
180 | (first (split (swap! a #(second (split %)))))))]
181 | (fn []
182 | (let [rng (.get thread-local)
183 | [rng1 rng2] (split rng)]
184 | (.set thread-local rng2)
185 | rng1))))
186 |
187 | (defn make-random
188 | "Given an optional Long seed, returns an object that satisfies the
189 | IRandom protocol."
190 | ([] (next-rng))
191 | ([seed] (make-java-util-splittable-random seed)))
192 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/rose_tree.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.rose-tree
11 | "A lazy tree data structure used for shrinking."
12 | (:refer-clojure :exclude [filter remove seq])
13 | (:require [#?(:clj clojure.core :cljs cljs.core) :as core]))
14 |
15 | (deftype RoseTree [root children]
16 | #?(:clj clojure.lang.Indexed
17 | :cljs IIndexed)
18 | (#?(:clj nth :cljs -nth) [this i]
19 | (cond (= i 0) root
20 | (= i 1) children
21 | :else (throw #?(:clj (IndexOutOfBoundsException.)
22 | :cljs (js/Error. "Index out of bounds in rose tree")))))
23 |
24 | (#?(:clj nth :cljs -nth) [this i not-found]
25 | (cond (= i 0) root
26 | (= i 1) children
27 | :else not-found)))
28 |
29 | (defn root
30 | "Returns the root of a Rose tree."
31 | {:no-doc true}
32 | [^RoseTree rose]
33 | (.-root rose))
34 |
35 | (defn children
36 | "Returns the children of the root of the Rose tree."
37 | {:no-doc true}
38 | [^RoseTree rose]
39 | (.-children rose))
40 |
41 | (defn make-rose
42 | [root children]
43 | (RoseTree. root children))
44 |
45 | (defn- exclude-nth
46 | "Exclude the nth value in a collection."
47 | [n coll]
48 | (lazy-seq
49 | (when-let [s (core/seq coll)]
50 | (if (zero? n)
51 | (rest coll)
52 | (cons (first s)
53 | (exclude-nth (dec n) (rest s)))))))
54 |
55 | (defn join
56 | "Turn a tree of trees into a single tree. Does this by concatenating
57 | children of the inner and outer trees."
58 | {:no-doc true}
59 | [rose]
60 | (let [outer-root (root rose)
61 | outer-children (children rose)
62 | inner-root (root outer-root)
63 | inner-children (children outer-root)]
64 | (make-rose inner-root (concat (map join outer-children)
65 | inner-children))))
66 |
67 | (defn pure
68 | "Puts a value `x` into a Rose tree, with no children."
69 | {:no-doc true}
70 | [x]
71 | (make-rose x []))
72 |
73 | (defn fmap
74 | "Applies functions `f` to all values in the tree."
75 | {:no-doc true}
76 | [f rose]
77 | (make-rose (f (root rose)) (map #(fmap f %) (children rose))))
78 |
79 | (defn bind
80 | "Takes a Rose tree (m) and a function (k) from
81 | values to Rose tree and returns a new Rose tree.
82 | This is the monadic bind (>>=) for Rose trees."
83 | {:no-doc true}
84 | [m k]
85 | (join (fmap k m)))
86 |
87 | (defn filter
88 | "Returns a new Rose tree whose values pass `pred`. Values who
89 | do not pass `pred` have their children cut out as well."
90 | {:no-doc true}
91 | [pred rose]
92 | (make-rose (root rose)
93 | (map #(filter pred %)
94 | (core/filter #(pred (root %)) (children rose)))))
95 |
96 | (defn permutations
97 | "Create a seq of vectors, where each rose in turn, has been replaced
98 | by its children."
99 | {:no-doc true}
100 | [roses]
101 | (for [[rose index] (map vector roses (range))
102 | child (children rose)]
103 | (assoc roses index child)))
104 |
105 | (defn zip
106 | "Apply `f` to the sequence of Rose trees `roses`."
107 | {:no-doc true}
108 | [f roses]
109 | (make-rose
110 | (apply f (map root roses))
111 | (map #(zip f %)
112 | (permutations roses))))
113 |
114 | (defn remove
115 | {:no-doc true}
116 | [roses]
117 | (concat
118 | (map-indexed (fn [index _] (exclude-nth index roses)) roses)
119 | (permutations (vec roses))))
120 |
121 | (defn ^:private unchunk
122 | "Returns an equivalent lazy seq that is not chunked."
123 | [a-lazy-seq]
124 | (take
125 | #?(:clj Double/POSITIVE_INFINITY :cljs js/Infinity)
126 | a-lazy-seq))
127 |
128 | (defn shrink
129 | {:no-doc true}
130 | [f roses]
131 | (if (core/seq roses)
132 | (make-rose (apply f (map root roses))
133 | (map #(shrink f %) (remove (unchunk roses))))
134 | (make-rose (f) [])))
135 |
136 | (declare shrink-vector*)
137 |
138 | (defn ^:private bifurcate
139 | "Returns a sequence of rose trees representing shrinks that discard
140 | half of the vector of roses."
141 | [f roses]
142 | (when (<= 4 (count roses))
143 | (let [left-count (quot (count roses) 2)]
144 | (lazy-seq
145 | (cons
146 | (shrink-vector* f (subvec roses 0 left-count))
147 | (lazy-seq
148 | (list (shrink-vector* f (subvec roses left-count)))))))))
149 |
150 | (defn ^:private shrink-vector*
151 | [f roses]
152 | (let [thing (shrink f roses)]
153 | (make-rose (root thing)
154 | (concat (bifurcate f roses) (children thing)))))
155 |
156 | (defn shrink-vector
157 | [f roses]
158 | {:pre [(vector? roses)]}
159 | (let [rose (shrink-vector* f roses)
160 | empty-rose (make-rose (f) [])]
161 | (if (empty? roses)
162 | rose
163 | (make-rose (root rose)
164 | (cons empty-rose (children rose))))))
165 |
166 | (defn collapse
167 | "Return a new rose-tree whose depth-one children
168 | are the children from depth one _and_ two of the input
169 | tree."
170 | {:no-doc true}
171 | [rose]
172 | (make-rose (root rose)
173 | (let [the-children (children rose)]
174 | (concat (map collapse the-children)
175 | (map collapse
176 | (mapcat children the-children))))))
177 |
178 | (defn- make-stack
179 | [children stack]
180 | (if-let [s (core/seq children)]
181 | (cons children stack)
182 | stack))
183 |
184 | (defn seq
185 | "Create a lazy-seq of all of the (unique) nodes in a shrink-tree.
186 | This assumes that two nodes with the same value have the same children.
187 | While it's not common, it's possible to create trees that don't
188 | fit that description. This function is significantly faster than
189 | brute-force enumerating all of the nodes in a tree, as there will
190 | be many duplicates."
191 | [rose]
192 | (let [helper (fn helper [rose seen stack]
193 | (let [node (root rose)
194 | the-children (children rose)]
195 | (lazy-seq
196 | (if-not (seen node)
197 | (cons node
198 | (if (core/seq the-children)
199 | (helper (first the-children) (conj seen node) (make-stack (rest the-children) stack))
200 | (when-let [s (core/seq stack)]
201 | (let [f (ffirst s)
202 | r (rest (first s))]
203 | (helper f (conj seen node) (make-stack r (rest s)))))))
204 | (when-let [s (core/seq stack)]
205 | (let [f (ffirst s)
206 | r (rest (first s))]
207 | (helper f seen (make-stack r (rest s)))))))))]
208 | (helper rose #{} '())))
209 |
--------------------------------------------------------------------------------
/src/target/cljs/self-host/clojure/test/check/test/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns clojure.test.check.test.runner
2 | (:require [clojure.string :as string]
3 | [cljs.nodejs :as nodejs]
4 | [cljs.js :as cljs]
5 | [cljs.reader :as reader]))
6 |
7 | (def out-dir "target/out-self-host")
8 |
9 | (def src-paths [out-dir
10 | "src/main/clojure"
11 | "src/test/clojure"])
12 |
13 | (defn init-runtime
14 | "Initializes the runtime so that we can use the cljs.user
15 | namespace and so that Google Closure is set up to work
16 | properly with :optimizations :none."
17 | []
18 | (set! (.-user js/cljs) #js {})
19 | ;; monkey-patch isProvided_ to avoid useless warnings
20 | (js* "goog.isProvided_ = function(x) { return false; };")
21 | ;; monkey-patch goog.require, skip all the loaded checks
22 | (set! (.-require js/goog)
23 | (fn [name]
24 | (js/CLOSURE_IMPORT_SCRIPT
25 | (aget (.. js/goog -dependencies_ -nameToPath) name))))
26 | ;; setup printing
27 | (nodejs/enable-util-print!)
28 | ;; redef goog.require to track loaded libs
29 | (set! *loaded-libs* #{"cljs.core"})
30 | (set! (.-require js/goog)
31 | (fn [name reload]
32 | (when (or (not (contains? *loaded-libs* name)) reload)
33 | (set! *loaded-libs* (conj (or *loaded-libs* #{}) name))
34 | (js/CLOSURE_IMPORT_SCRIPT
35 | (aget (.. js/goog -dependencies_ -nameToPath) name))))))
36 |
37 | ;; Node file reading fns
38 |
39 | (def fs (nodejs/require "fs"))
40 |
41 | (defn node-read-file
42 | "Accepts a filename to read and a callback. Upon success, invokes
43 | callback with the source. Otherwise invokes the callback with nil."
44 | [filename cb]
45 | (.readFile fs filename "utf-8"
46 | (fn [err source]
47 | (cb (when-not err
48 | source)))))
49 |
50 | (defn node-read-file-sync
51 | "Accepts a filename to read. Upon success, returns the source.
52 | Otherwise returns nil."
53 | [filename]
54 | (.readFileSync fs filename "utf-8"))
55 |
56 | ;; Facilities for loading Closure deps
57 |
58 | (defn closure-index
59 | "Builds an index of Closure files. Similar to
60 | cljs.js-deps/goog-dependencies*"
61 | []
62 | (let [paths-to-provides
63 | (map (fn [[_ path provides]]
64 | [path (map second
65 | (re-seq #"'(.*?)'" provides))])
66 | (re-seq #"\ngoog\.addDependency\('(.*)', \[(.*?)\].*"
67 | (node-read-file-sync (str out-dir "/goog/deps.js"))))]
68 | (into {}
69 | (for [[path provides] paths-to-provides
70 | provide provides]
71 | [(symbol provide) (str out-dir "/goog/" (second (re-find #"(.*)\.js$" path)))]))))
72 |
73 | (def closure-index-mem (memoize closure-index))
74 |
75 | (defn load-goog
76 | "Loads a Google Closure implementation source file."
77 | [name cb]
78 | (if-let [goog-path (get (closure-index-mem) name)]
79 | (if-let [source (node-read-file-sync (str goog-path ".js"))]
80 | (cb {:source source
81 | :lang :js})
82 | (cb nil))
83 | (cb nil)))
84 |
85 | ;; Facilities for loading files
86 |
87 | (defn- filename->lang
88 | "Converts a filename to a lang keyword by inspecting the file
89 | extension."
90 | [filename]
91 | (if (string/ends-with? filename ".js")
92 | :js
93 | :clj))
94 |
95 | (defn replace-extension
96 | "Replaces the extension on a file."
97 | [filename new-extension]
98 | (string/replace filename #".clj[sc]?$" new-extension))
99 |
100 | (defn parse-edn
101 | "Parses edn source to Clojure data."
102 | [edn-source]
103 | (reader/read-string edn-source))
104 |
105 | (defn- read-some
106 | "Reads the first filename in a sequence of supplied filenames,
107 | using a supplied read-file-fn, calling back upon first successful
108 | read, otherwise calling back with nil. Before calling back, first
109 | attempts to read AOT artifacts (JavaScript and cache edn)."
110 | [[filename & more-filenames] read-file-fn cb]
111 | (if filename
112 | (read-file-fn
113 | filename
114 | (fn [source]
115 | (if source
116 | (let [source-cb-value {:lang (filename->lang filename)
117 | :source source}]
118 | (if (or (string/ends-with? filename ".cljs")
119 | (string/ends-with? filename ".cljc"))
120 | (read-file-fn
121 | (replace-extension filename ".js")
122 | (fn [javascript-source]
123 | (if javascript-source
124 | (read-file-fn
125 | (str filename ".cache.edn")
126 | (fn [cache-edn]
127 | (if cache-edn
128 | (cb {:lang :js
129 | :source javascript-source
130 | :cache (parse-edn cache-edn)})
131 | (cb source-cb-value))))
132 | (cb source-cb-value))))
133 | (cb source-cb-value)))
134 | (read-some more-filenames read-file-fn cb))))
135 | (cb nil)))
136 |
137 | (defn filenames-to-try
138 | "Produces a sequence of filenames to try reading, in the
139 | order they should be tried."
140 | [src-paths macros path]
141 | (let [extensions (if macros
142 | [".clj" ".cljc"]
143 | [".cljs" ".cljc" ".js"])]
144 | (for [extension extensions
145 | src-path src-paths]
146 | (str src-path "/" path extension))))
147 |
148 | (defn skip-load?
149 | "Indicates namespaces that we either don't need to load,
150 | shouldn't load, or cannot load (owing to unresolved
151 | technical issues)."
152 | [name macros]
153 | ((if macros
154 | #{'cljs.core
155 | 'cljs.pprint
156 | 'cljs.env.macros
157 | 'cljs.analyzer.macros
158 | 'cljs.compiler.macros}
159 | #{'goog.object
160 | 'goog.string
161 | 'goog.string.StringBuffer
162 | 'goog.array
163 | 'cljs.core
164 | 'cljs.env
165 | 'cljs.pprint
166 | 'cljs.tools.reader
167 | 'clojure.walk}) name))
168 |
169 | ;; An atom to keep track of things we've already loaded
170 | (def loaded (atom #{}))
171 |
172 | (defn load?
173 | "Determines whether the given namespace should be loaded."
174 | [name macros]
175 | (let [do-not-load (or (@loaded [name macros])
176 | (skip-load? name macros))]
177 | (swap! loaded conj [name macros])
178 | (not do-not-load)))
179 |
180 | (defn make-load-fn
181 | "Makes a load function that will read from a sequence of src-paths
182 | using a supplied read-file-fn. It returns a cljs.js-compatible
183 | *load-fn*.
184 | Read-file-fn is a 2-arity function (fn [filename source-cb] ...) where
185 | source-cb is itself a function (fn [source] ...) that needs to be called
186 | with the source of the library (as string)."
187 | [src-paths read-file-fn]
188 | (fn [{:keys [name macros path]} cb]
189 | (if (load? name macros)
190 | (if (re-matches #"^goog/.*" path)
191 | (load-goog name cb)
192 | (read-some (filenames-to-try src-paths macros path) read-file-fn cb))
193 | (cb {:source ""
194 | :lang :js}))))
195 |
196 | ;; Facilities for evaluating JavaScript
197 |
198 | (def vm (nodejs/require "vm"))
199 |
200 | (defn node-eval
201 | "Evaluates JavaScript in node."
202 | [{:keys [name source]}]
203 | (if-not js/COMPILED
204 | (.runInThisContext vm source (str (munge name) ".js"))
205 | (js/eval source)))
206 |
207 | ;; Facilities for driving cljs.js
208 |
209 | (def load-fn (make-load-fn src-paths node-read-file))
210 |
211 | (defn eval-form
212 | "Evaluates a supplied form in a given namespace,
213 | calling back with the evaluation result."
214 | [st ns form cb]
215 | (cljs/eval st
216 | form
217 | {:ns ns
218 | :context :expr
219 | :load load-fn
220 | :eval node-eval
221 | :verbose false}
222 | cb))
223 |
224 | (defn run-tests
225 | "Runs the tests."
226 | []
227 | (let [st (cljs/empty-state)]
228 | (cljs/load-analysis-cache! st 'cljs.core$macros
229 | (parse-edn (node-read-file-sync (str out-dir "/cljs/core$macros.cljc.cache.edn"))))
230 | (eval-form st 'cljs.user
231 | '(ns runner.core
232 | (:require [cljs.test :as test :refer-macros [run-tests]]
233 | [clojure.test.check.generators :as gen]
234 | [clojure.test.check.test]
235 | [clojure.test.check.random-test]
236 | [clojure.test.check.rose-tree-test]
237 | [clojure.test.check.clojure-test-test]))
238 | (fn [{:keys [value error]}]
239 | (if error
240 | (prn error)
241 | (eval-form st 'runner.core
242 | '(run-tests
243 | 'clojure.test.check.test
244 | 'clojure.test.check.random-test
245 | 'clojure.test.check.rose-tree-test
246 | 'clojure.test.check.clojure-test-test)
247 | (fn [{:keys [value error]}]
248 | (when error
249 | (prn error)))))))))
250 |
251 | (defn -main [& args]
252 | (init-runtime)
253 | (run-tests))
254 |
255 | (set! *main-cli-fn* -main)
256 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check/clojure_test.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.clojure-test
11 | (:require #?(:clj [clojure.test :as ct]
12 | :cljs [cljs.test :as ct :include-macros true])
13 | [clojure.test.check :as tc]
14 | [clojure.test.check.clojure-test.assertions]
15 | [clojure.test.check.impl :refer [get-current-time-millis]])
16 | #?(:cljs (:require-macros [clojure.test.check.clojure-test :refer [defspec]])))
17 |
18 | (defn assert-check
19 | [{:keys [result result-data] :as m}]
20 | (if-let [error (:clojure.test.check.properties/error result-data)]
21 | (throw error)
22 | (ct/is (clojure.test.check.clojure-test/check? m))))
23 |
24 | (def ^:dynamic *default-test-count* 100)
25 |
26 | (defn default-reporter-fn
27 | "Default function passed as the :reporter-fn to clojure.test.check/quick-check.
28 | Delegates to clojure.test/report."
29 | [{:keys [type] :as args}]
30 | (case type
31 | :complete
32 | (let [testing-vars #?(:clj ct/*testing-vars*
33 | :cljs (:testing-vars ct/*current-env*))
34 | params (merge (select-keys args [:result :num-tests :seed
35 | :time-elapsed-ms])
36 | (when (seq testing-vars)
37 | {:test-var (-> testing-vars first meta :name name)}))]
38 | (ct/report {:type :clojure.test.check.clojure-test/complete
39 | :clojure.test.check.clojure-test/property (:property args)
40 | :clojure.test.check.clojure-test/complete params}))
41 |
42 | :trial
43 | (ct/report {:type :clojure.test.check.clojure-test/trial
44 | :clojure.test.check.clojure-test/property (:property args)
45 | :clojure.test.check.clojure-test/trial [(:num-tests args)
46 | (:num-tests-total args)]})
47 |
48 | :failure
49 | (ct/report {:type :clojure.test.check.clojure-test/shrinking
50 | :clojure.test.check.clojure-test/property (:property args)
51 | :clojure.test.check.clojure-test/params (vec (:fail args))})
52 |
53 | :shrunk
54 | (ct/report {:type :clojure.test.check.clojure-test/shrunk
55 | :clojure.test.check.clojure-test/property (:property args)
56 | :clojure.test.check.clojure-test/params (-> args :shrunk :smallest vec)})
57 | nil))
58 |
59 | (def ^:dynamic *default-opts*
60 | "The default options passed to clojure.test.check/quick-check
61 | by defspec."
62 | {:reporter-fn default-reporter-fn})
63 |
64 | (defn process-options
65 | {:no-doc true}
66 | [options]
67 | (cond (nil? options) (merge {:num-tests *default-test-count*} *default-opts*)
68 | (number? options) (assoc *default-opts* :num-tests options)
69 | (map? options) (merge {:num-tests *default-test-count*}
70 | *default-opts*
71 | options)
72 | :else (throw (ex-info (str "Invalid defspec options: " (pr-str options))
73 | {:bad-options options}))))
74 |
75 | (defmacro defspec
76 | "Defines a new clojure.test test var that uses `quick-check` to verify the
77 | property, running num-times trials by default. You can call the function defined as `name`
78 | with no arguments to trigger this test directly (i.e., without starting a
79 | wider clojure.test run). If called with arguments, the first argument is the number of
80 | trials, optionally followed by keyword arguments as defined for `quick-check`."
81 | {:arglists '([name property] [name num-tests? property] [name options? property])}
82 | ([name property] `(defspec ~name nil ~property))
83 | ([name options property]
84 | `(defn ~(vary-meta name assoc
85 | ::defspec true
86 | :test `(fn []
87 | (clojure.test.check.clojure-test/assert-check
88 | (assoc (~name) :test-var (str '~name)))))
89 | {:arglists '([] ~'[num-tests & {:keys [seed max-size reporter-fn]}])}
90 | ([] (let [options# (process-options ~options)]
91 | (apply ~name (:num-tests options#) (apply concat options#))))
92 | ([times# & {:as quick-check-opts#}]
93 | (let [options# (merge (process-options ~options) quick-check-opts#)]
94 | (apply
95 | tc/quick-check
96 | times#
97 | (vary-meta ~property assoc :name '~name)
98 | (apply concat options#)))))))
99 |
100 | (def ^:dynamic *report-trials*
101 | "Controls whether property trials should be reported via clojure.test/report.
102 | Valid values include:
103 |
104 | * false - no reporting of trials (default)
105 | * a function - will be passed a clojure.test/report-style map containing
106 | :clojure.test.check/property and :clojure.test.check/trial slots
107 | * true - provides quickcheck-style trial reporting (dots) via
108 | `trial-report-dots`
109 |
110 | (Note that all reporting requires running `quick-check` within the scope of a
111 | clojure.test run (via `test-ns`, `test-all-vars`, etc.))
112 |
113 | Reporting functions offered by clojure.test.check include `trial-report-dots` and
114 | `trial-report-periodic` (which prints more verbose trial progress information
115 | every `*trial-report-period*` milliseconds)."
116 | false)
117 |
118 | (def ^:dynamic *report-shrinking*
119 | "If true, a verbose report of the property being tested, the
120 | failing return value, and the arguments provoking that failure is emitted
121 | prior to the start of the shrinking search."
122 | false)
123 |
124 | (def ^:dynamic *trial-report-period*
125 | "Milliseconds between reports emitted by `trial-report-periodic`."
126 | 10000)
127 |
128 | (def ^:private last-trial-report (atom 0))
129 |
130 | (defn- get-property-name
131 | [{property-fun ::property :as report-map}]
132 | (or (-> property-fun meta :name) (ct/testing-vars-str report-map)))
133 |
134 | (defn with-test-out* [f]
135 | #?(:clj (ct/with-test-out (f))
136 | :cljs (f)))
137 |
138 | (defn trial-report-periodic
139 | "Intended to be bound as the value of `*report-trials*`; will emit a verbose
140 | status every `*trial-report-period*` milliseconds, like this one:
141 |
142 | Passing trial 3286 / 5000 for (your-test-var-name-here) (:)"
143 | [m]
144 | (let [t (get-current-time-millis)]
145 | (when (> (- t *trial-report-period*) @last-trial-report)
146 | (with-test-out*
147 | (fn []
148 | (println "Passing trial"
149 | (-> m ::trial first) "/" (-> m ::trial second)
150 | "for" (get-property-name m))))
151 | (reset! last-trial-report t))))
152 |
153 | (defn trial-report-dots
154 | "Intended to be bound as the value of `*report-trials*`; will emit a single
155 | dot every 1000 trials reported."
156 | [{[so-far total] ::trial}]
157 | (when (pos? so-far)
158 | (when (zero? (mod so-far 1000))
159 | (print ".")
160 | (flush))
161 | (when (== so-far total) (println))))
162 |
163 | (def ^:dynamic *report-completion*
164 | "If true, completed tests report test-var, num-tests and seed. Failed tests
165 | report shrunk results. Defaults to true."
166 | true)
167 |
168 | (when #?(:clj true :cljs (not (and *ns* (re-matches #".*\$macros" (name (ns-name *ns*))))))
169 | ;; This check accomodates a number of tools that rebind ct/report
170 | ;; to be a regular function instead of a multimethod, and may do
171 | ;; so before this code is loaded (see TCHECK-125)
172 | (if-not (instance? #?(:clj clojure.lang.MultiFn :cljs MultiFn) ct/report)
173 | (binding [*out* #?(:clj *err* :cljs *out*)]
174 | (println "clojure.test/report is not a multimethod, some reporting functions have been disabled."))
175 | (let [begin-test-var-method (get-method ct/report #?(:clj :begin-test-var
176 | :cljs [::ct/default :begin-test-var]))]
177 | (defmethod ct/report #?(:clj :begin-test-var
178 | :cljs [::ct/default :begin-test-var]) [m]
179 | (reset! last-trial-report (get-current-time-millis))
180 | (when begin-test-var-method (begin-test-var-method m)))
181 |
182 | (defmethod ct/report #?(:clj ::trial :cljs [::ct/default ::trial]) [m]
183 | (when-let [trial-report-fn (and *report-trials*
184 | (if (true? *report-trials*)
185 | trial-report-dots
186 | *report-trials*))]
187 | (trial-report-fn m)))
188 |
189 | (defmethod ct/report #?(:clj ::shrinking :cljs [::ct/default ::shrinking]) [m]
190 | (when *report-shrinking*
191 | (with-test-out*
192 | (fn []
193 | (println "Shrinking" (get-property-name m)
194 | "starting with parameters" (pr-str (::params m)))))))
195 |
196 | (defmethod ct/report #?(:clj ::complete :cljs [::ct/default ::complete]) [m]
197 | (when *report-completion*
198 | (prn (::complete m))))
199 |
200 | (defmethod ct/report #?(:clj ::shrunk :cljs [::ct/default ::shrunk]) [m]
201 | (when *report-completion*
202 | (with-test-out*
203 | (fn [] (prn m))))))))
204 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/test/check/clojure_test_test.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check.clojure-test-test
11 | (:require [clojure.set :as set]
12 | [clojure.string :as str]
13 | #?@(:cljs
14 | [[cljs.test
15 | :as test
16 | :include-macros true
17 | :refer [test-var]
18 | :refer-macros [is deftest testing]]
19 | [cljs.reader :refer [read-string]]])
20 | #?(:clj
21 | [clojure.test :as test :refer :all])
22 | [clojure.test.check.generators :as gen]
23 | [clojure.test.check.properties :as prop]
24 | [clojure.test.check.clojure-test :as ct :refer [defspec]]))
25 |
26 | (declare ^:dynamic test-report)
27 |
28 | (defn capturing-report [reports m]
29 | (swap! reports conj m)
30 | (test-report m))
31 |
32 | (defn ^:private capture-test-var
33 | "Returns map of :reports, :report-counters, :out, and :test-out."
34 | [v]
35 | (let [reports (atom [])]
36 | (binding [test-report test/report
37 | test/report (partial capturing-report reports)]
38 | #?(:clj
39 | (binding [*report-counters* (ref *initial-report-counters*)
40 | *test-out* (java.io.StringWriter.)
41 | *testing-contexts* (list)
42 | *testing-vars* (list)]
43 | (let [out (with-out-str (test-var v))]
44 | {:reports @reports
45 | :report-counters @*report-counters*
46 | :out out
47 | :test-out (str *test-out*)}))
48 | :cljs
49 | (binding [test/*current-env* (test/empty-env)]
50 | (let [out (with-out-str (test-var v))]
51 | ;; cljs.test doesn't distinguish between *out* and *test-out*
52 | {:reports @reports
53 | :report-counters (:report-counters test/*current-env*)
54 | :out out
55 | :test-out out}))))))
56 |
57 | (defspec default-trial-counts
58 | (prop/for-all* [gen/small-integer] (constantly true)))
59 |
60 | (deftest can-use-num-tests-default-value
61 | (let [{:keys [reports]} (capture-test-var #'default-trial-counts)
62 | num-tests (->> reports
63 | (filter #(= ::ct/complete (:type %)))
64 | first
65 | ::ct/complete
66 | :num-tests)]
67 | (is (= num-tests ct/*default-test-count*))))
68 |
69 | (deftest tcheck-116-debug-prn-should-be-optional
70 | (testing "bind ct/*report-completion* to false to supress completion report"
71 | (binding [ct/*report-completion* false]
72 | (let [{:keys [out]} (capture-test-var #'default-trial-counts)]
73 | (is (= out "")))))
74 |
75 | (testing "report completions by default"
76 | (let [{:keys [out]} (capture-test-var #'default-trial-counts)
77 | completion (-> out read-string (select-keys [:test-var :result :num-tests]))]
78 | (is (= completion {:test-var "default-trial-counts"
79 | :result true
80 | :num-tests ct/*default-test-count*})))))
81 |
82 | (def trial-counts-num-tests 5000)
83 | (defspec trial-counts trial-counts-num-tests
84 | (prop/for-all* [gen/small-integer] (constantly true)))
85 |
86 | (deftest can-specify-num-tests
87 | (let [{:keys [reports]} (capture-test-var #'trial-counts)
88 | num-tests (->> reports
89 | (filter #(= ::ct/complete (:type %)))
90 | first
91 | ::ct/complete
92 | :num-tests)]
93 | (is (= num-tests trial-counts-num-tests))))
94 |
95 | (deftest can-report-completion-with-specified-num-tests
96 | (let [{:keys [out]} (capture-test-var #'trial-counts)
97 | completion (-> out read-string (select-keys [:test-var :result :num-tests]))]
98 | (is (= completion {:test-var "trial-counts"
99 | :result true
100 | :num-tests trial-counts-num-tests}))))
101 |
102 | (deftest can-report-trials-with-dots
103 | (binding [ct/*report-trials* true]
104 | (let [{:keys [out]} (capture-test-var #'trial-counts)]
105 | (is (re-matches #?(:clj (java.util.regex.Pattern/compile "(?s)\\.{5}.+")
106 | :cljs #"\.{5}[\s\S]+")
107 | out)))))
108 |
109 | (defspec long-running-spec 1000
110 | (prop/for-all*
111 | []
112 | #(do
113 | #?(:clj
114 | (Thread/sleep 1)
115 | :cljs
116 | (let [start (.valueOf (js/Date.))]
117 | ;; let's do some busy waiting for 1 msec, so we avoid setTimeout
118 | ;; which would make our test async
119 | (while (= start
120 | (.valueOf (js/Date.)))
121 | (apply + (range 50)))))
122 | true)))
123 |
124 | (defn wait-for-clock-tick
125 | "Allow time to progress to avoid timing issues with sub-millisecond code."
126 | [start]
127 | #?(:clj (Thread/sleep 1)
128 | :cljs (while (>= start (.valueOf (js/Date.)))
129 | (apply + (range 50)))))
130 |
131 | (deftest can-report-trials-periodically
132 | (binding [ct/*report-trials* ct/trial-report-periodic
133 | ct/*trial-report-period* 500]
134 | (let [last-trial-report @#'ct/last-trial-report]
135 |
136 | (testing "test/report with {:type :begin-test-var} increments last-trial-report"
137 | (let [initial-trial-report @last-trial-report]
138 | (wait-for-clock-tick initial-trial-report)
139 | (test/report {:type :begin-test-var})
140 | (is (> @last-trial-report initial-trial-report))))
141 |
142 | (testing "test/report with other :type does not increment last-trial-report"
143 | (let [initial-trial-report @last-trial-report]
144 | (wait-for-clock-tick initial-trial-report)
145 | (test/report {:type :end-test-var})
146 | (is (= @last-trial-report initial-trial-report))))
147 |
148 | (testing "running the test increments last-trial-report"
149 | (let [initial-trial-report @last-trial-report]
150 | (wait-for-clock-tick initial-trial-report)
151 | (is (re-seq
152 | #"(Passing trial \d{3} / 1000 for long-running-spec\n)+"
153 | (:test-out
154 | (capture-test-var #'long-running-spec))))
155 | (is (> @last-trial-report initial-trial-report)))))))
156 |
157 | (defn- vector-elements-are-unique*
158 | [v]
159 | (== (count v) (count (distinct v))))
160 |
161 | (def ^:private vector-elements-are-unique
162 | (prop/for-all*
163 | [(gen/vector gen/small-integer)]
164 | vector-elements-are-unique*))
165 |
166 | (defspec this-is-supposed-to-fail 100 vector-elements-are-unique)
167 |
168 | (deftest can-report-failures
169 | (let [{:keys [test-out]} (capture-test-var #'this-is-supposed-to-fail)
170 | [result-line expected-line actual-line & more] (->> (str/split-lines test-out)
171 | ;; skip any ::shrunk messages
172 | (drop-while #(not (re-find #"^FAIL" %))))]
173 | (is (re-find #"^FAIL in \(this-is-supposed-to-fail\) " result-line))
174 | #?(:clj (is (re-find #"\(clojure_test_test\.cljc:\d+\)$" result-line)))
175 | (is (= expected-line "expected: {:result true}"))
176 | (let [actual (read-string (subs actual-line 10))]
177 | (is (set/subset? #{:result :result-data :seed :failing-size :num-tests :fail :shrunk}
178 | (set (keys actual))))
179 | (is (= false (:result actual))))
180 | (is (nil? more))))
181 |
182 | (deftest can-report-shrinking
183 | (testing "don't emit Shrinking messages by default"
184 | (let [{:keys [report-counters test-out]} (capture-test-var #'this-is-supposed-to-fail)]
185 | (is (== 1 (:fail report-counters)))
186 | (is (not (re-find #"Shrinking" test-out)))))
187 |
188 | (testing "bind *report-shrinking* to true to emit Shrinking messages"
189 | (binding [ct/*report-shrinking* true]
190 | (let [{:keys [report-counters test-out]} (capture-test-var #'this-is-supposed-to-fail)]
191 | (is (== 1 (:fail report-counters)))
192 | (is (re-seq #"Shrinking this-is-supposed-to-fail starting with parameters \[\[[\s\S]+"
193 | test-out))))))
194 |
195 | (deftest tcheck-118-pass-shrunk-input-on-to-clojure-test
196 | (let [{trial ::ct/trial, shrinking ::ct/shrinking, shrunk ::ct/shrunk}
197 | (group-by :type (:reports (capture-test-var #'this-is-supposed-to-fail)))]
198 | ;; should have had some successful runs because the initial size
199 | ;; is too small for duplicates
200 | (is (seq trial))
201 |
202 | (is (= 1 (count shrinking)))
203 | (is (not (-> shrinking first ::ct/params first (->> (apply distinct?)))))
204 |
205 | (is (= 1 (count shrunk)))
206 | (let [[a b & more] (-> shrunk first ::ct/params first)]
207 | (is (empty? more))
208 | (is (and a b (= a b))))))
209 |
210 | (deftest can-report-shrunk
211 | (testing "supress shrunk report when ct/*report-completion* is bound to false"
212 | (binding [ct/*report-completion* false]
213 | (let [{:keys [test-out]} (capture-test-var #'this-is-supposed-to-fail)]
214 | (is (not (re-find #":type :clojure.test.check.clojure-test/shrunk" test-out))))))
215 |
216 | (testing "report shrunk by default"
217 | (let [{:keys [test-out]} (capture-test-var #'this-is-supposed-to-fail)]
218 | (is (re-find #":type :clojure.test.check.clojure-test/shrunk" test-out)))))
219 |
220 | (defspec this-throws-an-exception
221 | (prop/for-all [x gen/nat]
222 | (throw (ex-info "this property is terrible" {}))))
223 |
224 | (deftest can-re-throw-exceptions-to-clojure-test
225 | (let [{:keys [report-counters test-out]} (capture-test-var #'this-throws-an-exception)]
226 | (is (= report-counters {:test 1, :pass 0, :fail 0, :error 1}))
227 | (is (re-find #"ERROR in \(this-throws-an-exception\)" test-out))
228 | ;; TCHECK-151
229 | (is (= 1 (count (re-seq #"this property is terrible" test-out)))
230 | "Only prints exceptions twice")))
231 |
232 |
233 | (defn test-ns-hook
234 | "Run only tests defined by deftest, ignoring those defined by defspec."
235 | []
236 | (let [tests (->> (vals (ns-interns #?(:clj (find-ns 'clojure.test.check.clojure-test-test)
237 | :cljs 'clojure.test.check.clojure-test-test)))
238 | (filter #(let [m (meta %)]
239 | (and (:test m)
240 | (not (::ct/defspec m)))))
241 | (sort-by #(:line (meta %))))]
242 | (test/test-vars tests)))
243 |
--------------------------------------------------------------------------------
/CHANGELOG.markdown:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 1.1.2 (2025-11-25)
4 |
5 | * TCHECK-160 - Reduce allocation churn on shrink-int
6 |
7 | ## 1.1.1 (2021-12-01)
8 |
9 | * Exclude clojure.core/abs (prepping for use with 1.11)
10 |
11 | ## 1.1.0 (2020-07-10)
12 |
13 | * TCHECK-155 - don't generate :/ for keywords
14 |
15 | ## 1.0.0 (2020-02-18)
16 |
17 | * No changes
18 |
19 | ## 0.10.0 (2019-06-30 as RC1, 2019-08-11 as final)
20 |
21 | * Docstring improvements
22 | * Deprecated five small integer generators
23 | * `gen/int`
24 | * `gen/pos-int`
25 | * `gen/neg-int`
26 | * `gen/s-pos-int`
27 | * `gen/s-neg-int`
28 | Added a `gen/small-integer` to replace `gen/int`, and the
29 | docstrings for all the deprecated generators suggest alternatives
30 | * Added `gen/size-bounded-bigint` and `gen/big-ratio`, both jvm-only
31 | * Added `*-equatable` variants of `gen/simple-type`,
32 | `gen/simple-type-printable`, `gen/any`, and `gen/any-printable`;
33 | the only current difference is that the new generators never generate
34 | a `NaN`, and so they should always be `=` to equivalent objects
35 |
36 | ## 0.10.0-alpha4 (2019-03-10)
37 |
38 | * Automatically require cljs macros so users don't have to
39 | ([TCHECK-154](http://dev.clojure.org/jira/browse/TCHECK-154))
40 |
41 | ## 0.10.0-alpha3 (2018-05-27)
42 |
43 | * Improve failure reporting
44 | ([TCHECK-34](http://dev.clojure.org/jira/browse/TCHECK-34))
45 | * `gen/frequency` doesn't shrink to zero-weighted entries
46 | ([TCHECK-129](http://dev.clojure.org/jira/browse/TCHECK-129))
47 | * Faster PRNG in clojurescript
48 | * General symbol/keyword generator improvements (shorter on average,
49 | keyword generator includes colons and can generate `:/`)
50 | * passing test reporting is optional
51 | ([TCHECK-116](http://dev.clojure.org/jira/browse/TCHECK-116))
52 | * Doesn't crash when some other plugin redefines `clojure.test/report`
53 | ([TCHECK-125](http://dev.clojure.org/jira/browse/TCHECK-125))
54 | * test names used more reliably in certain reportings
55 | ([TCHECK-124](http://dev.clojure.org/jira/browse/TCHECK-124))
56 | * Removed the map-style bindings in `gen/let` introduced in `alpha1`
57 | ([TCHECK-133](http://dev.clojure.org/jira/browse/TCHECK-133))
58 | * Changed some of the key names in the `reporter-fn` calls to more
59 | closely match the data returned from `quick-check`, to minimize
60 | confusion
61 | * Clarified meaning of bindings in the `for-all` docstring
62 | ([TCHECK-121](http://dev.clojure.org/jira/browse/TCHECK-121))
63 | * Updated the `:result` key to be more backwards compatible, added a
64 | `:pass?` key, renamed `results/passing?` to `results/pass?`
65 | ([TCHECK-142](http://dev.clojure.org/jira/browse/TCHECK-142))
66 | * Added timing keys to the `quick-check` return data
67 | ([TCHECK-95](http://dev.clojure.org/jira/browse/TCHECK-95))
68 |
69 | ## 0.10.0-alpha2 (2017-06-27)
70 |
71 | * Added a 3rd optional argument to `gen/generate`, the `seed`
72 | * Reverted behavioral change in `prop/for-all` so that returned
73 | exceptions are treated as thrown exceptions
74 | ([TCHECK-131](http://dev.clojure.org/jira/browse/TCHECK-131))
75 |
76 | ## 0.10.0-alpha1 (2017-06-07)
77 |
78 | * Major changes
79 | * Adds a `:reporter-fn` callback for the `quick-check` function,
80 | fixing [TCHECK-33](http://dev.clojure.org/jira/browse/TCHECK-33)
81 | (this item is the most subject to change before the final release)
82 | * Rewrote `recursive-gen` to be more careful about sizing
83 | ([TCHECK-83](http://dev.clojure.org/jira/browse/TCHECK-83))
84 | * A new protocol clojure.test.check.results/Result that gives a standard
85 | way for a test to return metadata to the test runner
86 | * Minor changes
87 | * Generated keywords and symbols are now smaller, on average
88 | * `gen/any` and `gen/any-printable` can generate sets now
89 | * Collections shrink faster now
90 | * Created `clojure.test.check.clojure-test/*default-opts*`
91 | * `gen/frequency` can shrink to earlier generators
92 | ([TCHECK-114](http://dev.clojure.org/jira/browse/TCHECK-114))
93 | * `gen/such-that` can take an `ex-fn` option to customize exceptions
94 | * `gen/let` supports map bindings to specify independence
95 | ([TCHECK-98](http://dev.clojure.org/jira/browse/TCHECK-98))
96 | * Internal tweaks for compatibility with self-hosted cljs
97 | ([TCHECK-105](http://dev.clojure.org/jira/browse/TCHECK-105))
98 | * `gen/sample` uses size up to `200` instead of `100`
99 | * An assortment of internal changes
100 |
101 | ## 0.9.0 (2015-11-12)
102 |
103 | 0.9.0 contains an assortment of new generators, and is the first
104 | release that requires Clojure 1.7.0 (due to using `cljc` files to unify
105 | the clj & cljs code).
106 |
107 | * `gen/let`
108 | * `gen/uuid`
109 | * `gen/double`
110 | * `gen/large-integer`
111 | * `gen/set`
112 | * `gen/sorted-set`
113 | * `gen/vector-distinct`
114 | * `gen/vector-distinct-by`
115 | * `gen/list-distinct`
116 | * `gen/list-distinct-by`
117 | * `gen/map` now takes sizing options, the same as the preceding
118 | collection generators
119 |
120 |
121 | ## 0.8.2
122 | * Bugfix for [TCHECK-77](http://dev.clojure.org/jira/browse/TCHECK-77),
123 | which was a regression in the precision of `gen/choose` introduced in
124 | 0.8.1.
125 |
126 | ## 0.8.1
127 | * Bugfix for [TCHECK-73](http://dev.clojure.org/jira/browse/TCHECK-73),
128 | in which `gen/int` would sometimes generate doubles.
129 |
130 | ## 0.8.0
131 | * **Breaking ClojureScript Change**:
132 | The namespace names have changed:
133 | - `cljs.*` → `clojure.*`
134 | - `cljs.test.check.cljs-test` → `clojure.test.check.clojure-test`
135 | * Randomness is now provided by a port of the
136 | `java.util.SplittableRandom` algorithm, instead of
137 | `java.util.Random` and `goog.testing.PsuedoRandom`.
138 | * New functions in `clojure.test.check.generators`:
139 | * `scale`
140 | * `generate`
141 |
142 | ## 0.7.0
143 | * Add ClojureScript support, written by @swannodette. More usage can be
144 | found [in the
145 | README](https://github.com/clojure/test.check#clojurescript).
146 | * Raise an error if the incorrect arity of `defspec` is used.
147 | * Don't export the following private functions:
148 | * `make-rng`
149 | * `not-falsey-or-exception?`
150 | * `make-gen`
151 | * `bind-helper`
152 | * `recursive-helper`
153 | * `binding-vars`
154 | * `binding-gens`
155 |
156 | ## 0.6.2
157 | * Fix regression where floating point numbers weren't allowed to describe
158 | the number of tests in the defspec macro. Ex: (defspec foo 1e5 ...) now
159 | works again.
160 | * Allow `gen/shuffle` to work on anything that can be turned into a
161 | sequence.
162 | * Allow for testing to be cancelled inside the REPL with ctrl-c.
163 | * Fix StackOverflow error that would be caused when generating vector with
164 | more than about 10k elements.
165 |
166 | ## 0.6.1
167 | * Fix bug introduced in 0.6.0: The `defspec` macro could only accept map or
168 | numeric _literals_ as options, instead of a symbol.
169 |
170 | ## 0.6.0
171 | * Add a `shuffle` generator, which generates permutations of a given
172 | sequence.
173 | * Rename `alpha-numeric` functions to `alphanumeric`. `char-alpha-numeric`
174 | and `string-alpha-numeric` are now deprecated in favor of
175 | `char-alphanumeric` and `string-alphanumeric`. The deprecated versions
176 | will be removed in a future version of `test.check`.
177 | * Update the `defspec` macro to allow an optional map argument, which
178 | allows for the setting of `:seed`, `:num-tests` and `:max-size`. Examples
179 | below:
180 |
181 | ```clojure
182 | (defspec run-with-map {:num-tests 1} (prop/for-all* [gen/int] (constantly true)))
183 |
184 | (defspec run-with-map {:num-tests 1
185 | :seed 1}
186 | my-prop)
187 | ```
188 | * Provide better error-messages for the misuse of generator combinators.
189 | Many of the functions now test that their arguments are of the
190 | appropriate type, and will throw a runtime error otherwise.
191 | * Print test failures that can be copied directly. For example, print the
192 | empty string as `""`, instead of a blank value. This is fixed by using
193 | `prn` instead of `println`.
194 |
195 | ## 0.5.9
196 | * Better sizing for recursive generators
197 | * Add `gen/recursive-gen` function for writing recursive generators
198 | * Add keyword and symbol generators that may include namespaces
199 | * `gen/keyword-ns`
200 | * `gen/symbol-ns`
201 |
202 | ## 0.5.8
203 | * Limit the number of retries for gen/such-that. A two-arity version is
204 | provided if you need to retry more than 10 times. This should be a
205 | code-smell, though.
206 | * Return random seed used on test failure
207 | * Fix keyword generator to conform to reader specs
208 | * Correct documentation mentions of namespaces
209 | * Add more detailed contributing instructions
210 | * Internal: use a record internally for generators. This is meant to help
211 | convey the fact that generators are opaque
212 | * Extract rose-tree code into a separate namespace
213 |
214 | ## 0.5.7
215 | * Rename project to test.check. See README for migrating
216 | from _simple-check_.
217 |
218 | ## simple-check 0.5.6
219 | * Fix `choose` bug introduced in 0.5.4, the upper-bound was not inclusive.
220 |
221 | ## simple-check 0.5.5
222 | * Fix botched release
223 |
224 | ## simple-check 0.5.4
225 | * Fix documentation typos
226 | * Fix defspec default num-tests bug (#52)
227 | * Fix docstring position on `exclude-nth` function (#50)
228 | * Add rose-seq helper function
229 | * Use full Long range in rand-range (#42)
230 | * More useful error-message with `one-of`
231 | * Add `no-shrink` and `shrink-2` combinators
232 | * Fix `interpose-twice-the-length` test (#52)
233 |
234 | ## simple-check 0.5.3
235 | * All dependencies are now dev-dependencies
236 | * Minor doc typo correction
237 |
238 | ## simple-check 0.5.2
239 | * Improve shrinking for sequences
240 | * __BACKWARD_INCOMPATIBILITY__: update API for gen/hash-map,
241 | now mirrors closer the clojure.core API
242 |
243 | ## simple-check 0.5.1
244 | * Remove unused dependency (clj-tuple)
245 | * Add 'any' generator
246 | * Add not-empty generator modifier
247 | * Change one-of to shrink toward earlier generators on failure
248 |
249 | ## simple-check 0.5.0
250 | * Shrinking will only shrink to values that could have been created by the
251 | generator
252 | * Bugfix with the byte and bytes generator
253 | * Create strings of variable length (instead of always length=size)
254 | * Fix off-by-one error in number of tests reported
255 | * Generate sizes starting at 0, not 1
256 |
257 | ## simple-check 0.4.1
258 | * When a property fails, add the result of the final shrink to the output
259 | map. This can be found in [:shrunk :result]
260 | * Make pairs respect their size during shrinking (they're just tuples)
261 | * Add a default num-tests to `defspec`
262 |
263 | ## simple-check 0.4.0
264 | * tuple generator now creates tuple types (from `clj-tuple`)
265 | * __BACKWARD_INCOMPATIBILITY__: `gen/tuple` now takes var-args, instead
266 | of a vector of arguments. So change:
267 |
268 | ```clojure
269 | (gen/tuple [gen/int gen/boolean])
270 | ```
271 |
272 | to
273 |
274 | ```clojure
275 | (gen/tuple gen/int gen/boolean)
276 | ```
277 |
278 | Tuples will now retain their size when shrunk.
279 |
280 | * add a `ratio` generator
281 | * correctly shrink empty lists
282 | * switch to `codox-md` for documentation
283 |
284 | ## simple-check 0.3.0
285 | * add strictly-positive and strictly-negative integer generators
286 | * allow scientific notation in number of tests parameter
287 | * allow specification of number of elements in vector generator
288 |
289 | ## simple-check 0.2.1
290 | * remove unused `diff` function
291 | * eliminate reflection warnings
292 |
293 | ## simple-check 0.2.0
294 | * added `gen/byte` and `gen/bytes`
295 | * swapped order of args in `gen/such-that`
296 | * swapped order of args in `simple-check.clojure-test/defspec`
297 | * add implicit `do` to `defspec` body
298 |
299 | ## simple-check 0.1.0
300 | * First release
301 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
205 |
206 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/test/check.cljc:
--------------------------------------------------------------------------------
1 | ; Copyright (c) Rich Hickey, Reid Draper, and contributors.
2 | ; All rights reserved.
3 | ; The use and distribution terms for this software are covered by the
4 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
5 | ; which can be found in the file epl-v10.html at the root of this distribution.
6 | ; By using this software in any fashion, you are agreeing to be bound by
7 | ; the terms of this license.
8 | ; You must not remove this notice, or any other, from this software.
9 |
10 | (ns clojure.test.check
11 | (:require [clojure.test.check.generators :as gen]
12 | [clojure.test.check.random :as random]
13 | [clojure.test.check.results :as results]
14 | [clojure.test.check.rose-tree :as rose]
15 | [clojure.test.check.impl :refer [get-current-time-millis]]))
16 |
17 | (declare shrink-loop failure)
18 |
19 | (defn- make-rng
20 | [seed]
21 | (if seed
22 | [seed (random/make-random seed)]
23 | (let [non-nil-seed (get-current-time-millis)]
24 | [non-nil-seed (random/make-random non-nil-seed)])))
25 |
26 | (defn- complete
27 | [property num-trials seed start-time reporter-fn]
28 | (let [time-elapsed-ms (- (get-current-time-millis) start-time)]
29 | (reporter-fn {:type :complete
30 | :property property
31 | :result true
32 | :pass? true
33 | :num-tests num-trials
34 | :time-elapsed-ms time-elapsed-ms
35 | :seed seed})
36 | {:result true
37 | :pass? true
38 | :num-tests num-trials
39 | :time-elapsed-ms time-elapsed-ms
40 | :seed seed}))
41 |
42 |
43 | (defn ^:private legacy-result
44 | "Returns a value for the legacy :result key, which has the peculiar
45 | property of conflating returned exceptions with thrown exceptions."
46 | [result]
47 | (if (satisfies? results/Result result)
48 | (let [d (results/result-data result)]
49 | (if-let [[_ e] (find d :clojure.test.check.properties/error)]
50 | #?(:clj e
51 | :cljs (if (instance? js/Error e)
52 | e
53 | (ex-info "Non-Error object thrown in test"
54 | {}
55 | e)))
56 | (results/pass? result)))
57 | result))
58 |
59 | (defn quick-check
60 | "Tests `property` `num-tests` times.
61 |
62 | Takes several optional keys:
63 |
64 | `:seed`
65 | Can be used to re-run previous tests, as the seed used is returned
66 | after a test is run.
67 |
68 | `:max-size`.
69 | can be used to control the 'size' of generated values. The size will
70 | start at 0, and grow up to max-size, as the number of tests increases.
71 | Generators will use the size parameter to bound their growth. This
72 | prevents, for example, generating a five-thousand element vector on
73 | the very first test.
74 |
75 | `:reporter-fn`
76 | A callback function that will be called at various points in the test
77 | run, with a map like:
78 |
79 | ;; called after a passing trial
80 | {:type :trial
81 | :args [...]
82 | :num-tests
83 | :num-tests-total
84 | :seed 42
85 | :pass? true
86 | :property #<...>
87 | :result true
88 | :result-data {...}}
89 |
90 | ;; called after the first failing trial
91 | {:type :failure
92 | :fail [...failing args...]
93 | :failing-size 13
94 | :num-tests
95 | :pass? false
96 | :property #<...>
97 | :result false/exception
98 | :result-data {...}
99 | :seed 42}
100 |
101 | It will also be called on :complete, :shrink-step and :shrunk. Many
102 | of the keys also appear in the quick-check return value, and are
103 | documented below.
104 |
105 | If the test passes, the return value will be something like:
106 |
107 | {:num-tests 100,
108 | :pass? true,
109 | :result true,
110 | :seed 1561826505982,
111 | :time-elapsed-ms 24}
112 |
113 | If the test fails, the return value will be something like:
114 |
115 | {:fail [0],
116 | :failed-after-ms 0,
117 | :failing-size 0,
118 | :num-tests 1,
119 | :pass? false,
120 | :result false,
121 | :result-data nil,
122 | :seed 1561826506080,
123 | :shrunk
124 | {:depth 0,
125 | :pass? false,
126 | :result false,
127 | :result-data nil,
128 | :smallest [0],
129 | :time-shrinking-ms 0,
130 | :total-nodes-visited 0}}
131 |
132 | The meaning of the individual entries is:
133 |
134 | :num-tests
135 | The total number of trials that was were run, not including
136 | shrinking (if applicable)
137 |
138 | :pass?
139 | A boolean indicating whether the test passed or failed
140 |
141 | :result
142 | A legacy entry that is similar to :pass?
143 |
144 | :seed
145 | The seed used for the entire test run; can be used to reproduce
146 | a test run by passing it as the :seed option to quick-check
147 |
148 | :time-elapsed-ms
149 | The total time, in milliseconds, of a successful test run
150 |
151 | :fail
152 | The generated values for the first failure; note that this is
153 | always a vector, since prop/for-all can have multiple clauses
154 |
155 | :failed-after-ms
156 | The total time, in milliseconds, spent finding the first failing
157 | trial
158 |
159 | :failing-size
160 | The value of the size parameter used to generate the first
161 | failure
162 |
163 | :result-data
164 | The result data, if any, of the first failing trial (to take
165 | advantage of this a property must return an object satisfying
166 | the clojure.test.check.results/Result protocol)
167 |
168 | :shrunk
169 | A map of data about the shrinking process; nested keys that
170 | appear at the top level have the same meaning; other keys are
171 | documented next
172 |
173 | :shrunk / :depth
174 | The depth in the shrink tree that the smallest failing instance
175 | was found; this is essentially the idea of how many times the
176 | original failure was successfully shrunk
177 |
178 | :smallest
179 | The smallest values found in the shrinking process that still
180 | fail the test; this is a vector of the same type as :fail
181 |
182 | :time-shrinking-ms
183 | The total time, in milliseconds, spent shrinking
184 |
185 | :total-nodes-visited
186 | The total number of steps in the shrinking process
187 |
188 | Examples:
189 |
190 | (def p (for-all [a gen/nat] (> (* a a) a)))
191 |
192 | (quick-check 100 p)
193 | (quick-check 200 p
194 | :seed 42
195 | :max-size 50
196 | :reporter-fn (fn [m]
197 | (when (= :failure (:type m))
198 | (println \"Uh oh...\"))))"
199 | [num-tests property & {:keys [seed max-size reporter-fn]
200 | :or {max-size 200, reporter-fn (constantly nil)}}]
201 | (let [[created-seed rng] (make-rng seed)
202 | size-seq (gen/make-size-range-seq max-size)
203 | start-time (get-current-time-millis)]
204 | (loop [so-far 0
205 | size-seq size-seq
206 | rstate rng]
207 | (if (== so-far num-tests)
208 | (complete property num-tests created-seed start-time reporter-fn)
209 | (let [[size & rest-size-seq] size-seq
210 | [r1 r2] (random/split rstate)
211 | result-map-rose (gen/call-gen property r1 size)
212 | result-map (rose/root result-map-rose)
213 | result (:result result-map)
214 | args (:args result-map)
215 | so-far (inc so-far)]
216 | (if (results/pass? result)
217 | (do
218 | (reporter-fn {:type :trial
219 | :args args
220 | :num-tests so-far
221 | :num-tests-total num-tests
222 | :pass? true
223 | :property property
224 | :result result
225 | :result-data (results/result-data result)
226 | :seed seed})
227 | (recur so-far rest-size-seq r2))
228 | (failure property result-map-rose so-far size
229 | created-seed start-time reporter-fn)))))))
230 |
231 | (defn- smallest-shrink
232 | [total-nodes-visited depth smallest start-time]
233 | (let [{:keys [result]} smallest]
234 | {:total-nodes-visited total-nodes-visited
235 | :depth depth
236 | :pass? false
237 | :result (legacy-result result)
238 | :result-data (results/result-data result)
239 | :time-shrinking-ms (- (get-current-time-millis) start-time)
240 | :smallest (:args smallest)}))
241 |
242 | (defn- shrink-loop
243 | "Shrinking a value produces a sequence of smaller values of the same type.
244 | Each of these values can then be shrunk. Think of this as a tree. We do a
245 | modified depth-first search of the tree:
246 |
247 | Do a non-exhaustive search for a deeper (than the root) failing example.
248 | Additional rules added to depth-first search:
249 | * If a node passes the property, you may continue searching at this depth,
250 | but not backtrack
251 | * If a node fails the property, search its children
252 | The value returned is the left-most failing example at the depth where a
253 | passing example was found.
254 |
255 | Calls reporter-fn on every shrink step."
256 | [rose-tree reporter-fn]
257 | (let [start-time (get-current-time-millis)
258 | shrinks-this-depth (rose/children rose-tree)]
259 | (loop [nodes shrinks-this-depth
260 | current-smallest (rose/root rose-tree)
261 | total-nodes-visited 0
262 | depth 0]
263 | (if (empty? nodes)
264 | (smallest-shrink total-nodes-visited depth current-smallest start-time)
265 | (let [;; can't destructure here because that could force
266 | ;; evaluation of (second nodes)
267 | head (first nodes)
268 | tail (rest nodes)
269 | result (:result (rose/root head))
270 | args (:args (rose/root head))
271 | pass? (results/pass? result)
272 | reporter-fn-arg {:type :shrink-step
273 | :shrinking {:args args
274 | :depth depth
275 | :pass? (boolean pass?)
276 | :result result
277 | :result-data (results/result-data result)
278 | :smallest (:args current-smallest)
279 | :total-nodes-visited total-nodes-visited}}]
280 | (if pass?
281 | ;; this node passed the test, so now try testing its right-siblings
282 | (do
283 | (reporter-fn reporter-fn-arg)
284 | (recur tail current-smallest (inc total-nodes-visited) depth))
285 | ;; this node failed the test, so check if it has children,
286 | ;; if so, traverse down them. If not, save this as the best example
287 | ;; seen now and then look at the right-siblings
288 | ;; children
289 | (let [new-smallest (rose/root head)]
290 | (reporter-fn (assoc-in reporter-fn-arg
291 | [:shrinking :smallest]
292 | (:args new-smallest)))
293 | (if-let [children (seq (rose/children head))]
294 | (recur children new-smallest (inc total-nodes-visited) (inc depth))
295 | (recur tail new-smallest (inc total-nodes-visited) depth)))))))))
296 |
297 | (defn- failure
298 | [property failing-rose-tree trial-number size seed start-time reporter-fn]
299 | (let [failed-after-ms (- (get-current-time-millis) start-time)
300 | root (rose/root failing-rose-tree)
301 | result (:result root)
302 | failure-data {:fail (:args root)
303 | :failing-size size
304 | :num-tests trial-number
305 | :pass? false
306 | :property property
307 | :result (legacy-result result)
308 | :result-data (results/result-data result)
309 | :failed-after-ms failed-after-ms
310 | :seed seed}]
311 |
312 | (reporter-fn (assoc failure-data :type :failure))
313 |
314 | (let [shrunk (shrink-loop failing-rose-tree
315 | #(reporter-fn (merge failure-data %)))]
316 | (reporter-fn (assoc failure-data
317 | :type :shrunk
318 | :shrunk shrunk))
319 | (-> failure-data
320 | (dissoc :property)
321 | (assoc :shrunk shrunk)))))
322 |
--------------------------------------------------------------------------------
/doc/intro.md:
--------------------------------------------------------------------------------
1 | # Introduction to test.check
2 |
3 | test.check is a tool for writing property-based tests. This differs from
4 | traditional unit-testing, where you write individual test-cases. With
5 | test.check you write universal quantifications, properties that should hold
6 | true for all input. For example, for all vectors, reversing the vector should
7 | preserve the count. Reversing it twice should equal the input. In this guide,
8 | we'll cover the thought process for coming up with properties, as well as the
9 | practice of writing the tests themselves.
10 |
11 | ## A simple example
12 |
13 | First, let's start with an example, suppose we want to test a sort function.
14 | It's easy to come up with some trivial properties for our function, namely that
15 | the output should be in ascending order. We also might want to make sure that
16 | the count of the input is preserved. Our test might look like:
17 |
18 | ```clojure
19 | (require '[clojure.test.check :as tc]
20 | '[clojure.test.check.generators :as gen]
21 | '[clojure.test.check.properties :as prop #?@(:cljs [:include-macros true])])
22 |
23 | (def property
24 | (prop/for-all [v (gen/vector gen/small-integer)]
25 | (let [s (sort v)]
26 | (and (= (count v) (count s))
27 | (or (empty? s)
28 | (apply <= s))))))
29 |
30 | ;; test our property
31 | (tc/quick-check 100 property)
32 | ;; => {:result true,
33 | ;; => :pass? true,
34 | ;; => :num-tests 100,
35 | ;; => :time-elapsed-ms 90,
36 | ;; => :seed 1528578896309}
37 | ```
38 |
39 | What if we were to forget to actually sort our vector? The test will fail, and
40 | then test.check will try and find 'smaller' inputs that still cause the test
41 | to fail. For example, the function might originally fail with input:
42 | `[5 4 2 2 2]`, but test.check will shrink this down to `[0 -1]` (or `[1 0]`).
43 |
44 | ```clojure
45 | (def bad-property
46 | (prop/for-all [v (gen/vector gen/small-integer)]
47 | (or (empty? v) (apply <= v))))
48 |
49 | (tc/quick-check 100 bad-property)
50 | ;; => {:num-tests 6,
51 | ;; => :seed 1528579035247,
52 | ;; => :fail [[-2 -4 -4 -3]],
53 | ;; => :failed-after-ms 1,
54 | ;; => :result false,
55 | ;; => :result-data nil,
56 | ;; => :failing-size 5,
57 | ;; => :pass? false,
58 | ;; => :shrunk
59 | ;; => {:total-nodes-visited 16,
60 | ;; => :depth 4,
61 | ;; => :pass? false,
62 | ;; => :result false,
63 | ;; => :result-data nil,
64 | ;; => :time-shrinking-ms 1,
65 | ;; => :smallest [[0 -1]]}}
66 | ```
67 |
68 | This process of shrinking is done automatically, even for our more complex
69 | generators that we write ourselves.
70 |
71 | ## Generators
72 |
73 | In order to write our property, we'll use generators. A generator knows how to
74 | generate random values for a specific type. The `test.check.generators`
75 | namespace has many built-in generators, as well as combinators for creating
76 | your own new generators. You can write sophisticated generators just by
77 | combining the existing generators with the given combinators. As we write
78 | generators, we can see them in practice with the `sample` function:
79 |
80 | ```clojure
81 | (require '[clojure.test.check.generators :as gen])
82 |
83 | (gen/sample gen/small-integer)
84 | ;; => (0 1 -1 0 -1 4 4 2 7 1)
85 | ```
86 |
87 | we can ask for more samples:
88 |
89 | ```clojure
90 | (gen/sample gen/small-integer 20)
91 | ;; => (0 1 1 0 2 -4 0 5 -7 -8 4 5 3 11 -9 -4 6 -5 -3 0)
92 | ```
93 |
94 | or get a lazy-seq of values:
95 |
96 |
97 | ```clojure
98 | (take 1 (gen/sample-seq gen/small-integer))
99 | ;; => (0)
100 | ```
101 |
102 | You may notice that as you ask for more values, the 'size' of the generated
103 | values increases. As test.check generates more values, it increases the
104 | 'size' of the generated values. This allows tests to fail early, for simple
105 | values, and only increase the size as the test continues to pass.
106 |
107 | ### Compound generators
108 |
109 | Some generators take other generators as arguments. For example the `vector`
110 | and `list` generator:
111 |
112 |
113 | ```clojure
114 | (gen/sample (gen/vector gen/nat))
115 | ;; => ([] [] [1] [1] [] [] [5 6 6 2 0 1] [3 7 5] [2 0 0 6 2 5 8] [9 1 9 3 8 3 5])
116 |
117 | (gen/sample (gen/list gen/boolean))
118 | ;; => (() () (false) (false true false) (false true) (false true true true) (true) (false false true true) () (true))
119 |
120 | (gen/sample (gen/map gen/keyword gen/boolean) 5)
121 | ;; => ({} {:z false} {:k true} {:v8Z false} {:9E false, :3uww false, :2s true})
122 | ```
123 |
124 | Sometimes we'll want to create heterogeneous collections. The `tuple` generator
125 | allows us to do this:
126 |
127 | ```clojure
128 | (gen/sample (gen/tuple gen/nat gen/boolean gen/ratio))
129 | ;; => ([0 false 0] [1 false 0] [0 false 2] [0 false -1/3] [1 true 2] [1 false 0] [2 false 3/5] [3 true -1] [3 true -5/3] [6 false 9/5])
130 | ```
131 |
132 | ### Generator combinators
133 |
134 | There are several generator combinators, we'll take a look at `fmap`,
135 | `such-that` and `bind`.
136 |
137 | #### fmap
138 |
139 | `fmap` allows us to create a new generator by applying a function to the
140 | values generated by another generator. Let's say we want to to create a set of
141 | natural numbers. We can create a set by calling `set` on a vector. So let's
142 | create a vector of natural numbers (using the `nat` generator), and then use
143 | `fmap` to call `set` on the values:
144 |
145 | ```clojure
146 | (gen/sample (gen/fmap set (gen/vector gen/nat)))
147 | ;; => (#{} #{1} #{1} #{3} #{0 4} #{1 3 4 5} #{0 6} #{3 4 5 7} #{0 3 4 5 7} #{1 5})
148 | ```
149 |
150 | Imagine you have a record, that has a convenience creation function, `foo`. You
151 | can create random `foo`s by generating the types of the arguments to `foo` with
152 | `tuple`, and then using `(fmap foo (tuple ...))`.
153 |
154 | #### such-that
155 |
156 | `such-that` allows us to create a generator that passes a predicate. Imagine we
157 | wanted to generate non-empty lists, we can use `such-that` to filter out empty
158 | lists:
159 |
160 | ```clojure
161 | (gen/sample (gen/such-that not-empty (gen/list gen/boolean)))
162 | ;; => ((true) (true) (false) (true false) (false) (true) (false false true true) (false) (true) (false))
163 | ```
164 |
165 | #### bind
166 |
167 | `bind` allows us to create a new generator based on the _value_ of a previously
168 | created generator. For example, say we wanted to generate a vector of keywords,
169 | and then choose a random element from it, and return both the vector and the
170 | random element. `bind` takes a generator, and a function that takes a value
171 | from that generator, and creates a new generator.
172 |
173 | ```clojure
174 | (def keyword-vector (gen/such-that not-empty (gen/vector gen/keyword)))
175 | (def vec-and-elem
176 | (gen/bind keyword-vector
177 | (fn [v] (gen/tuple (gen/elements v) (gen/return v)))))
178 |
179 | (gen/sample vec-and-elem 4)
180 | ;; => ([:va [:va :b4]] [:Zu1 [:w :Zu1]] [:2 [:2]] [:27X [:27X :KW]])
181 | ```
182 |
183 | This allows us to build quite sophisticated generators.
184 |
185 | ### Record generators
186 |
187 | Let's go through an example of generating random values of our own
188 | `defrecord`s. Let's create a simple user record:
189 |
190 | ```clojure
191 | (defrecord User [user-name user-id email active?])
192 |
193 | ;; recall that a helper function is automatically generated
194 | ;; for us
195 |
196 | (->User "reiddraper" 15 "reid@example.com" true)
197 | ;; #user.User{:user-name "reiddraper",
198 | ;; :user-id 15,
199 | ;; :email "reid@example.com",
200 | ;; :active? true}
201 | ```
202 |
203 | We can use the `->User` helper function to construct our user. First, let's
204 | look at the generators we'll use for the arguments. For the user-name, we can
205 | just use an alphanumeric string, user IDs will be natural numbers, we'll
206 | construct our own simple email generator, and we'll use booleans to denote
207 | whether the user account is active. Let's write a simple email address
208 | generator:
209 |
210 | ```clojure
211 | (def domain (gen/elements ["gmail.com" "hotmail.com" "computer.org"]))
212 | (def email-gen
213 | (gen/fmap (fn [[name domain-name]]
214 | (str name "@" domain-name))
215 | (gen/tuple (gen/not-empty gen/string-alphanumeric) domain)))
216 |
217 | (last (gen/sample email-gen))
218 | ;; => "CW6161Q6@hotmail.com"
219 | ```
220 |
221 | To put it all together, we'll use `fmap` to call our record constructor, and
222 | `tuple` to create a vector of the arguments:
223 |
224 | ```clojure
225 | (def user-gen
226 | (gen/fmap (partial apply ->User)
227 | (gen/tuple (gen/not-empty gen/string-alphanumeric)
228 | gen/nat
229 | email-gen
230 | gen/boolean)))
231 |
232 | (last (gen/sample user-gen))
233 | ;; => #user.User{:user-name "kWodcsE2",
234 | ;; :user-id 1,
235 | ;; :email "r2ed3VE@computer.org",
236 | ;; :active? true}
237 | ```
238 |
239 | ### Recursive generators
240 |
241 | ---
242 | NOTE: Writing recursive generators was significantly simplified in version
243 | 0.5.9. For the old way, see the [0.5.8
244 | documentation](https://github.com/clojure/test.check/blob/v0.5.8/doc/intro.md#recursive-generators).
245 |
246 | ---
247 |
248 | Writing recursive, or tree-shaped generators is easy using `gen/recursive-gen`.
249 | `recursive-gen` takes two arguments, a compound generator, and a scalar
250 | generator. We'll start with a simple example, and then move into something more
251 | complex. First, let's generate a nested vector of booleans. So our compound
252 | generator will be `gen/vector` and our scalar will be `gen/boolean`:
253 |
254 | ```clojure
255 | (def nested-vector-of-boolean (gen/recursive-gen gen/vector gen/boolean))
256 | (last (gen/sample nested-vector-of-boolean 20))
257 | ;; => [[[true] true] [[] []]]
258 | ```
259 |
260 | Now, let's make our own, JSON-like generator. We'll allow `gen/list` and
261 | `gen/map` as our compound types and `gen/small-integer` and `gen/boolean` as our scalar
262 | types. Since `recursive-gen` only accepts one of each type of generator, we'll
263 | combine our compound types with a simple function, and the two scalars with
264 | `gen/one-of`.
265 |
266 | ```clojure
267 | (def compound (fn [inner-gen]
268 | (gen/one-of [(gen/list inner-gen)
269 | (gen/map inner-gen inner-gen)])))
270 | (def scalars (gen/one-of [gen/small-integer gen/boolean]))
271 | (def my-json-like-thing (gen/recursive-gen compound scalars))
272 | (last (gen/sample my-json-like-thing 20))
273 | ;; =>
274 | ;; (()
275 | ;; {(false false) {true -3, false false, -7 1},
276 | ;; {4 -11, 1 -19} (false),
277 | ;; {} {1 6}})
278 | ```
279 |
280 | And we see we got a list whose first element is the empty list. The second
281 | element is a map with int keys and values. Etc.
282 |
283 | ## More Examples
284 |
285 | Let's say we're testing a sort function. We want to check that that our sort
286 | function is idempotent, that is, applying sort twice should be equivalent to
287 | applying it once: `(= (sort a) (sort (sort a)))`. Let's write a quick test to
288 | make sure this is the case:
289 |
290 | ```clojure
291 | (require '[clojure.test.check :as tc])
292 | (require '[clojure.test.check.generators :as gen])
293 | (require '[clojure.test.check.properties :as prop #?@(:cljs [:include-macros true])])
294 |
295 | (def sort-idempotent-prop
296 | (prop/for-all [v (gen/vector gen/small-integer)]
297 | (= (sort v) (sort (sort v)))))
298 |
299 | (tc/quick-check 100 sort-idempotent-prop)
300 | ;; => {:result true,
301 | ;; => :pass? true,
302 | ;; => :num-tests 100,
303 | ;; => :time-elapsed-ms 28,
304 | ;; => :seed 1528580707376}
305 | ```
306 |
307 | In prose, this test reads: for all vectors of integers, `v`, sorting `v` is
308 | equal to sorting `v` twice.
309 |
310 | What happens if our test fails? _test.check_ will try and find 'smaller'
311 | inputs that still fail. This process is called shrinking. Let's see it in
312 | action:
313 |
314 | ```clojure
315 | (def prop-sorted-first-less-than-last
316 | (prop/for-all [v (gen/not-empty (gen/vector gen/small-integer))]
317 | (let [s (sort v)]
318 | (< (first s) (last s)))))
319 |
320 | (tc/quick-check 100 prop-sorted-first-less-than-last)
321 | ;; => {:num-tests 5,
322 | ;; => :seed 1528580863556,
323 | ;; => :fail [[-3]],
324 | ;; => :failed-after-ms 1,
325 | ;; => :result false,
326 | ;; => :result-data nil,
327 | ;; => :failing-size 4,
328 | ;; => :pass? false,
329 | ;; => :shrunk
330 | ;; => {:total-nodes-visited 5,
331 | ;; => :depth 2,
332 | ;; => :pass? false,
333 | ;; => :result false,
334 | ;; => :result-data nil,
335 | ;; => :time-shrinking-ms 1,
336 | ;; => :smallest [[0]]}}
337 | ```
338 |
339 | This test claims that the first element of a sorted vector should be less-than
340 | the last. Of course, this isn't true: the test fails with input `[-3]`, which
341 | gets shrunk down to `[0]`, as seen in the output above. As your test functions
342 | require more sophisticated input, shrinking becomes critical to being able
343 | to understand exactly why a random test failed. To see how powerful shrinking
344 | is, let's come up with a contrived example: a function that fails if it's
345 | passed a sequence that contains the number 42:
346 |
347 | ```clojure
348 | (def prop-no-42
349 | (prop/for-all [v (gen/vector gen/small-integer)]
350 | (not (some #{42} v))))
351 |
352 | (tc/quick-check 100 prop-no-42)
353 | ;; => {:num-tests 45,
354 | ;; => :seed 1528580964834,
355 | ;; => :fail
356 | ;; => [[-35 -9 -31 12 -30 -40 36 36 25 -2 -31 42 8 31 17 -19 3 -15 44 -1 -8 27 16]],
357 | ;; => :failed-after-ms 11,
358 | ;; => :result false,
359 | ;; => :result-data nil,
360 | ;; => :failing-size 44,
361 | ;; => :pass? false,
362 | ;; => :shrunk
363 | ;; => {:total-nodes-visited 16,
364 | ;; => :depth 5,
365 | ;; => :pass? false,
366 | ;; => :result false,
367 | ;; => :result-data nil,
368 | ;; => :time-shrinking-ms 1,
369 | ;; => :smallest [[42]]}}
370 | ```
371 |
372 | We see that the test failed on a rather large vector, as seen in the `:fail`
373 | key. But then _test.check_ was able to shrink the input down to `[42]`, as
374 | seen in the keys `[:shrunk :smallest]`.
375 |
376 | To learn more, check out the [documentation](#documentation) links.
377 |
378 | ### `clojure.test` Integration
379 |
380 | The macro `clojure.test.check.clojure-test/defspec` allows you to succinctly
381 | write properties that run under the `clojure.test` runner, for example:
382 |
383 | ```clojure
384 | (defspec first-element-is-min-after-sorting ;; the name of the test
385 | 100 ;; the number of iterations for test.check to test
386 | (prop/for-all [v (gen/not-empty (gen/vector gen/small-integer))]
387 | (= (apply min v)
388 | (first (sort v)))))
389 | ```
390 |
391 | ### ClojureScript
392 |
393 | ClojureScript support was added in version `0.7.0`.
394 |
395 | Integrating with `cljs.test` is via the
396 | `clojure.test.check.clojure-test/defspec` macro, in the same fashion
397 | as integration with `clojure.test` on the jvm.
398 |
399 | ---
400 |
401 | Check out [page two](generator-examples.md) for more examples of using
402 | generators in practice.
403 |
--------------------------------------------------------------------------------
/doc/growth-and-shrinking.md:
--------------------------------------------------------------------------------
1 | # Growth and Shrinking
2 |
3 | Sizing in test.check seems simple at first glance, but there are
4 | subtleties that are important to understand to ensure that your tests
5 | are covering what you expect them to, and that failing cases can
6 | shrink in an effective way.
7 |
8 | It's useful to keep in mind that the way test.check controls the
9 | "size" of the data it generates is entirely different from how it
10 | shrinks failing examples, and we'll cover both of these processes
11 | below.
12 |
13 | ## Growth
14 |
15 | ### The `size` Parameter
16 |
17 | Internally, a generator cannot produce a value without specifying what
18 | `size` the value should be. This is what allows test.check to start a
19 | test run by trying very simple values first, and gradually trying
20 | larger and larger values. The meaning of `size` depends on the
21 | generator; some generators ignore it altogether.
22 |
23 | You can see how `size` affects different generators by experimenting
24 | with the `gen/generate` function, which takes an optional `size`
25 | argument:
26 |
27 | ``` clojure
28 | (defn sizing-sample
29 | [g]
30 | (into {}
31 | (for [size [0 5 25 200]]
32 | [size
33 | (repeatedly 5 #(gen/generate g size))])))
34 |
35 | ;; with gen/nat, the integer is roughly proportional to the `size`
36 | (sizing-sample gen/nat)
37 | ;; => {0 (0 0 0 0 0),
38 | ;; 5 (4 1 3 3 5),
39 | ;; 25 (12 8 24 25 22),
40 | ;; 200 (63 143 31 199 7)}
41 |
42 | ;; with gen/large-integer, the integer can be much larger
43 | (sizing-sample gen/large-integer)
44 | ;; => {0 (-1 0 -1 -1 -1),
45 | ;; 5 (1 6 7 4 2),
46 | ;; 25 (-1 55798 23198 -11 6124159),
47 | ;; 200 (8371567737
48 | ;; -393642130983883
49 | ;; -56826587109
50 | ;; 114071285698586153
51 | ;; 5723723802814291)}
52 |
53 | ;; a collection generator grows the collection size and the
54 | ;; size of its elements
55 | (dissoc (sizing-sample (gen/vector gen/nat)) 200)
56 | ;; => {0 ([] [] [] [] []),
57 | ;; 5 ([1 0 4 3 4] [3] [] [2 0] [2 2 4]),
58 | ;; 25 ([8 10 0 16 7 7 2 19 16 10]
59 | ;; [16 8 0 18 11 23 11 7 1 19 10 4 0 23 17 2 17 12 1 20]
60 | ;; [15 19 7 6 4]
61 | ;; [6 5 14 12 19 12 7 13 17 10 16 6 9 1]
62 | ;; [19 16 5 22 2 5 10 3 6 7 22 19 21 10 4 22 23 5 9 21 19 16 2])}
63 |
64 | ;; unless we fix the size of the collection
65 | (sizing-sample (gen/vector gen/large-integer 3))
66 | ;; => {0 ([-1 -1 -1] [-1 0 -1] [-1 0 -1] [0 0 -1] [0 0 -1]),
67 | ;; 5 ([-10 -1 -1] [4 -14 15] [2 2 -1] [-3 -7 2] [-2 0 0]),
68 | ;; 25 ([-4417 32 189]
69 | ;; [12886 576 -2]
70 | ;; [0 -2 -89799]
71 | ;; [108 -250318 -1218212]
72 | ;; [-10 -27 -5]),
73 | ;; 200 ([-8526639064861 -44 2311]
74 | ;; [819670069072907 -4481451104804003250 -81]
75 | ;; [-1985273 781374 -480118376]
76 | ;; [-2038 2593 -5355]
77 | ;; [143974988 4 209260326382094708])}
78 |
79 | ;; gen/uuid completely ignores `size`
80 | (sizing-sample gen/uuid)
81 | ;; => {0 (#uuid "29ec3e6f-e35c-466f-b9d5-fa27e043743d"
82 | ;; #uuid "7bb1c53d-0b12-4be0-a2c5-b7d6a406f64f"
83 | ;; #uuid "8f07cab1-4e3d-4bd1-a699-6d653b353588"
84 | ;; #uuid "b2e65dcb-fad1-4f1e-afe6-5645d1759a9d"
85 | ;; #uuid "83d9ca17-cc07-4515-bf22-625e1e537943"),
86 | ;; 5 (#uuid "f1a35527-128a-4cda-b9ba-d0fec4255674"
87 | ;; #uuid "f7a7f621-5e84-4d09-b3e3-849bebfea048"
88 | ;; #uuid "d92aa9f9-7be6-4e02-80a7-ab89d9074b48"
89 | ;; #uuid "c5f24f29-1472-454a-9171-34a2d74074b1"
90 | ;; #uuid "c14ac2f6-a31a-4c0b-8e50-273feac6dbda"),
91 | ;; 25 (#uuid "008a8dcb-11b1-41cc-87b7-5b7b1b704e8e"
92 | ;; #uuid "f89c245a-7667-4d2f-9a88-b33c92ad09ca"
93 | ;; #uuid "dc209d21-cfbc-4e3e-a338-7a8b146084b4"
94 | ;; #uuid "c9585173-a4f5-4f3b-9a98-7eecc4801007"
95 | ;; #uuid "b10196fb-9cdb-4148-8d99-dea80be6d7fa"),
96 | ;; 200 (#uuid "b9a70100-4b02-404b-9de4-611ad5a2cefe"
97 | ;; #uuid "32ca9f24-3248-476a-ab9f-d58c9aa5df9c"
98 | ;; #uuid "9de09d09-0121-4ffe-b7a7-37cb95191bcb"
99 | ;; #uuid "bcaeeaf8-4225-40d9-a2b4-7ba2c417a3f0"
100 | ;; #uuid "6153d4e4-038c-4e73-be0c-2394b3078e25")}
101 | ```
102 |
103 | ### How `size` changes over a test run
104 |
105 | `clojure.test.check/quick-check` generates the input for the first
106 | trial using `size=0`, for the second trial it uses `size=1`, and
107 | continues incrementing until the 200th trial with `size=199`, after
108 | which it starts over. In general it uses `(cycle (range 200))`.
109 |
110 | Test.check starts with small sizes so that it will catch easy bugs
111 | quickly without needing to generate very large input and then shrink
112 | it, and so that edge cases produced by small sizes have a good chance
113 | of being caught. Custom generators that ignore the `size` parameter
114 | are thwarting this feature.
115 |
116 | Also see the warning about small test counts below.
117 |
118 | ### Controlling `size`
119 |
120 | Custom generators can use and modify `size` in several different ways.
121 |
122 | #### `gen/sized`
123 |
124 | `gen/sized` is essentially a facility for "reading" the size as you
125 | create a generator.
126 |
127 | ``` clojure
128 | (def g
129 | (gen/sized
130 | (fn [size]
131 | (gen/let [x gen/large-integer]
132 | (format "I generated %d using size=%d!" x size)))))
133 |
134 | (gen/sample g)
135 | ;; => ("I generated -1 using size=0!"
136 | ;; "I generated 0 using size=1!"
137 | ;; "I generated -1 using size=2!"
138 | ;; "I generated 0 using size=3!"
139 | ;; "I generated -1 using size=4!"
140 | ;; "I generated 2 using size=5!"
141 | ;; "I generated 0 using size=6!"
142 | ;; "I generated -2 using size=7!"
143 | ;; "I generated 12 using size=8!"
144 | ;; "I generated -2 using size=9!")
145 | ```
146 |
147 | #### `gen/resize`
148 |
149 | `gen/resize` lets you pin a generator to a particular `size`
150 |
151 | ``` clojure
152 | (def g
153 | (gen/sized
154 | (fn [size]
155 | (gen/let [x (gen/resize 100 gen/large-integer)]
156 | (format "I generated %d, even though size=%d!" x size)))))
157 |
158 | (gen/sample g)
159 | ;; => ("I generated 2047953455, even though size=0!"
160 | ;; "I generated -126726750629, even though size=1!"
161 | ;; "I generated 50066179923, even though size=2!"
162 | ;; "I generated 2078170872141134, even though size=3!"
163 | ;; "I generated 678227175, even though size=4!"
164 | ;; "I generated 3858768648, even though size=5!"
165 | ;; "I generated -23231577, even though size=6!"
166 | ;; "I generated 4, even though size=7!"
167 | ;; "I generated 3503438568408, even though size=8!"
168 | ;; "I generated 186422559275, even though size=9!")
169 | ```
170 |
171 | #### `gen/scale`
172 |
173 | `gen/scale` is a convenient way to modify the `size` that a generator
174 | sees (which you could do more tediously by combining `gen/sized` and
175 | `gen/resize`).
176 |
177 | ``` clojure
178 | (def gen-small-vectors-of-large-numbers
179 | (gen/scale #(max 0 (Math/log %))
180 | (gen/vector (gen/scale #(* % 100) gen/large-integer))))
181 |
182 | (gen/sample gen-small-vectors-of-large-numbers 20)
183 | ;; => ([]
184 | ;; []
185 | ;; []
186 | ;; [234236101]
187 | ;; [34663197938259]
188 | ;; [-15]
189 | ;; [87]
190 | ;; []
191 | ;; []
192 | ;; [-5310368659078251]
193 | ;; [-8403929563691 126041240]
194 | ;; []
195 | ;; []
196 | ;; []
197 | ;; []
198 | ;; [-84306261785]
199 | ;; [35060841580649472 45255404980]
200 | ;; []
201 | ;; [61658595345 277549824780866555]
202 | ;; [])
203 | ```
204 |
205 | ### Gotchas
206 |
207 | #### Integer generators
208 |
209 | Test.check originally contained six integer generators which are
210 | all variants of the same thing:
211 |
212 | ``` clojure
213 | (gen/sample (gen/tuple gen/nat gen/int
214 | gen/pos-int gen/neg-int
215 | gen/s-pos-int gen/s-neg-int))
216 |
217 | ;; => ([0 0 0 0 1 -1]
218 | ;; [1 1 0 0 1 -2]
219 | ;; [0 2 0 -1 2 -2]
220 | ;; [0 0 2 -2 2 -3]
221 | ;; [3 -2 2 -2 5 -2]
222 | ;; [3 -4 3 -2 5 -2]
223 | ;; [0 0 1 -2 5 -4]
224 | ;; [6 5 3 -6 2 -4]
225 | ;; [1 -2 8 -5 4 -5]
226 | ;; [4 -6 2 -9 8 -2])
227 | ```
228 |
229 | Besides the confusing names, the big gotcha is that the range of these
230 | generators is is more or less strictly bounded by `size`, and so any
231 | use of them will by default not test numbers bigger than `200`, which
232 | is unacceptable coverage for a lot of
233 | applications. `gen/large-integer` should avoid this issue. Most of the
234 | small integer generators have been deprecated, with the exception of
235 | `gen/nat` and the new-and-less-confusingly-named `gen/small-integer`.
236 |
237 | #### Small Test Count
238 |
239 | Due to the use of `(cycle (range 200))` as the `size` progression
240 | during a test run (described above), tests that use less than 200
241 | trials will not be exposed to the normal range of sizes, and in
242 | particular tests that run less than ~10 trials will be getting very
243 | poor coverage.
244 |
245 | If you don't want to run very many trials for some reason, you can
246 | mitigate this with `gen/scale`; e.g.:
247 |
248 | ``` clojure
249 | ;; uses sizes 0,20,40,60,80,100,120,140,160,180
250 | (tc/quick-check 10
251 | (prop/for-all [x (gen/scale #(* 20 %) g)]
252 | (f x)))
253 | ```
254 |
255 | #### `gen/sample`
256 |
257 | `gen/sample` starts with very small sizes in the same way that the
258 | `quick-check` function does. This can be misleading to users who don't
259 | expect that and take the first ten results from `gen/sample` to be
260 | representative of the distribution of a generator. Using `gen/generate`
261 | with an explicit `size` argument can be a better way of learning about
262 | the distribution of a generator.
263 |
264 | #### Collection composition
265 |
266 | _See [TCHECK-106](https://clojure.atlassian.net/browse/TCHECK-106)_
267 |
268 | test.check's collection generators by default select a size for the
269 | generated collection that is proportional to the `size` parameter.
270 |
271 | This generally works well enough, but when creating generators of
272 | nested collections it can lead to Very Large output, in the worst
273 | case exhausting available memory.
274 |
275 | ``` clojure
276 | (defn max-size
277 | [colls]
278 | (->> colls (map flatten) (map count) (apply max)))
279 |
280 | (-> gen/nat
281 | (gen/vector)
282 | (gen/vector)
283 | (gen/sample 200)
284 | (max-size))
285 | ;; => 4747
286 |
287 | (-> gen/nat
288 | (gen/vector)
289 | (gen/vector)
290 | (gen/vector)
291 | (gen/sample 200)
292 | (max-size))
293 | ;; => 195635
294 | ```
295 |
296 | This can be mitigated with strategic resizing.
297 |
298 | ## Shrinking
299 |
300 | Despite the conceptual similarity, the shrinking algorithm has nothing
301 | to do with the `size` parameter. `size` affects the distribution of a
302 | random process, while shrinking is entirely deterministic and based on
303 | the properties of the basic generators and the combinators.
304 |
305 | ### Gotchas
306 |
307 | #### Unnecessary `bind`
308 |
309 | _See [TCHECK-112](http://dev.clojure.org/jira/browse/TCHECK-112)_
310 |
311 | `gen/bind` (and multi-clause uses of `gen/let`) is a powerful
312 | combinator that allows you to combine generators in "phases", where
313 | the later generators can make use of values generated in earlier
314 | generators. This can be very useful, but it also is difficult to
315 | shrink in a general way (see the details in the jira ticket linked
316 | above for an example of this).
317 |
318 | This means that if you care about the effectiveness of shrinking, it
319 | can be worth taking care not to use `gen/bind` where you don't have
320 | to.
321 |
322 | For example, say you wanted to generate a collection of an even number
323 | of integers. You might think to do this by first generating an even
324 | number for the length, and then using that with `gen/vector`:
325 |
326 | ``` clojure
327 | (def gen-an-even-number-of-integers
328 | (gen/let [even-number (gen/fmap #(* 2 %) gen/nat)]
329 | (gen/vector gen/large-integer even-number))
330 | ;; or, rewritten without gen/let:
331 | #_
332 | (gen/bind (gen/fmap #(* 2 %) gen/nat)
333 | (fn [even-number]
334 | (gen/vector gen/large-integer even-number))))
335 |
336 | (def gen-strictly-increasing-integers
337 | (gen/bind gen/nat #(gen-strictly-increasing-integers* % 0)))
338 |
339 | (gen/sample gen-an-even-number-of-integers)
340 | ;; => ([]
341 | ;; [-1 -1]
342 | ;; []
343 | ;; []
344 | ;; [-1 -4 0 -1 -2 2]
345 | ;; [0 2]
346 | ;; [6 -1 1 4 8 30 -2 0 21 -2 -1 0]
347 | ;; [2 -2]
348 | ;; [0 -1 7 -33 9 -49 14 14 1 1 -1 0 1 -2]
349 | ;; [0 2 -1 -9])
350 | ```
351 |
352 | It looks okay, but we can see a problem when shrinking
353 |
354 | ``` clojure
355 | (tc/quick-check
356 | 10000
357 | (prop/for-all [xs gen-an-even-number-of-integers]
358 | (not-any? #{42} xs)))
359 |
360 | ;; => {:result false,
361 | ;; :seed 1482063539636,
362 | ;; :failing-size 176,
363 | ;; :num-tests 177,
364 | ;; :fail [[-92431438766962 63530 -164135493216497125 -3270829858185774102
365 | ;; -260351529 -59352395648111 -4 -17469 -31636041044035
366 | ;; 7336711261875630 -1636343167264 -20912505735276 -23753842660
367 | ;; 13368897139830488 -1 -250220724 24370059524 -8266208340
368 | ;; 949778971431 -2233935110 -10 -226980 -166150097914784515
369 | ;; 1446375390291034 17977873032 -306481593932634684 2 321887
370 | ;; 1535082621176844 24757631603 -15034747392805020 -248163661633
371 | ;; -2272021814312959965 -1045247795284 177163345 13467
372 | ;; -355036687887336641 -4098005768175 -8055 -6317647 133903089
373 | ;; 3881 42630713210694061 -2673915744452669 421802903098966
374 | ;; -34741965 1630280301 231213827 858102836152006 5282
375 | ;; -269037059 -4985695680423 -187884359879 -68958514179
376 | ;; 1356369075861 -1 5701573467 -9 3993 -66360585914444
377 | ;; 1796329244719094 -9139976096708138 -11216 908965 17156900
378 | ;; 5559124946 13403 -2345413999 42 1 -76248253307297 222887742816784
379 | ;; 1274360 -68929 1 -213900 -122103507959521 2767011893757957
380 | ;; -3626024977 84758031 461767131016 -122390014709033 -1052250928741535
381 | ;; 1 383 -575550 -8793837628976134 -540423902910181208
382 | ;; 7896218 -49725987 -68869268253 -470133169 -7407245227931
383 | ;; -2266127667584039 -60700760 7759 14242030181 -565807123157122480
384 | ;; -21599378358624 1000368132 -1 109045164 23447410579428773
385 | ;; 1966123182 949341425 16444393 60598 340542 -187842543295
386 | ;; 3676708478 -236529145197024202 -791408585920527 -3452127625272781
387 | ;; -132208027103 25 -17500698053396417 -3375613232 -88206409961854
388 | ;; -3368 7 -179071081209 16894761949763 -132946664 -30990191248478947
389 | ;; 402283570687771 29732288327985 -6211 -885340544041821
390 | ;; -2134764587 -16103 518432298883356507 -30801 -311015444053486
391 | ;; -52408941698 -2282761018048237612 438556242]],
392 | ;; :shrunk {:total-nodes-visited 97,
393 | ;; :depth 72,
394 | ;; :result false,
395 | ;; :smallest [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
396 | ;; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
397 | ;; 0 0 0 0 0 0 0 0 0 0 0 0 42 0]]}}
398 | ```
399 |
400 | Test.check wasn't able to shrink the collection to the optimal size (2
401 | elements) because of the structure of the generators (though it did
402 | manage to shrink from 136 to 70 elements). `gen/vector` is not able to
403 | remove elements from the vector when shrinking because it was called
404 | with a fixed size. So the only way to shrink the size of the vector is
405 | to shrink the value from `(gen/fmap #(* 2 %) gen/nat)`. This is one of
406 | the things that `gen/bind` tries, but when it shrinks the
407 | `even-number`, it has no choice but to create an entirely new
408 | generator from `gen-vector` and generate a fresh value that's
409 | unrelated to the original failing collection. This fresh value is
410 | highly unlikely to also fail, and so that part of the shrinking
411 | algorithm is unlikely to be very fruitful (though sometimes it works,
412 | like in the example above where we were lucky to reduce the size from
413 | 136 to 70).
414 |
415 | Sometimes there are natural ways to write a generator that do not use
416 | `gen/bind`. For example, in this case we could use `gen/vector`
417 | without a fixed size, and modify it with `gen/fmap` to ensure it has
418 | an even number of elements:
419 |
420 | ``` clojure
421 | (def gen-an-even-number-of-integers
422 | (gen/let [xs (gen/vector gen/large-integer)]
423 | (cond-> xs (odd? (count xs)) pop))
424 | ;; or, rewritten without gen/let:
425 | #_
426 | (gen/fmap (fn [xs] (cond-> xs (odd? (count xs)) pop))
427 | (gen/vector gen/large-integer)))
428 |
429 | (gen/sample gen-an-even-number-of-integers)
430 | ;; => ([]
431 | ;; []
432 | ;; []
433 | ;; [0 0]
434 | ;; [-6 0]
435 | ;; [0 6]
436 | ;; [22 -2 -2 0]
437 | ;; []
438 | ;; [0 -2 15 95]
439 | ;; [0 -7 -205 -9])
440 |
441 | ;; => {:result false,
442 | ;; :seed 1482064393801,
443 | ;; :failing-size 160,
444 | ;; :num-tests 161,
445 | ;; :fail [[-20 3758 -174 2908907278 7767028 6628657334113049 -7409556399
446 | ;; -3379156667294 -473722 760549137 -7137938397056 124401939
447 | ;; 1590227088 174 482329 4972338 -53955167617312 -237816
448 | ;; 1 -13159175 2 1911311087865 -2675112025 -391133804902
449 | ;; -1444282617174675 1477509406066 138075 -3555024567808
450 | ;; 0 -26579022516 0 5182 -82958251 -1287 -35417824257454314
451 | ;; -129794819488 42 1642761942897 975833887255494324 701657767868417
452 | ;; 3940940 2 458 -1337864380187855428 -6716451 23621121
453 | ;; 1 -826778832808 -2 -137892 6996928807632 -1 -506146269826334582
454 | ;; -23783 -419873644169 3928808977969 0 -3595621317791
455 | ;; -66706208260298 -13099314 10721686280827793 -50904466
456 | ;; -6134528453735 24779423757 -43 1042490490 134213823314 -29]],
457 | ;; :shrunk {:total-nodes-visited 115, :depth 68, :result false, :smallest [[42 0]]}}
458 | ```
459 |
--------------------------------------------------------------------------------
/doc/old-confluence-notes.md:
--------------------------------------------------------------------------------
1 | # Old Confluence Notes
2 |
3 | These notes were hastily exported from dev.clojure.org on 2019-04-24.
4 |
5 | Formatting was mostly preserved, except that bulleted lists ended up
6 | as preformatted text.
7 |
8 | ## Main Page
9 |
10 | Some old and largely outdated design ideas for test.check.
11 |
12 |
13 | ### API Overhaul Ideas
14 |
15 | Responding to TCHECK-99 got me thinking about trying to pull off a
16 | complete redesign of the (mostly generators) API, while maintaining as
17 | much backwards compatibility as possible, at least for a few versions.
18 |
19 | I'm going to try to list here the sorts of problems that could be
20 | fixed with such an overhaul that would be hard to fix otherwise.
21 |
22 | Generators
23 | Convert all generators to be functions; if a default generator is available, it would be obtained by calling the function with 0 args
24 | Removes confusion such as with the current pair of large-integer and large-integer*
25 | Get rid of nat, pos-int, s-pos-int, and similar. Replace with a generator called small-integer that takes various options
26 | rename large-integer to integer, consider whether to allow bigints
27 | change gen/vector and gen/list APIs to match gen/set
28 | Maybe allow passing options when calling a generator, so that generators like strings and doubles can be customizable in a different way
29 | e.g., this way a generator like gen/any could support being customized to never generate NaNs anywhere without having to explicitly code for that
30 | clojure-test
31 | change defspec to use clojure.test/is
32 | can maybe do this w/o breaking changes by putting an alternative to prop/for-all in the clojure-test namespace???
33 | Properties
34 |
35 | ### Numerics
36 |
37 | The situation here used to be much worse, when all of the integer
38 | generators would only generate numbers up to ~200 by default, and no
39 | double generator existed at all. Now we have gen/large-integer which
40 | does well for large ranges of integers, and gen/double which is also
41 | pretty good. The remaining holes are relatively minor:
42 |
43 | numbers from infinite sets
44 | a generator for bigints; unclear if this would require bounds or not
45 | a good generator for ratios; again unclear about bounds, both on abs value and on denominator size
46 | bigdecimals
47 | some lower-level utility generators
48 | a generator for doubles between 0 and 1
49 | a raw generator of evenly-distributed longs
50 |
51 | #### What is a solid approach to generating infinite-domain types?
52 |
53 | In particular bigints, ratios, bigdecimals, and maybe even collections.
54 |
55 | One idea I had was a generator that always has a non-zero probability of generating any given value, and the size parameter determines the average size of the thing generated (e.g., in bits). I think this means that the size of the output, at least to start, would have a geometric distribution.
56 |
57 | #### How should NaNs be handled?
58 |
59 | ##### Background
60 |
61 | gen/double and gen/double* generate NaN, at least by default. This is intentional, since NaN is a useful edge case to test for any code that deals with doubles. However NaN != NaN, which means that any data structure containing a NaN is not equal to itself, which screws up all sorts of things.
62 |
63 | This isn't such a problem for gen/double, since users can opt-out of NaNs with gen/double* when appropriate. However, gen/simple-type, gen/simple-type-printable, gen/any, and gen/any-printable are all affected by this in 0.9.0.
64 |
65 | ##### Possible Approaches
66 |
67 | Modify generators in-place
68 | Just the four composite generators (gen/simple-type{-printable} and gen/any{-printable})
69 | gen/double doesn't generate NaN, and gen/double* allows it as opt-in
70 | Add new generators
71 | Analogues of the four composite, or some subset (gen/simple-type-no-NaN, gen/any-no-NaN, etc.)
72 | Something else?
73 | arguably users can manage this on their own, although excluding NaN from gen/any is not so simple, since a naïve wrapping in gen/such-that won't work
74 |
75 | ### Test Runner Topics
76 | #### Test Failure Feedback
77 |
78 | Currently a test error (i.e., exception caught) gives ample feedback
79 | because the user gets the exception object to inspect, but a mere
80 | failure (falsy return value) doesn't communicate anything other than
81 | whether the result was false or nil. clojure.test goes farther for
82 | this, with a probably-overly-macrocentric approach.
83 |
84 | Probably the best approach is to evaluate the result of a property
85 | expression with a protocol, so the current behavior can be maintained
86 | but new implementations can provide more information.
87 |
88 | I think reid has already started in this direction on the "feature/result-map" branch
89 |
90 | Related: https://github.com/yeller/matcha and this alternate clojure.test integration.
91 |
92 | #### Parallel Tests
93 |
94 | Requires the immutable RNG for full determinism.
95 |
96 | Do we actually need complex coordination of threads or can we do a naive thing?
97 |
98 | #### Rerunning a single test easily
99 |
100 | #### Running tests for a specific amount of time
101 |
102 | Related: test.chuck/times, and a branch that Tom Crayford worked on
103 |
104 | ### Some ideas from Hypothesis
105 |
106 | From this (http://www.drmaciver.com/2015/01/using-multi-armed-bandits-to-satisfy-property-based-tests/) blog post primarily. The library itself is here.
107 |
108 | It essentially uses a rather more sophisticated approach to getting more interesting distributions.
109 |
110 | Idea for trying this out: have each generator keep track of how many
111 | size parameters it can accept. E.g., gen/int takes one size
112 | parameter. (gen/list gen/int) takes two (one for the size of the list,
113 | one for the size given to gen/int). (gen/tuple g1 g2 g3) takes the sum
114 | of the size-parameter-counts for its args. This breaks down when using
115 | bind and recursive generators, so we might have to fall back on just
116 | passing an infinite number of size parameters, or using the RNG to
117 | determine them, or something like that.
118 |
119 | On the other hand, I spoke to David directly about this feature and he
120 | says he regrets including it in hypothesis due to the high complexity
121 | and low value. I didn't ask why he thought it was low value.
122 |
123 | ### Orchestrating Concurrent (and distributed?) Programs
124 |
125 | The point being to test them deterministically, and to test different
126 | possible linearizations to look for concurrency bugs.
127 |
128 | ### Testing Generators, Regression Tests
129 |
130 | Several related topics:
131 |
132 | How do we validate that a generator is likely to generate certain kinds of things?
133 | How could we collect stats about the sorts of things generated?
134 | When we find a rare failing test, is there an easy way to ensure that such cases are generated more often?
135 |
136 | Need to research other QC impls.
137 |
138 | ### Generator Syntax Sugar
139 |
140 | We ended up adding gen/let for this, but test.chuck/for is still fancier and especially useful for tuples.
141 |
142 | ### Shrinking Topics
143 |
144 | Check other QC impls for all of this.
145 |
146 | #### Can we use more specialized generators to minimize the need for bind?
147 |
148 | There was an example somewhere of trying to generate a 2d vector,
149 | where doing it without bind seemed impossible but bind caused it to
150 | shrink poorly.
151 |
152 | Also see "Stateful Generators" elsewhere on this page.
153 |
154 | #### Should shrink-resumption be part of the API?
155 |
156 |
157 | #### Custom shrinking algorithms?
158 |
159 |
160 | ### Advanced Generators
161 | #### "Stateful" Generators
162 |
163 | There are common use cases for being able to generate sequences of
164 | events, where each event can affect what future events are
165 | possible. Perhaps the most natural is modeling a collection of records
166 | with create/update/delete events, where you can only have an update or
167 | delete for a record that already exists (and no updates on a deleted
168 | record).
169 |
170 | This can be done pretty easily with a recursive generator that uses
171 | gen/bind at each step, but this comes at the huge expense of almost
172 | entirely thwarting the shrinking process. It would be great to devise
173 | some general way of constructing these sorts of generators that also
174 | shrinks well. This seems easier for special cases like the record-set
175 | case described above, but difficult in the general case.
176 |
177 | ##### Single Set of Independent Records
178 |
179 | A specialized generator that takes as input args similar to
180 | gen/hash-map (i.e., a description of what keys a record has and
181 | generators for each of the keys) and shrinks reasonably well should be
182 | possible to write, especially if there are no constraints beyond the
183 | ordering of the create/update/delete for individual records.
184 |
185 | There would be custom low-level generator code that avoids using
186 | gen/bind, paired with custom shrinking code. The shrinking part might
187 | only have to ensure that if a shrink removes a "create" event that it
188 | deletes all subsequent events for that record. There might also have
189 | to be a reified "id" that doesn't shrink or else shrinks all
190 | references to an id in unison.
191 |
192 | ##### Full Relational Dataset
193 |
194 | This would be like the previous example, but would involve multiple
195 | datasets ("tables") and relationships between them ("foreign
196 | keys"). The tricky part would be intelligent handling of foreign
197 | keys. If a record's relative disappears, the shrinking code would
198 | manually do cascading deletes when necessary (or perhaps divert
199 | foreign keys to other records when that makes sense).
200 |
201 | ##### General Problem
202 |
203 |
204 | The only natural framing of the general problem that I can think of is a generator such as:
205 |
206 | (defn gen-events
207 | "Given an init-state and a reduce function for determining the
208 | current state from a sequence of events, together with a function
209 | that takes a state and returns a generator of a new event, returns
210 | a generator of sequences of events."
211 | [reduce-func init-state state->event-gen]
212 | ...)
213 |
214 | It seems difficult to find a general way to layer smart shrinking on
215 | top of this sort of API, since any change to earlier events could
216 | potentially require arbitrary changes to future events, and I'm not
217 | sure how to build in a way of allowing the user to specify those
218 | changes.
219 |
220 | One idea is to take an additional argument, a function that receives
221 | an event that was previously generated and a new state that is the
222 | precursor to that event. The function can choose to leave the event
223 | as-is (if it's still valid in the new state), apply return a function
224 | suitable for use with fmap (if the event can be easily adjusted to the
225 | new state), or signal that the event should be removed altogether. I
226 | think this should be sufficient for implementing the two dataset
227 | examples above, but I'm not sure whether it works for more complex
228 | uses.
229 |
230 | ### Breaking Changes Fantasy
231 |
232 | If we decided to make a large breaking-changes release, what would we include?
233 |
234 | Rename the pos-int/s-pos-int style functions
235 | Change the arg order for bind? It's awkward that it's opposite fmap
236 | Change the multi-clause behavior of prop/for-all?
237 | Currently can't integrate test.chuck/for w/ prop/for-all support without breaking this
238 |
239 |
240 | ### Old/Obsolete Notes
241 |
242 | Things that are done or decided against.
243 |
244 | #### Immutable RNG
245 |
246 | I think we need to do some more performance checks. Ideas for things to look into:
247 |
248 | How does linear generation with java.util.SplittableRandom compare to JUR and IJUSR? Knowing this should give us an idea if the slowdown with IJUSR is more about the algorithm or immutability
249 | Seems to be entirely about immutability. E.g., JUSR is actually slightly faster with JUR, even after removing concurrency from JUR.
250 | Can we squeeze any extra performance by using a more specialized API anywhere?
251 | Like split-n
252 | This is looking like the most promising
253 | Can we generate batches of numbers faster than the more general splitting method? If so, can we take advantage of that cleanly in the generators?
254 | E.g., you could linearly generate 256 longs, put them in an immutable array, and have the RNG reference a range of that array. rand-long returns the first number in the range, splitting returns two objects with the range cut in half. Splitting a singleton range triggers another array creation. I think there are still some subtleties to work out though.
255 | It's also worth noting that the splittable API involves twice as many allocation as a linear-immutable API (see split-n idea above)
256 | Is the macro usage subverting inlining?
257 | Is the long->double->int process taking a while?
258 | Try using an unrolled pair (ztellman style) for the return value from split
259 |
260 | Some code for messing with this stuff is on this branch.
261 |
262 | ## Generators Reboot
263 |
264 | This is a list of problems that can be addressed to some extent by
265 | creating a new generators-2 namespace.
266 |
267 | For each problem, there is a proposed solution with and without adding
268 | a new namespace.
269 |
270 | Under every "WITHOUT Reboot" section, there is an implicit alternative
271 | to do nothing.
272 |
273 | Many generators have confusing names
274 | E.g., monadic names (return, fmap, bind), and others (elements, hash-map, one-of)
275 | WITH Reboot
276 | Pick better names
277 | WITHOUT Reboot
278 | Add aliases and mark the old names as deprecated
279 | Difference between a var referring to a generator vs a function that returns a generator can be error-prone for users, and leads to a duplication of names like gen/double and gen/double* to allow for options to be passed
280 | WITH Reboot
281 | Make every var a function that returns a generator
282 | WITHOUT Reboot
283 | Add an IGenerator protocol so that vars like gen/double can be backwards-compatibly changed to functions that can act as generators in a deprecated manner (with optional warnings?)
284 | Options API is inconsistent
285 | Some functions take an options map, others have similar options as positional args
286 | WITH Reboot
287 | every generator should take an options map, as an optional argument after all of the required args
288 | WITHOUT Reboot
289 | Modify signatures with backwards compatible support for the deprecated signatures
290 | Built-in composite generators like gen/any do not allow customizing the underlying base generators – most notably, you cannot ask gen/any to avoid generating NaNs (but with more options on other base generators this would be a more general problem)
291 | WITH Reboot
292 | These generators would be functions and could therefore pass options down to their constituent generators
293 | WITHOUT Reboot
294 | Do the "WITHOUT Reboot" steps in the previous two points, and then do the same thing we would do in a reboot
295 | Integer generators need overhauling
296 | There are a lot and some are misleadingly named. They could all be collapsed into a single generator with rich options for specifying the distribution
297 | WITH Reboot
298 | Make a single gen2/integer
299 | WITHOUT Reboot
300 | Make gen2/integer and deprecate all the others
301 | gen/bind has confusing argument order
302 | Every other combinator takes the generator as its last argument
303 | WITHOUT Reboot
304 | Accept args in either order? This could be ambiguous if we also convert everything to functions but allow the functions to be used as generators
305 | WITH Reboot
306 | Change the arg order in the new function
307 | String generators are pretty basic
308 | the non-ascii generators use a fixed small range of non-ASCII unicode characters
309 | WITHOUT Reboot
310 | Add a new string generator that takes options for distribution, deprecate all the old ones
311 | WITH Reboot
312 | Add a new string generator that takes options
313 | Collection sizing is bad
314 | Collection generations naïvely result in the expected size of the generated value being N times larger than the expected size from the element generator
315 | A better situation would perhaps involve
316 | Collection generators that reduce the `size` used for their elements according to some rule, perhaps based on the expected size of the collection
317 | Some sort of in-place accounting for how much stuff is being generated as it goes, so that the size can be suppressed further if the value is getting too big, mitigating the risk of the largest 0.001% of values being OOMly large
318 | Standard options for users to have more control of the above behavior
319 | WITHOUT Reboot
320 | Change built-in generators in place to effect the above solution (should this be considered a breaking change, if old calls still work but perhaps generate different distributions?)
321 | Create new collection generators according to the above solution and deprecate the old ones
322 | WITH Reboot
323 | New collection generators according to the above solution
324 |
325 | ## quickcheck refactoring
326 |
327 | This is a hopefully temporary page, tracking evaluation of [Nico's
328 | proposed refactoring](https://clojure.atlassian.net/browse/TCHECK-126)
329 | and any alternatives and related issues.
330 |
331 | Organized as (nested) list of questions.
332 |
333 | What problem are we trying to solve?
334 | A general change to allow an implementation of a variety of useful features (in test.check directly or by users/libraries):
335 | async tests
336 | statistics
337 | time-bounded runs, time-bounded shrinks
338 | more speculatively, pausing and resuming shrinks
339 | Note that we shouldn't assume we have to solve all these problems with the same changes, but if a change simultaneously enables all of them, that could be considered evidence in its favor
340 | How do other quickchecks do any of those things?
341 | At least
342 | Hedgehog
343 | Proper
344 | Optionally
345 | Quickcheck
346 | Hypothesis
347 | Nico's solution
348 | My general concern is that it changes the public API, so we have to get it right, and it seems to allow a lot of undefined/strange things, since the user's supplied function could return whatever it wants; e.g., it could return a structure that's inconsistent or sends the algorithm to an unexpected state
349 | I'm also wondering how it composes; e.g., if the stats library provides one implementation of the state transition function, and the user provides another for limiting shrink time, how do you combine those functions together? does `comp` work? is it commutative?
350 |
--------------------------------------------------------------------------------