├── 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 | ![YourKit](https://www.yourkit.com/images/yklogo.png) 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 | --------------------------------------------------------------------------------