diff --git a/deps.edn b/deps.edn
index d45a9704..25faf037 100644
--- a/deps.edn
+++ b/deps.edn
@@ -55,10 +55,7 @@
:extra-deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"}
org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha"
:sha "0dec1f88cbde74a0470b454396f09a03adb4ae39"}
- lambdaisland/regal {:git/url "https://github.com/lambdaisland/regal"
- :sha "f902d2c43121f9e1c48603d6eb99f5900eb6a9f6"}
- weavejester/medley {:git/url "https://github.com/weavejester/medley"
- :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
+ lambdaisland/regal {:mvn/version "0.0.143"}
cprop/cprop {:mvn/version "0.1.16"}
comb/comb {:mvn/version "0.1.1"}
mvxcvi/arrangement {:mvn/version "2.0.0"}
@@ -69,16 +66,16 @@
henryw374/cljc.java-time
{:git/url "https://github.com/henryw374/cljc.java-time.git"
:sha "e3d184b78e933322b3fcaa6ca66cbb8f42a6b35c"}
- camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.1"}
+ camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"}
aero/aero {:mvn/version "1.1.6"}
org.clojure/data.generators {:mvn/version "1.0.0"}
- honeysql/honeysql {:mvn/version "1.0.444"}
- com.github.seancorfield/honeysql {:mvn/version "2.0.0-rc2"}
- minimallist/minimallist {:mvn/version "0.0.6"}
- circleci/bond {:mvn/version "0.4.0"}
- version-clj/version-clj {:mvn/version "2.0.1"}
+ honeysql/honeysql {:mvn/version "1.0.461"}
+ com.github.seancorfield/honeysql {:mvn/version "2.2.840"}
+ minimallist/minimallist {:mvn/version "0.0.10"}
+ circleci/bond {:mvn/version "0.6.0"}
+ version-clj/version-clj {:mvn/version "2.0.2"}
gaka/gaka {:mvn/version "0.3.0"}
- failjure/failjure {:mvn/version "2.1.1"}
+ failjure/failjure {:mvn/version "2.2.0"}
io.helins/binf {:mvn/version "1.1.0-beta0"}
rm-hull/jasentaa {:mvn/version "0.2.5"}
slingshot/slingshot {:mvn/version "0.12.2"}
@@ -104,7 +101,20 @@
listora/again {:mvn/version "1.0.0"}
org.clojure/tools.gitlibs {:mvn/version "2.4.172"}
environ/environ {:mvn/version "1.2.0"}
- table/table {:git/url "https://github.com/cldwalker/table", :sha "55aef3d5fced682942af811bf5d642f79fb87688"}}
+ table/table {:git/url "https://github.com/cldwalker/table", :sha "f6293c5f3dac1dd6f525a80fc80930f8ccdf16b7"}
+ markdown-clj/markdown-clj {:mvn/version "1.10.8"}
+ org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace", :sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b"}
+ medley/medley {:mvn/version "1.3.0"}
+ io.github.cognitect-labs/test-runner {:git/tag "v0.5.0", :git/sha "b3fd0d2"}
+ borkdude/missing.test.assertions {:git/url "https://github.com/borkdude/missing.test.assertions", :sha "603cb01bee72fb17addacc53c34c85612684ad70"}
+ dev.nubank/docopt {:mvn/version "0.6.1-fix7"}
+ testdoc/testdoc {:mvn/version "1.4.1"}
+ org.clojars.lispyclouds/contajners {:mvn/version "0.0.4"}
+ borkdude/rewrite-edn {:mvn/version "0.1.0"}
+ clojure-term-colors/clojure-term-colors {:mvn/version "0.1.0"}
+ io.aviso/pretty {:mvn/version "1.1.1"}
+ progrock/progrock {:mvn/version "0.1.2"}
+ djblue/portal {:mvn/version "0.19.0"}}
:classpath-overrides {org.clojure/clojure nil
org.clojure/spec.alpha nil}}
:clj-nvd
diff --git a/doc/dev.md b/doc/dev.md
index c059a9a8..c3e2876d 100644
--- a/doc/dev.md
+++ b/doc/dev.md
@@ -85,15 +85,15 @@ Babashka runs tests of libraries that are compatible with it through
and run them, use the script `add-libtest.clj` e.g. `script/add-libtest.clj
'{listora/again {:mvn/version "1.0.0"}}' https://github.com/liwp/again --test`.
-If the library you want to add doesn't work with the script, you can manually do the following:
+If the library you want to add doesn't work automatically, you can manually do the following:
* Add an entry for the library in `deps.edn` under the `:lib-tests` alias.
* Create a directory for the library in `test-resources/lib_tests/` and copy its tests to there.
-* Add an entry in `run_all_libtests.clj` to run the added test namespaces.
+* Add a manual lib entry using `add-libtest.clj` e.g. `script/add-libtest.clj http-kit/http-kit -m '{:test-namespaces [httpkit.client-test]}'`.
* Run the tests `script/lib_tests/run_all_libtests NS1 NS2`
-Note: If you have to modify a test to have it work with bb, add an inline
-comment with prefix "BB-TEST-PATCH:" explaining what you did.
+Note: If you have to modify any test file or configuration to have it work with
+bb, add an inline comment with prefix `BB-TEST-PATCH:` explaining what you did.
## Build
diff --git a/doc/libraries.csv b/doc/libraries.csv
new file mode 100644
index 00000000..2665caa7
--- /dev/null
+++ b/doc/libraries.csv
@@ -0,0 +1,72 @@
+maven-name,git-url
+aero/aero,http://github.com/juxt/aero
+amperity/vault-clj,https://github.com/amperity/vault-clj
+babashka/babashka.curl,https://github.com/babashka/babashka.curl
+better-cond/better-cond,https://github.com/Engelberg/better-cond
+borkdude/deps,https://github.com/borkdude/deps.clj
+borkdude/missing.test.assertions,https://github.com/borkdude/missing.test.assertions
+borkdude/rewrite-edn,https://github.com/borkdude/rewrite-edn
+camel-snake-kebab/camel-snake-kebab,https://github.com/clj-commons/camel-snake-kebab
+circleci/bond,https://github.com/circleci/bond
+clj-commons/clj-yaml,https://github.com/clj-commons/clj-yaml
+clj-commons/multigrep,https://github.com/clj-commons/multigrep
+clojure-csv/clojure-csv,https://github.com/davidsantiago/clojure-csv
+clojure-term-colors/clojure-term-colors,https://github.com/trhura/clojure-term-colors
+com.github.seancorfield/honeysql,https://github.com/seancorfield/honeysql
+com.grammarly/omniconf,https://github.com/grammarly/omniconf
+com.stuartsierra/component,https://github.com/stuartsierra/component
+com.stuartsierra/dependency,https://github.com/stuartsierra/dependency
+comb/comb,https://github.com/weavejester/comb
+cprop/cprop,https://github.com/tolitius/cprop
+crispin/crispin,https://github.com/dunaj-project/crispin
+dev.nubank/docopt,https://github.com/nubank/docopt.clj
+djblue/portal,https://github.com/djblue/portal
+doric/doric,https://github.com/joegallo/doric
+douglass/clj-psql,https://github.com/DarinDouglass/clj-psql
+environ/environ,https://github.com/weavejester/environ
+exoscale/coax,https://github.com/exoscale/coax
+expound/expound,https://github.com/bhb/expound
+failjure/failjure,https://github.com/adambard/failjure
+ffclj/ffclj,https://github.com/luissantos/ffclj
+gaka/gaka,https://github.com/cdaddr/gaka
+hato/hato,https://github.com/gnarroway/hato
+henryw374/cljc.java-time,https://github.com/henryw374/cljc.java-time
+hiccup/hiccup,http://github.com/weavejester/hiccup
+honeysql/honeysql,https://github.com/seancorfield/honeysql
+http-kit/http-kit,https://github.com/http-kit/http-kit
+io.aviso/pretty,https://github.com/AvisoNovate/pretty
+io.github.cognitect-labs/test-runner,https://github.com/cognitect-labs/test-runner
+io.github.technomancy/limit-break,https://github.com/technomancy/limit-break
+io.helins/binf,https://github.com/helins/binf.cljc
+io.replikativ/hasch,https://github.com/replikativ/hasch
+java-http-clj/java-http-clj,http://www.github.com/schmee/java-http-clj
+lambdaisland/regal,https://github.com/lambdaisland/regal
+listora/again,https://github.com/liwp/again
+markdown-clj/markdown-clj,https://github.com/yogthos/markdown-clj
+medley/medley,https://github.com/weavejester/medley
+minimallist/minimallist,https://github.com/green-coder/minimallist
+mvxcvi/arrangement,https://github.com/greglook/clj-arrangement
+orchestra/orchestra,https://github.com/jeaye/orchestra
+org.babashka/spec.alpha,https://github.com/babashka/spec.alpha
+org.clj-commons/clj-http-lite,https://github.com/clj-commons/clj-http-lite
+org.clj-commons/digest,https://github.com/clj-commons/clj-digest
+org.clojars.askonomm/ruuter,https://github.com/askonomm/ruuter
+org.clojars.lispyclouds/contajners,https://github.com/lispyclouds/contajners
+org.clojure/core.match,https://github.com/clojure/core.match
+org.clojure/data.csv,https://github.com/clojure/data.csv
+org.clojure/data.generators,https://github.com/clojure/data.generators
+org.clojure/data.json,https://github.com/clojure/data.json
+org.clojure/data.zip,https://github.com/clojure/data.zip
+org.clojure/math.combinatorics,https://github.com/clojure/math.combinatorics
+org.clojure/test.check,https://github.com/clojure/test.check
+org.clojure/tools.gitlibs,https://github.com/clojure/tools.gitlibs
+org.clojure/tools.namespace,https://github.com/babashka/tools.namespace
+progrock/progrock,https://github.com/weavejester/progrock
+reifyhealth/specmonstah,https://github.com/reifyhealth/specmonstah
+rewrite-clj/rewrite-clj,https://github.com/clj-commons/rewrite-clj
+rm-hull/jasentaa,https://github.com/rm-hull/jasentaa
+selmer/selmer,https://github.com/yogthos/Selmer
+slingshot/slingshot,https://github.com/scgilardi/slingshot
+table/table,https://github.com/cldwalker/table
+testdoc/testdoc,https://github.com/liquidz/testdoc
+version-clj/version-clj,https://github.com/xsc/version-clj
diff --git a/doc/projects.md b/doc/projects.md
index 0a55b0b1..b3cc625c 100644
--- a/doc/projects.md
+++ b/doc/projects.md
@@ -56,6 +56,7 @@ The following libraries and projects are known to work with babashka.
- [contajners](#contajners)
- [dependency](#dependency)
- [specmonstah](#specmonstah)
+ - [markdown-clj](#markdown-clj)
- [Pods](#pods)
- [Projects](#projects-1)
- [babashka-test-action](#babashka-test-action)
@@ -82,13 +83,14 @@ The following libraries and projects are known to work with babashka.
- [Babashka + scittle guestbook](#babashka--scittle-guestbook)
- [bb htmx todo app](#bb-htmx-todo-app)
-For more supported libraries, see [this test
-file](../test-resources/lib_tests/babashka/run_all_libtests.clj ). Also keep an eye
-on the [news](news.md) page for new projects, gists and other developments
-around babashka.
+Also keep an eye on the [news](news.md) page for new projects, gists and other
+developments around babashka.
## Libraries
+For a full list of libraries, see [libraries.csv](./libraries.csv). To add a
+library, see [these instructions](./dev.md#tests-for-libraries).
+
### [tools.namespace](https://github.com/babashka/tools.namespace)
A fork of `tools.namespace`. This is used by other libraries and enables them to
@@ -110,12 +112,12 @@ instrumentation! Its readme also contains instructions on how to use
A fork of `tools.build`.
-### [clj-http-lite](https://github.com/babashka/clj-http-lite)
+### [clj-http-lite](https://github.com/clj-commons/clj-http-lite)
-A fork of a fork of `clj-http-lite`. Example:
+Example:
``` shell
-$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clj-http-lite {:git/url "https://github.com/babashka/clj-http-lite" :sha "f44ebe45446f0f44f2b73761d102af3da6d0a13e"}}}' -Spath)"
+$ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {org.clj-commons/clj-http-lite {:mvn/version "0.4.392"}}}' -Spath)"
$ bb "(require '[clj-http.lite.client :as client]) (:status (client/get \"https://www.clojure.org\"))"
200
@@ -145,16 +147,10 @@ Ran 1 tests containing 0 assertions.
### [medley](https://github.com/weavejester/medley/)
-Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
-
-``` clojure
-{:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}
-```
-
Example:
``` shell
-$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley {:git/url "https://github.com/weavejester/medley" :sha "a4e5fb5383f5c0d83cb2d005181a35b76d8a136d"}}}')
+$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {medley/medley {:mvn/version "1.3.0"}}}')
$ bb -e "(require '[medley.core :as m]) (m/index-by :id [{:id 1} {:id 2}])"
{1 {:id 1}, 2 {:id 2}}
@@ -201,16 +197,10 @@ export BABASHKA_CLASSPATH="$(clojure -Sdeps '{:deps {clojure-csv {:mvn/version "
### [regal](https://github.com/lambdaisland/regal)
-Requires `bb` >= v0.0.71. Latest coordinates checked with with bb:
-
-``` clojure
-{:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}
-```
-
Example:
``` shell
-$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {regal {:git/url "https://github.com/lambdaisland/regal" :sha "d4e25e186f7b9705ebb3df6b21c90714d278efb7"}}}')
+$ export BABASHKA_CLASSPATH=$(clojure -Spath -Sdeps '{:deps {lambdaisland/regal {:mvn/version "0.0.143"}}}')
$ bb -e "(require '[lambdaisland.regal :as regal]) (regal/regex [:* \"ab\"])"
#"(?:\Qab\E)*"
@@ -792,6 +782,10 @@ Represent dependency graphs as a directed acylic graph.
Write concise, maintainable test fixtures with clojure.spec.alpha.
+### [markdown-clj](https://github.com/yogthos/markdown-clj)
+
+Markdown parser that translates markdown to html.
+
## Pods
[Babashka pods](https://github.com/babashka/babashka.pods) are programs that can
diff --git a/script/add-libtest.clj b/script/add-libtest.clj
index d44e549e..98215340 100755
--- a/script/add-libtest.clj
+++ b/script/add-libtest.clj
@@ -1,21 +1,29 @@
#!/usr/bin/env bb
-;; Adds a library to bb-tested-libs.edn to be tested given a library version and
-;; git repository. Optionally takes a --test to then test the added library.
+;; Adds a library to bb-tested-libs.edn and libraries.csv and optionally run its
+;; tests. There are two modes to this script - automatic (default) and manual.
+;; The script defaults to automatically copying tests as this normally works.
+;; There are several options to specify where the library is including
+;; --git-url, --dir and --test-directories. See --help for more. In manual mode,
+;; tests are manually added outside of the script and the script is run to add
+;; the library to library lists.
(ns add-libtest
(:require [babashka.deps :as deps]
[babashka.fs :as fs]
[babashka.tasks :refer [shell]]
+ [org.httpkit.client :as http]
[clojure.string :as str]
[clojure.java.io :as io]
[clojure.tools.cli :as cli]
[clojure.edn :as edn]))
(deps/add-deps '{:deps {org.clojure/tools.gitlibs {:mvn/version "2.4.172"}
- borkdude/rewrite-edn {:mvn/version "0.1.0"}}})
+ borkdude/rewrite-edn {:mvn/version "0.1.0"}
+ org.clojure/data.csv {:mvn/version "1.0.0"}}})
(require '[clojure.tools.gitlibs :as gl])
(require '[borkdude.rewrite-edn :as r])
+(require '[clojure.data.csv :as csv])
;; CLI Utils
;; =========
@@ -53,48 +61,66 @@
(let [nodes (-> "deps.edn" slurp r/parse-string)]
(spit "deps.edn"
(str (r/assoc-in nodes
- [:aliases :lib-tests :extra-deps (symbol lib-name)]
+ [:aliases :lib-tests :extra-deps lib-name]
lib-coordinate)))))
+(defn- default-test-dir
+ [lib-root-dir]
+ (some #(when (fs/exists? (fs/file lib-root-dir %))
+ (str (fs/file lib-root-dir %)))
+ ;; Most common test dir
+ ["test"
+ ;; official clojure repos like https://github.com/clojure/tools.gitlibs
+ "src/test/clojure"]))
+
(defn- copy-tests
- [git-url lib-name {:keys [directory branch]}]
+ [git-url lib-name {:keys [directory branch test-directories]}]
(let [lib-dir (if branch
(gl/procure git-url lib-name branch)
(or (gl/procure git-url lib-name "master")
(gl/procure git-url lib-name "main")))
- lib-root-dir (if directory
- (fs/file lib-dir directory) lib-dir)
- test-dir (some #(when (fs/exists? (fs/file lib-root-dir %))
- (str (fs/file lib-root-dir %)))
- ;; Search common test dirs
- ["test"
- ;; official clojure repos like https://github.com/clojure/tools.gitlibs
- "src/test/clojure"])]
- (when-not test-dir
- (error "No test dir found"))
- (shell "cp -R" (str test-dir fs/file-separator) "test-resources/lib_tests/")
+ _ (println "Git clone is at" lib-dir)
+ lib-root-dir (if directory (fs/file lib-dir directory) lib-dir)
+ test-dirs (if test-directories
+ (map #(when (fs/exists? (fs/file lib-root-dir %))
+ (str (fs/file lib-root-dir %)))
+ test-directories)
+ (some-> (default-test-dir lib-root-dir) vector))]
+ (when (empty? test-dirs)
+ (error "No test directories found"))
+ (doseq [test-dir test-dirs]
+ (shell "cp -R" (str test-dir fs/file-separator) "test-resources/lib_tests/"))
{:lib-dir lib-dir
- :test-dir test-dir}))
+ :test-dirs test-dirs}))
+
+(defn- default-test-namespaces
+ [test-dir]
+ (let [relative-test-files (map #(str (fs/relativize test-dir %))
+ (fs/glob test-dir "**/*.{clj,cljc}"))]
+ (when (empty? relative-test-files)
+ (error (str "No test files found in " test-dir)))
+ (map #(-> %
+ (str/replace fs/file-separator ".")
+ (str/replace "_" "-")
+ (str/replace-first #"\.clj(c?)$" "")
+ symbol)
+ relative-test-files)))
(defn- add-lib-to-tested-libs
- [lib-name git-url {:keys [lib-dir test-dir]} options]
- (let [git-sha (fs/file-name lib-dir)
- relative-test-files (map #(str (fs/relativize test-dir %))
- (fs/glob test-dir "**/*.{clj,cljc}"))
- _ (when (empty? relative-test-files)
- (error "No test files found"))
- namespaces (map #(-> %
- (str/replace fs/file-separator ".")
- (str/replace "_" "-")
- (str/replace-first #"\.clj(c?)$" "")
- symbol)
- relative-test-files)
- lib (merge
- {:git-sha git-sha
- :git-url git-url
- :test-namespaces namespaces}
- ;; Options needed to update libs
- (select-keys options [:branch :directory]))
+ [lib-name git-url {:keys [lib-dir test-dirs]} options]
+ (let [namespaces (or (get-in options [:manually-added :test-namespaces])
+ (mapcat default-test-namespaces test-dirs))
+ default-lib (merge
+ {:git-url git-url
+ :test-namespaces namespaces}
+ ;; Options needed to update libs
+ (select-keys options [:branch :directory :test-directories]))
+ lib (if (:manually-added options)
+ (-> default-lib
+ (merge (:manually-added options))
+ (assoc :manually-added true))
+ (assoc default-lib
+ :git-sha (fs/file-name lib-dir)))
nodes (-> "test-resources/lib_tests/bb-tested-libs.edn" slurp r/parse-string)]
(spit "test-resources/lib_tests/bb-tested-libs.edn"
(str (r/assoc-in nodes
@@ -102,25 +128,63 @@
lib)))
namespaces))
+(defn- fetch-artifact
+ "Using the clojars api to get a library's git url doesn't always work. A
+ possibly more reliable data source could be the scm urls in this POM feed -
+ https://github.com/clojars/clojars-web/wiki/Data#useful-extracts-from-the-poms"
+ [artifact]
+ (let [url (str "https://clojars.org/api/artifacts/" artifact)
+ _ (println "GET" url "...")
+ resp @(http/get url {:headers {"Accept" "application/edn"}})]
+ (if (= 200 (:status resp))
+ (-> resp :body slurp edn/read-string)
+ (error (str "Response failed and returned " (pr-str resp))))))
+
+(defn- get-lib-map
+ [deps-string options]
+ ;; if deps-string is artifact name
+ (if (re-matches #"\S+/\S+" deps-string)
+ (let [artifact-edn (fetch-artifact deps-string)]
+ {:lib-name (symbol deps-string)
+ :lib-coordinate {:mvn/version (:latest_version artifact-edn)}
+ :git-url (or (:git-url options) (:homepage artifact-edn))})
+ (let [deps-map (edn/read-string deps-string)
+ _ (when (or (not (map? deps-map)) (not= 1 (count deps-map)))
+ (error "Deps map must have one key"))
+ lib-coordinate (-> deps-map vals first)]
+ {:lib-name (ffirst deps-map)
+ :lib-coordinate lib-coordinate
+ :git-url (or (:git/url lib-coordinate) (:git-url options))})))
+
+(defn- write-lib-to-csv
+ "Updates libraries.csv with latest bb-tested-libs.edn"
+ []
+ (let [libs (-> "test-resources/lib_tests/bb-tested-libs.edn" slurp edn/read-string)
+ rows (sort-by first
+ (map (fn [[name {:keys [git-url]}]]
+ [name git-url]) libs))]
+ (with-open [w (io/writer "doc/libraries.csv")]
+ (csv/write-csv w (into [["maven-name" "git-url"]] rows)))))
+
(defn- add-libtest*
[args options]
- (let [[deps-string git-url] args
- deps-map (edn/read-string deps-string)
- _ (when (not= 1 (count deps-map))
- (error "Deps map must have one key"))
- lib-name (ffirst deps-map)
- lib-coordinate (deps-map lib-name)
- _ (add-lib-to-deps lib-name lib-coordinate)
- dirs (copy-tests git-url lib-name options)
+ (let [[artifact-or-deps-string] args
+ {:keys [lib-name lib-coordinate git-url]}
+ (get-lib-map artifact-or-deps-string options)
+ _ (when (nil? git-url)
+ (error "git-url is required. Please specify with --git-url"))
+ _ (when-not (:manually-added options) (add-lib-to-deps lib-name lib-coordinate))
+ dirs (when-not (:manually-added options) (copy-tests git-url lib-name options))
namespaces (add-lib-to-tested-libs lib-name git-url dirs options)]
(println "Added lib" lib-name "which tests the following namespaces:" namespaces)
+ (write-lib-to-csv)
(when (:test options)
(apply shell "script/lib_tests/run_all_libtests" namespaces))))
(defn add-libtest
[{:keys [arguments options summary]}]
- (if (or (< (count arguments) 2) (:help options))
- (print-summary "DEPS-MAP GIT-URL " summary)
+ (if (or (< (count arguments) 1) (:help options))
+ (print-summary "ARTIFACT-OR-DEPS-MAP " summary)
(add-libtest* arguments options)))
(def cli-options
@@ -129,7 +193,13 @@
;; https://github.com/weavejester/environ/tree/master/environ used this option
["-d" "--directory DIRECTORY" "Directory where library is located"]
;; https://github.com/reifyhealth/specmonstah used this option
- ["-b" "--branch BRANCH" "Default branch for git url"]])
+ ["-b" "--branch BRANCH" "Default branch for git url"]
+ ["-g" "--git-url GITURL" "Git url for artifact. Defaults to homepage on clojars"]
+ ["-m" "--manually-added LIB-MAP" "Only add library to edn file with LIB-MAP merged into library entry"
+ :parse-fn edn/read-string :validate-fn map?]
+ ;; https://github.com/jeaye/orchestra used this option
+ ["-T" "--test-directories TEST-DIRECTORY" "Directories where library tests are located"
+ :multi true :update-fn conj]])
(when (= *file* (System/getProperty "babashka.file"))
(run-command add-libtest *command-line-args* cli-options))
diff --git a/test-resources/lib_tests/aero/core_test.cljc b/test-resources/lib_tests/aero/core_test.cljc
index aabbae06..62b552d8 100644
--- a/test-resources/lib_tests/aero/core_test.cljc
+++ b/test-resources/lib_tests/aero/core_test.cljc
@@ -13,7 +13,7 @@
goog.string.format
[cljs.tools.reader.reader-types
:refer [source-logging-push-back-reader]]]))
- ;; TODO:
+ ;; BB-TEST-PATCH
#_#?(:clj (:import [aero.core Deferred])))
(defn env [s]
@@ -38,6 +38,8 @@
(if (= value :favorite) :chocolate :vanilla))
(deftest basic-test
+ ;; BB-TEST-PATCH: This and several other test files were changed to work with
+ ;; our dir structure
(let [config (read-config "test-resources/lib_tests/aero/config.edn")]
(is (= "Hello World!" (:greeting config))))
(testing "Reading empty config returns nil"
diff --git a/test-resources/lib_tests/aero/lumo_test.cljs b/test-resources/lib_tests/aero/lumo_test.cljs
new file mode 100644
index 00000000..365e61c9
--- /dev/null
+++ b/test-resources/lib_tests/aero/lumo_test.cljs
@@ -0,0 +1,23 @@
+(ns aero.lumo-test
+ (:require
+ aero.core-test
+ [cljs.test :refer-macros [deftest is testing run-tests]]))
+
+(def resolve-p (atom nil))
+
+(def p (new js/Promise (fn [resolve reject]
+ (reset! resolve-p resolve))))
+
+(defmethod cljs.test/report [:cljs.test/default :end-run-tests]
+ [m]
+ (@resolve-p m))
+
+(defn -main [& argv]
+ (println "Testing with lumo")
+ (run-tests 'aero.core-test)
+ (-> p
+ (.then (fn [m]
+ (.exit (js/require "process")
+ (if (cljs.test/successful? m)
+ 0
+ 1))))))
diff --git a/test-resources/lib_tests/babashka/lambdaisland/regal_test.clj b/test-resources/lib_tests/babashka/lambdaisland/regal_test.clj
deleted file mode 100644
index 7f2fcf60..00000000
--- a/test-resources/lib_tests/babashka/lambdaisland/regal_test.clj
+++ /dev/null
@@ -1,15 +0,0 @@
-(ns babashka.lambdaisland.regal-test
- (:require [clojure.test :as t :refer [deftest is]]))
-
-(prn :requiring :lambdaisland)
-(require '[lambdaisland.regal :as regal])
-(prn ::done :requiring :lambdaisland)
-
-(def r [:cat
- [:+ [:class [\a \z]]]
- "="
- [:+ [:not \=]]])
-
-(deftest regal-test
- (is (= "[a-z]+=[^=]+" (str (regal/regex r))))
- (is (= "foo=bar" (re-matches (regal/regex r) "foo=bar"))))
diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj
index a3511e78..58cc0ba8 100644
--- a/test-resources/lib_tests/babashka/run_all_libtests.clj
+++ b/test-resources/lib_tests/babashka/run_all_libtests.clj
@@ -4,8 +4,6 @@
[clojure.edn :as edn]
[clojure.test :as t]))
-#_(require 'clojure.spec.alpha)
-
(def ns-args (set (map symbol *command-line-args*)))
(def status (atom {}))
@@ -27,82 +25,13 @@
(str/lower-case)
(str/includes? "win")))
-;;;; clj-http-lite
+;; Standard test-runner for libtests
+(let [lib-tests (edn/read-string (slurp (io/resource "bb-tested-libs.edn")))]
+ (doseq [{tns :test-namespaces skip-windows :skip-windows} (vals lib-tests)]
+ (when-not (and skip-windows windows?)
+ (apply test-namespaces tns))))
-(test-namespaces 'clj-http.lite.client-test)
-
-;; ;;;; clojure.spec
-
-(test-namespaces 'clojure.test-clojure.spec
- 'clojure.test-clojure.instr
- 'clojure.test-clojure.multi-spec)
-
-;;;; regal
-
-(test-namespaces 'babashka.lambdaisland.regal-test)
-
-;;;; medley
-
-(require '[medley.core :refer [index-by random-uuid]])
-(prn (index-by :id [{:id 1} {:id 2}]))
-(prn (random-uuid))
-
-;;;; babashka.curl
-; skip tests on Windows because of the :compressed thing
-(when-not windows? (test-namespaces 'babashka.curl-test))
-
-;;;; cprop
-
-;; TODO: port to test-namespaces
-
-(require '[cprop.core])
-(require '[cprop.source :refer [from-env]])
-(println (:cprop-env (from-env)))
-
-;;;; clj-yaml
-
-(test-namespaces 'clj-yaml.core-test)
-
-;;;; clojure.data.zip
-
-;; TODO: port to test-namespaces
-
-(require '[clojure.data.xml :as xml])
-(require '[clojure.zip :as zip])
-(require '[clojure.data.zip.xml :refer [attr attr= xml1->]])
-
-(def data (str ""
- " "
- " "
- ""))
-
-;; TODO: convert to test
-(let [xml (zip/xml-zip (xml/parse (java.io.StringReader. data)))]
- ;(prn :xml xml)
- (prn :alice-is-a (xml1-> xml :character [(attr= :name "alice")] (attr :type)))
- (prn :animal-is-called (xml1-> xml :character [(attr= :type "animal")] (attr :name))))
-
-;;;; clojure.data.csv
-
-(test-namespaces 'clojure.data.csv-test)
-
-;;;; clojure.math.combinatorics
-
-(test-namespaces 'clojure.math.test-combinatorics)
-
-;;;; deps.clj
-
-;; TODO: port to test-namespaces
-
-(require '[babashka.curl :as curl])
-(spit "deps_test.clj"
- (:body (curl/get "https://raw.githubusercontent.com/borkdude/deps.clj/master/deps.clj"
- (if windows? {:compressed false} {}))))
-
-(binding [*command-line-args* ["-Sdescribe"]]
- (load-file "deps_test.clj"))
-
-(.delete (io/file "deps_test.clj"))
+;; Non-standard tests - These are tests with unusual setup around test-namespaces
;;;; doric
@@ -112,45 +41,7 @@
((resolve 'doric.core/table) [:a :b] [{:a 1 :b 2}]))
(when (test-namespace? 'doric.test.core)
- (test-doric-cyclic-dep-problem)
- (test-namespaces 'doric.test.core))
-
-;;;; cljc-java-time
-
-(test-namespaces 'cljc.java-time-test)
-
-;;;; camel-snake-kebab
-
-(test-namespaces 'camel-snake-kebab.core-test)
-
-;;;; aero
-
-(test-namespaces 'aero.core-test)
-
-;;;; clojure.data.generators
-
-(test-namespaces 'clojure.data.generators-test)
-
-;;;; honeysql
-
-(test-namespaces 'honeysql.core-test 'honeysql.format-test)
-
-;;;; minimallist
-
-(test-namespaces 'minimallist.core-test)
-
-;;;; bond
-(test-namespaces 'bond.test.james)
-
-;;;; version-clj
-(test-namespaces 'version-clj.compare-test
- 'version-clj.core-test
- 'version-clj.split-test
- 'version-clj.via-use-test)
-
-;;;; httpkit client
-
-(test-namespaces 'httpkit.client-test)
+ (test-doric-cyclic-dep-problem))
;;;; babashka.process
(when-not windows?
@@ -161,79 +52,6 @@
(require '[babashka.process] :reload)
(test-namespaces 'babashka.process-test))
-(test-namespaces 'core-match.core-tests)
-
-(test-namespaces 'hiccup.core-test)
-(test-namespaces 'hiccup2.core-test)
-
-(test-namespaces 'test-check.smoke-test)
-
-(test-namespaces 'gaka.core-test)
-
-(test-namespaces 'failjure.test-core)
-
-(test-namespaces 'rewrite-clj.parser-test
- 'rewrite-clj.node-test
- 'rewrite-clj.zip-test
- 'rewrite-clj.paredit-test
- 'rewrite-clj.zip.subedit-test
- 'rewrite-clj.node.coercer-test)
-
-(test-namespaces 'helins.binf.test)
-
-(test-namespaces 'selmer.core-test)
-(test-namespaces 'selmer.our-test)
-
-(test-namespaces 'jasentaa.position-test
- 'jasentaa.worked-example-1
- 'jasentaa.worked-example-2
- 'jasentaa.collections-test
- 'jasentaa.parser.basic-test
- 'jasentaa.parser.combinators-test)
-
-(test-namespaces 'honey.sql-test
- 'honey.sql.helpers-test
- 'honey.sql.postgres-test)
-
-(test-namespaces 'slingshot.slingshot-test
- 'slingshot.support-test
- ;; TODO:
- ;; 'slingshot.test-test
- )
-
-(test-namespaces 'omniconf.core-test)
-
-(test-namespaces 'crispin.core-test)
-
-(test-namespaces 'multigrep.core-test)
-
-(test-namespaces
- ;; TODO: env tests don't work because envoy lib isn't compatible with bb
- #_'vault.env-test
- 'vault.lease-test
- 'vault.client.http-test
- ;; TODO:
- ;; failing tests in the following namespaces:
- #_'vault.client.mock-test
- #_'vault.secrets.kvv1-test
- #_'vault.secrets.kvv2-test)
-
-;; we don't really run any tests for java-http-clj yet, but we require the
-;; namespaces to see if they at least load correctly
-(test-namespaces 'java-http-clj.smoke-test)
-
-(test-namespaces 'component.component-test)
-
-(test-namespaces 'clj-commons.digest-test)
-
-(test-namespaces 'hato.client-test)
-
-(test-namespaces 'orchestra.core-test 'orchestra.expound-test 'orchestra.many-fns 'orchestra.reload-test)
-
-(let [lib-tests (edn/read-string (slurp (io/resource "bb-tested-libs.edn")))]
- (doseq [{tns :test-namespaces} (vals lib-tests)]
- (apply test-namespaces tns)))
-
;;;; final exit code
(let [{:keys [:test :fail :error] :as m} @status]
diff --git a/test-resources/lib_tests/bb-tested-libs.edn b/test-resources/lib_tests/bb-tested-libs.edn
index cbc276be..2eafea51 100644
--- a/test-resources/lib_tests/bb-tested-libs.edn
+++ b/test-resources/lib_tests/bb-tested-libs.edn
@@ -8,7 +8,7 @@
mvxcvi/arrangement {:git-sha "360d29e7ae81abbf986b5a8e272f2086227d038d", :git-url "https://github.com/greglook/clj-arrangement", :test-namespaces (arrangement.core-test)}
clojure-csv/clojure-csv {:git-sha "b6bb882a3a9ac1f82e06eb2262ae7c8141935228", :git-url "https://github.com/davidsantiago/clojure-csv", :test-namespaces (clojure-csv.test.utils clojure-csv.test.core)}
environ/environ {:git-sha "aa90997b38bb8070d94dc4a00a14e656eb5fc9ae", :git-url "https://github.com/weavejester/environ", :test-namespaces (environ.core-test), :directory "environ"}
- table/table {:git-sha "55aef3d5fced682942af811bf5d642f79fb87688", :git-url "https://github.com/cldwalker/table", :test-namespaces (table.width-test table.core-test)}
+ table/table {:git-sha "f6293c5f3dac1dd6f525a80fc80930f8ccdf16b7", :git-url "https://github.com/cldwalker/table", :test-namespaces (table.width-test table.core-test)}
com.stuartsierra/dependency {:git-sha "3a467918cd0e5b6ab775d344cfb2a80b56daad6d", :git-url "https://github.com/stuartsierra/dependency", :test-namespaces (com.stuartsierra.dependency-test)}
reifyhealth/specmonstah {:git-sha "a2b357009a3aa99a0c2d2361f3bbcd0b0e36505e", :git-url "https://github.com/reifyhealth/specmonstah", :test-namespaces (reifyhealth.specmonstah.spec-gen-test reifyhealth.specmonstah.test-data reifyhealth.specmonstah.core-test), :branch "develop"}
exoscale/coax {:git-sha "0d4212af7c07e4f05f74186f05df8a97777b43fe", :git-url "https://github.com/exoscale/coax", :test-namespaces (exoscale.coax-test)}
@@ -16,4 +16,84 @@
org.clojars.askonomm/ruuter {:git-sha "78659212f95cac827efc816dfbdab8181c25fc3d", :git-url "https://github.com/askonomm/ruuter", :test-namespaces (ruuter.core-test)}
;; clojure.data.json-gen-test ommitted from test-namespaces b/c it hangs on stest/check
org.clojure/data.json {:git-sha "9f1c9ccf3fd3e5a39cfb7289d3d456e842ddf442", :git-url "https://github.com/clojure/data.json", :test-namespaces (clojure.data.json-test clojure.data.json-test-suite-test clojure.data.json-compat-0-1-test)}
- io.replikativ/hasch {:git-sha "04d9c0bd34d86bad79502d8a6963eb2525a22b15", :git-url "https://github.com/replikativ/hasch", :test-namespaces (hasch.test)}}
+ io.replikativ/hasch {:git-sha "04d9c0bd34d86bad79502d8a6963eb2525a22b15", :git-url "https://github.com/replikativ/hasch", :test-namespaces (hasch.test)}
+ ;; BB-TEST-PATCH: Removed markdown.md-file-test b/c tests hardcode path to test
+ ;; files. Removed markdown.benchmark b/c it depends on criterium which isn't bb compatible
+ markdown-clj/markdown-clj {:git-sha "ac245d3049afa25a6d41fcb5ba5a268f52c610e4", :git-url "https://github.com/yogthos/markdown-clj", :test-namespaces (markdown.md-test)}
+ ;; BB-TEST-PATCH: Removed clojure.tools.namespace.dir-test as it fails on windows
+ org.clojure/tools.namespace {:git-sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b", :git-url "https://github.com/babashka/tools.namespace", :test-namespaces (clojure.tools.namespace.test-helpers clojure.tools.namespace.dependency-test clojure.tools.namespace.find-test clojure.tools.namespace.move-test clojure.tools.namespace.parse-test), :branch "babashka"}
+ com.stuartsierra/component {:git-sha "9f9653d1d95644e3c30beadf8c8811f86758ea23", :git-url "https://github.com/stuartsierra/component", :test-namespaces (com.stuartsierra.component-test)}
+ slingshot/slingshot {:git-sha "6961ab0593ab9633c15b7697ffd43823090720be", :git-url "https://github.com/scgilardi/slingshot", :test-namespaces (slingshot.slingshot-test slingshot.support-test slingshot.test-test)}
+ rm-hull/jasentaa {:git-sha "f52a0e75cbdf1d2b72d9604232db264ff6473f12", :git-url "https://github.com/rm-hull/jasentaa", :test-namespaces (jasentaa.position-test jasentaa.worked-example-2 jasentaa.collections-test jasentaa.parser.basic-test jasentaa.parser.combinators-test jasentaa.test-helpers jasentaa.worked-example-1)}
+ failjure/failjure {:git-sha "c6e528c1eda6ad5eaab0f1fb2a97e766bf41fdd5", :git-url "https://github.com/adambard/failjure", :test-namespaces (failjure.test-core)}
+ gaka/gaka {:git-sha "2f264758881d6dc586b948ca8757134675f542a7", :git-url "https://github.com/cdaddr/gaka", :test-namespaces (gaka.core-test)}
+ version-clj/version-clj {:git-sha "9d86cd870f7e435fd6d593cb689790a22d8040a6", :git-url "https://github.com/xsc/version-clj", :test-namespaces (version-clj.compare-test version-clj.split-test version-clj.core-test version-clj.via-use-test)}
+ circleci/bond {:git-sha "0d389cfb4628341824bddbe8bf102f15ad25ad0d", :git-url "https://github.com/circleci/bond", :test-namespaces (bond.assertions-test bond.james-test bond.target-data)}
+ ;; BB-TEST-PATCH: minimallist.generator-test excluded because generator ns can't be required
+ minimallist/minimallist {:git-sha "f10ebbd3c2b93e7579295618a7ed1e870c489bc4", :git-url "https://github.com/green-coder/minimallist", :test-namespaces (minimallist.util-test minimallist.core-test), :branch "all-work-and-no-play"}
+ aero/aero {:git-sha "743e9bc495425b4a4a7c780f5e4b09f6680b4e7a", :git-url "http://github.com/juxt/aero", :test-namespaces (aero.core-test)}
+ org.clojure/data.generators {:git-sha "bf65f99aa9dcabed7de7c09b74d71db208cf61ee", :git-url "https://github.com/clojure/data.generators", :test-namespaces (clojure.data.generators-test)}
+ camel-snake-kebab/camel-snake-kebab {:git-sha "d072c7fd242ab0becd4bb265622ded415f2a4b68", :git-url "https://github.com/clj-commons/camel-snake-kebab", :test-namespaces (camel-snake-kebab.internals.string-separator-test camel-snake-kebab.extras-test camel-snake-kebab.core-test)}
+ ;; BB-TEST-PATCH: Deleted cljs-test-opts.edn
+ henryw374/cljc.java-time {:git-sha "b9da12ea25e80a0e284a5bffd88ebcbf18fc3bf7", :git-url "https://github.com/henryw374/cljc.java-time", :test-namespaces (cljc.java-time-test)}
+ org.babashka/spec.alpha {:git-sha "6c4aed643daaf55c6f898d4915275704db683aa2", :git-url "https://github.com/babashka/spec.alpha", :test-namespaces (clojure.test-clojure.instr clojure.test-clojure.spec)}
+ ;; BB-TEST-PATCH: Don't have 4 tests namespaces because they depend on
+ ;; additional libs that aren't bb compatible e.g. instaparse and malli
+ lambdaisland/regal {:git-sha "d13f26dfdf37186ee86016ed144fc823c5b24c11", :git-url "https://github.com/lambdaisland/regal", :test-namespaces (lambdaisland.regal.test-util lambdaisland.regal-test)}
+ medley/medley {:git-sha "d723afcb18e1fae27f3b68a25c7a151569159a9e", :git-url "https://github.com/weavejester/medley", :test-namespaces (medley.core-test)}
+ clj-commons/clj-yaml {:git-sha "9c2d602ec6ab33da061575f52e3de1aff41f67f5", :git-url "https://github.com/clj-commons/clj-yaml", :test-namespaces (clj-yaml.core-test)}
+ org.clojure/data.csv {:git-sha "aa9b3bdd3a1d3f6a7fe12eaab76b45ef3f197ad5", :git-url "https://github.com/clojure/data.csv", :test-namespaces (clojure.data.csv-test)}
+ org.clojure/math.combinatorics {:git-sha "e555a45b5802cf5e8c43b4377628ef34a634554b", :git-url "https://github.com/clojure/math.combinatorics", :test-namespaces (clojure.math.test-combinatorics)}
+ doric/doric {:git-sha "8747fdce565187a5c368c575cf4ca794084b0a5c", :git-url "https://github.com/joegallo/doric", :test-namespaces (doric.test.core doric.test.readme doric.test.doctest)}
+ com.github.seancorfield/honeysql {:git-sha "6e4e1f6928450788353c181f32474d930d6afe84", :git-url "https://github.com/seancorfield/honeysql", :test-namespaces (honey.sql-test honey.sql.helpers-test honey.sql.postgres-test), :branch "develop"}
+ honeysql/honeysql {:git-sha "1137dd12350afdc30ad4976c3718279581390b36", :git-url "https://github.com/seancorfield/honeysql", :test-namespaces (honeysql.format-test honeysql.core-test), :branch "v1"}
+ ; skip tests on Windows because of the :compressed thing
+ babashka/babashka.curl {:git-url "https://github.com/babashka/babashka.curl", :test-namespaces [babashka.curl-test], :skip-windows true, :manually-added true}
+ http-kit/http-kit {:git-url "https://github.com/http-kit/http-kit", :test-namespaces [httpkit.client-test], :manually-added true}
+ org.clojure/core.match {:git-url "https://github.com/clojure/core.match", :test-namespaces [core-match.core-tests], :manually-added true}
+ hiccup/hiccup {:git-url "http://github.com/weavejester/hiccup", :test-namespaces [hiccup.core-test hiccup2.core-test], :manually-added true}
+ org.clojure/test.check {:git-url "https://github.com/clojure/test.check", :test-namespaces [test-check.smoke-test], :manually-added true}
+ io.helins/binf {:git-url "https://github.com/helins/binf.cljc", :test-namespaces [helins.binf.test], :manually-added true}
+ selmer/selmer {:git-url "https://github.com/yogthos/Selmer", :test-namespaces [selmer.core-test selmer.our-test], :manually-added true}
+ com.grammarly/omniconf {:git-url "https://github.com/grammarly/omniconf", :test-namespaces [omniconf.core-test], :manually-added true}
+ crispin/crispin {:git-url "https://github.com/dunaj-project/crispin", :test-namespaces [crispin.core-test], :manually-added true}
+ clj-commons/multigrep {:git-url "https://github.com/clj-commons/multigrep", :test-namespaces [multigrep.core-test], :manually-added true}
+ org.clj-commons/digest {:git-url "https://github.com/clj-commons/clj-digest", :test-namespaces [clj-commons.digest-test], :manually-added true}
+ hato/hato {:git-url "https://github.com/gnarroway/hato", :test-namespaces [hato.client-test], :manually-added true}
+ java-http-clj/java-http-clj {:git-url "http://www.github.com/schmee/java-http-clj", :test-namespaces [java-http-clj.smoke-test], :manually-added true}
+ rewrite-clj/rewrite-clj {:git-url "https://github.com/clj-commons/rewrite-clj", :test-namespaces [rewrite-clj.parser-test rewrite-clj.node-test rewrite-clj.zip-test rewrite-clj.paredit-test rewrite-clj.zip.subedit-test rewrite-clj.node.coercer-test], :manually-added true}
+ ;; TODO: env tests don't work because envoy lib isn't compatible with bb
+ ;; TODO: failing tests in the following namespaces: vault.client.mock-test, vault.secrets.kvv1-test vault.secrets.kvv2-test
+ amperity/vault-clj {:git-url "https://github.com/amperity/vault-clj", :test-namespaces [vault.lease-test vault.client.http-test], :manually-added true}
+ orchestra/orchestra {:git-url "https://github.com/jeaye/orchestra", :test-namespaces (orchestra.make-fns orchestra.many-fns orchestra.expound-test orchestra.core-test orchestra.reload-test), :test-directories ("test/cljc" "test/clj"), :git-sha "81e5181f7b42e5e2763a2b37db17954f3be0314e"}
+ ;; BB-TEST-PATCH: Deleted tasks.clj
+ org.clj-commons/clj-http-lite {:git-url "https://github.com/clj-commons/clj-http-lite", :test-namespaces (clj-http.lite.test-runner clj-http.lite.client-test), :test-directories ("bb"), :git-sha "6b53000df55ac05c4ff8e5047a5323fc08a52e8b"}
+ cprop/cprop {:git-url "https://github.com/tolitius/cprop", :test-namespaces [cprop.smoke-test], :manually-added true}
+ org.clojure/data.zip {:git-url "https://github.com/clojure/data.zip", :test-namespaces [clojure.data.zip-test], :manually-added true}
+ borkdude/deps {:git-url "https://github.com/borkdude/deps.clj", :test-namespaces [borkdude.deps.smoke-test], :manually-added true}
+ io.github.cognitect-labs/test-runner {:git-url "https://github.com/cognitect-labs/test-runner", :test-namespaces (cognitect.test-runner.samples-test cognitect.test-runner.sample-property-test cognitect.test-runner-test), :git-sha "cc75980b43011773162b485f46f939dc5fba91e4"}
+ borkdude/missing.test.assertions {:git-url "https://github.com/borkdude/missing.test.assertions", :test-namespaces (missing.test.assertions-test missing.test.old-methods), :git-sha "603cb01bee72fb17addacc53c34c85612684ad70"}
+ ;; No tests to run
+ io.github.technomancy/limit-break {:git-url "https://github.com/technomancy/limit-break", :test-namespaces [], :manually-added true}
+ dev.nubank/docopt {:git-url "https://github.com/nubank/docopt.clj", :test-namespaces (docopt.core-test), :git-sha "2794195a7288002e8d8a44f7bc37180c9cca8971"}
+ ;; BB-TEST-PATCH: Deleted unused resources/
+ testdoc/testdoc {:git-url "https://github.com/liquidz/testdoc", :test-namespaces (testdoc.style.repl-test testdoc.style.code-first-test testdoc.core-test), :git-sha "6b995ef25f3cc6450a1ce30f72baed371476f6eb"}
+ ;; BB-TEST-PATCH: Remove contajners.core-test as it fails
+ org.clojars.lispyclouds/contajners {:git-url "https://github.com/lispyclouds/contajners", :test-namespaces (contajners.impl-test), :git-sha "d163637ff36d79995516d6705da1e9afc7b44764"}
+ ;; Don't run tests b/c they depend on `psql`
+ douglass/clj-psql {:git-url "https://github.com/DarinDouglass/clj-psql", :test-namespaces [], :manually-added true}
+ ;; Don't run tests b/c they depend on `ffmpeg`
+ ffclj/ffclj {:git-url "https://github.com/luissantos/ffclj", :test-namespaces [], :manually-added true}
+ ;; BB-TEST-PATCH: Can't load deps for tests - expound.alpha-test, expound.spell-spec-test, expound.paths-test. Skip expound.printer-test as most tests fail
+ ;; BB-TEST-PATCH: Deleted cljs_test.cljs and *.txt
+ expound/expound {:git-url "https://github.com/bhb/expound", :test-namespaces (expound.specs-test expound.print-length-test expound.test-utils expound.spec-gen expound.problems-test), :git-sha "589a7f69323dc0423197b346c75808e48e771427"}
+ ;; BB-TEST-PATCH: Removed borkdude.rewrite-edn-test because it fails
+ borkdude/rewrite-edn {:git-url "https://github.com/borkdude/rewrite-edn", :test-namespaces [], :branch "63f09048a3ebbd48f86fa9626076e7e540cfb7ee", :git-sha "63f09048a3ebbd48f86fa9626076e7e540cfb7ee"}
+ clojure-term-colors/clojure-term-colors {:git-url "https://github.com/trhura/clojure-term-colors", :test-namespaces (clojure.term.colors-test), :git-sha "71620a5e121d51afe28c50c0aa14ceb4cbff7981"}
+ ;; BB-TEST-PATCH: Removed io.aviso.exception-test because it can't load ns with clojure.lang.Compiler.
+ ;; BB-TEST-PATCH: Deleted demo*.clj
+ ;; BB-TEST-PATCH: Don't run on windows as most binary tests fail
+ io.aviso/pretty {:git-url "https://github.com/AvisoNovate/pretty", :test-namespaces (io.aviso.binary-test), :git-sha "155926f991f94addaf6f5c8621748924ab144988" :skip-windows true}
+ progrock/progrock {:git-url "https://github.com/weavejester/progrock", :test-namespaces (progrock.core-test), :git-sha "9c277a3244c52bfde19c21add327d6e20b94fdf5"}
+ ;; Don't run portal.jvm-test as it depends on headless chrome
+ djblue/portal {:git-url "https://github.com/djblue/portal", :test-namespaces (portal.test-runner portal.runtime.cson-test portal.runtime.fs-test portal.e2e portal.bench), :git-sha "64e4624bcf3bee2dd47e3d8e47982c709738eb11"}}
diff --git a/test-resources/lib_tests/bond/assertions_test.clj b/test-resources/lib_tests/bond/assertions_test.clj
new file mode 100644
index 00000000..1969600a
--- /dev/null
+++ b/test-resources/lib_tests/bond/assertions_test.clj
@@ -0,0 +1,121 @@
+(ns bond.assertions-test
+ (:require [clojure.test :refer (deftest is testing)]
+ [bond.assertions :as assertions]
+ [bond.james :as bond :include-macros true]
+ [bond.target-data :as target]))
+
+(deftest called?-works
+ (testing "a spy was called directly"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (is (assertions/called? target/foo))))
+
+ (testing "a spy was called indirectly"
+ (bond/with-spy [target/foo]
+ (target/foo-caller 1)
+ (is (assertions/called? target/foo))))
+
+ (testing "a spy was not called"
+ (bond/with-spy [target/foo]
+ (is (not (assertions/called? target/foo)))))
+
+ (testing "called? fails when its argument is not spied"
+ (is (thrown? IllegalArgumentException
+ (assertions/called? target/foo)))))
+
+(deftest called-times?-works
+ (testing "the number of times a spy was called"
+ (bond/with-spy [target/foo]
+ (target/foo-caller 1)
+ (is (assertions/called-times? target/foo 1))
+ (target/foo 2)
+ (is (assertions/called-times? target/foo 2))))
+
+ (testing "the number of times a spy was not called"
+ (bond/with-spy [target/foo]
+ (target/foo-caller 1)
+ (is (not (assertions/called-times? target/foo 2)))
+ (target/foo-caller 2)
+ (is (not (assertions/called-times? target/foo 1)))))
+
+ (testing "called-times? fails when its argument is not spied"
+ (is (thrown? IllegalArgumentException
+ (assertions/called-times? target/foo 0)))))
+
+(deftest called-with-args?-works
+ (testing "an assertion for calling a spy with args"
+ (bond/with-spy [target/foo
+ target/bar]
+ (target/foo-caller 1)
+ (is (assertions/called-with-args? target/foo [[1]]))
+ (is (not (assertions/called-with-args? target/foo [[2]])))
+ (is (not (assertions/called-with-args? target/bar [[1]])))
+ (is (not (assertions/called-with-args? target/foo [[1 2]])))))
+
+ (testing "an assertion for calling a spy multiple times with args"
+ (bond/with-spy [target/foo]
+ (target/foo-caller 1)
+ (target/foo-caller 2)
+ (is (assertions/called-with-args? target/foo [[1] [2]]))))
+
+ (testing "called-with-args? fails when its argument is not spied"
+ (is (thrown? IllegalArgumentException
+ (assertions/called-with-args? target/foo [])))))
+
+(deftest called-once-with-args?-works
+ (testing "an assertion for calling a spy once with args"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (is (assertions/called-once-with-args? target/foo [1]))
+ (is (not (assertions/called-once-with-args? target/foo [2])))))
+
+ (testing "an assertion for calling a spy twice with args"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (target/foo 2)
+ (is (not (assertions/called-once-with-args? target/foo [1])))
+ (is (not (assertions/called-once-with-args? target/foo [2])))))
+
+ (testing "an assertion for calling a spy indirectly once with args"
+ (bond/with-spy [target/foo]
+ (target/foo-caller 1)
+ (is (assertions/called-once-with-args? target/foo [1]))
+ (is (not (assertions/called-once-with-args? target/foo [2])))))
+
+ (testing "an assertion for a spy that was not called"
+ (bond/with-spy [target/foo]
+ (is (not (assertions/called-once-with-args? target/foo [])))))
+
+ (testing "called-once-with-args? fails when its argument is not spied"
+ (is (thrown? IllegalArgumentException
+ (assertions/called-once-with-args? target/foo [])))))
+
+(deftest called-at-least-once-with-args?-works
+ (testing "an assertion for calling a spy multiple times"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (target/foo 2)
+ (is (assertions/called-at-least-once-with-args? target/foo [1]))
+ (is (assertions/called-at-least-once-with-args? target/foo [2]))
+ (is (not (assertions/called-at-least-once-with-args? target/foo [3])))))
+
+ (testing "an assertion for calling a spy multiple times with the same value"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (target/foo 1)
+ (is (assertions/called-at-least-once-with-args? target/foo [1]))
+ (is (not (assertions/called-at-least-once-with-args? target/foo [2])))))
+
+ (testing "an assertion for calling a spy once"
+ (bond/with-spy [target/foo]
+ (target/foo 1)
+ (is (assertions/called-at-least-once-with-args? target/foo [1]))
+ (is (not (assertions/called-at-least-once-with-args? target/foo [2])))))
+
+ (testing "an assertion for a spy that was not called"
+ (bond/with-spy [target/foo]
+ (is (not (assertions/called-at-least-once-with-args? target/foo [])))))
+
+ (testing "called-at-least-once-with-args? fails when its argument is not spied"
+ (is (thrown? IllegalArgumentException
+ (assertions/called-at-least-once-with-args? target/foo [])))))
diff --git a/test-resources/lib_tests/bond/james_test.clj b/test-resources/lib_tests/bond/james_test.clj
new file mode 100644
index 00000000..f6a5696f
--- /dev/null
+++ b/test-resources/lib_tests/bond/james_test.clj
@@ -0,0 +1,109 @@
+(ns bond.james-test
+ {:clj-kondo/config {:linters {:private-call {:level :off}
+ :invalid-arity {:level :off}}}}
+ (:require [clojure.test :refer (deftest is testing)]
+ [bond.james :as bond :include-macros true]
+ [bond.target-data :as target]))
+
+(deftest spy-logs-args-and-results
+ (bond/with-spy [target/foo]
+ (is (= 2 (target/foo 1)))
+ (is (= 4 (target/foo 2)))
+ (is (= [{:args [1] :return 2}
+ {:args [2] :return 4}]
+ (bond/calls target/foo)))
+ (let [exception (is (thrown? clojure.lang.ArityException (target/foo 3 4)))]
+ (is (= {:args [3 4] :throw exception}
+ (-> target/foo bond/calls last))))))
+
+(deftest calls-fails-on-unspied-fns
+ (is (thrown? IllegalArgumentException
+ (bond/calls target/foo))))
+
+(deftest spy-can-spy-private-fns
+ (bond/with-spy [target/private-foo]
+ (is (= 4 (#'target/private-foo 2)))
+ (is (= 6 (#'target/private-foo 3)))
+ (is (= [{:args [2] :return 4}
+ {:args [3] :return 6}]
+ (bond/calls #'target/private-foo)))))
+
+(deftest stub-works
+ (is (= ""
+ (with-out-str
+ (bond/with-stub [target/bar]
+ (target/bar 3))))))
+
+(deftest stub-works-with-private-fn
+ (testing "without replacement"
+ (bond/with-stub [target/private-foo]
+ (is (nil? (#'target/private-foo 3)))
+ (is (= [3] (-> #'target/private-foo bond/calls first :args)))))
+ (testing "with replacement"
+ (bond/with-stub [[target/private-foo (fn [x] (* x x))]]
+ (is (= 9 (#'target/private-foo 3)))
+ (is (= [3] (-> #'target/private-foo bond/calls first :args))))))
+
+(deftest stub-with-replacement-works
+ (bond/with-stub [target/foo
+ [target/bar #(str "arg is " %)]]
+ (testing "stubbing works"
+ (is (nil? (target/foo 4)))
+ (is (= "arg is 3" (target/bar 3))))
+ (testing "spying works"
+ (is (= [4] (-> target/foo bond/calls first :args)))
+ (is (= [3] (-> target/bar bond/calls first :args))))))
+
+
+(deftest iterator-style-stubbing-works
+ (bond/with-stub [target/foo
+ [target/bar [#(str "first arg is " %)
+ #(str "second arg is " %)
+ #(str "third arg is " %)]]]
+ (testing "stubbing works"
+ (is (nil? (target/foo 4)))
+ (is (= "first arg is 3" (target/bar 3)))
+ (is (= "second arg is 4" (target/bar 4)))
+ (is (= "third arg is 5" (target/bar 5))))
+ (testing "spying works"
+ (is (= [4] (-> target/foo bond/calls first :args)))
+ (is (= [3] (-> target/bar bond/calls first :args)))
+ (is (= [4] (-> target/bar bond/calls second :args)))
+ (is (= [5] (-> target/bar bond/calls last :args))))))
+
+(deftest stub!-complains-loudly-if-there-is-no-arglists
+ (is (thrown? IllegalArgumentException
+ (bond/with-stub! [[target/without-arglists (constantly 42)]]
+ (throw (Exception. "shouldn't get here"))))))
+
+(deftest stub!-throws-arity-exception
+ (bond/with-stub! [[target/foo (constantly 9)]]
+ (is (= 9 (target/foo 12)))
+ (is (= [{:args [12] :return 9}] (bond/calls target/foo))))
+ (bond/with-stub! [target/bar
+ target/quuk
+ [target/quux (fn [_ _ & x] x)]]
+ (is (thrown? clojure.lang.ArityException
+ (target/bar 1 2)))
+ (is (thrown? clojure.lang.ArityException
+ (target/quuk 1)))
+ (is (= [6 5] (target/quux 8 7 6 5)))))
+
+(deftest spying-entire-namespaces-works
+ (bond/with-spy-ns [bond.target-data]
+ (target/foo 1)
+ (target/foo 2)
+ (is (= [{:args [1] :return 2}
+ {:args [2] :return 4}]
+ (bond/calls target/foo)))
+ (is (= 0 (-> target/bar bond/calls count)))))
+
+(deftest stubbing-entire-namespaces-works
+ (testing "without replacements"
+ (bond/with-stub-ns [bond.target-data]
+ (is (nil? (target/foo 10)))
+ (is (= [10] (-> target/foo bond/calls first :args)))))
+ (testing "with replacements"
+ (bond/with-stub-ns [[bond.target-data (constantly 3)]]
+ (is (= 3 (target/foo 10)))
+ (is (= [10] (-> target/foo bond/calls first :args))))))
diff --git a/test-resources/lib_tests/bond/target_data.clj b/test-resources/lib_tests/bond/target_data.clj
new file mode 100644
index 00000000..986a06de
--- /dev/null
+++ b/test-resources/lib_tests/bond/target_data.clj
@@ -0,0 +1,35 @@
+(ns bond.target-data
+ "Reference def targets for bond to test against."
+ {:clj-kondo/config {:linters {:unused-binding {:level :off}
+ :unused-private-var {:level :off}}}})
+
+(defn foo
+ [x]
+ (* 2 x))
+
+(defn- private-foo
+ [x]
+ (* 2 x))
+
+(defn foo-caller [x]
+ (foo x))
+
+(defn bar
+ [x]
+ (println "bar!") (* 2 x))
+
+(defn quux
+ [a b & c]
+ c)
+
+(defn quuk
+ [a b & c]
+ c)
+
+(defmacro baz
+ [x]
+ `(* ~x 2))
+
+(def without-arglists
+ (fn [x]
+ (* 2 x)))
diff --git a/test-resources/lib_tests/borkdude/deps/smoke_test.clj b/test-resources/lib_tests/borkdude/deps/smoke_test.clj
new file mode 100644
index 00000000..50781333
--- /dev/null
+++ b/test-resources/lib_tests/borkdude/deps/smoke_test.clj
@@ -0,0 +1,20 @@
+(ns borkdude.deps.smoke-test
+ (:require [clojure.test :as t :refer [deftest is]]
+ [clojure.java.io :as io]
+ [clojure.string :as str]
+ [babashka.curl :as curl]))
+
+
+(def windows? (-> (System/getProperty "os.name")
+ (str/lower-case)
+ (str/includes? "win")))
+
+(deftest basic-test
+ (spit "deps_test.clj"
+ (:body (curl/get "https://raw.githubusercontent.com/borkdude/deps.clj/master/deps.clj"
+ (if windows? {:compressed false} {}))))
+
+ (binding [*command-line-args* ["-Sdescribe"]]
+ (load-file "deps_test.clj"))
+
+ (.delete (io/file "deps_test.clj")))
diff --git a/test-resources/lib_tests/borkdude/rewrite_edn_test.cljc b/test-resources/lib_tests/borkdude/rewrite_edn_test.cljc
new file mode 100644
index 00000000..b5eb4074
--- /dev/null
+++ b/test-resources/lib_tests/borkdude/rewrite_edn_test.cljc
@@ -0,0 +1,133 @@
+(ns borkdude.rewrite-edn-test
+ (:require [borkdude.rewrite-edn :as r]
+ [clojure.test :as t :refer [deftest testing is]]))
+
+(deftest assoc-test
+ (testing "Base case"
+ (is (= "{:a 1}"
+ (str (r/assoc
+ (r/parse-string "{}")
+ :a 1)))))
+ (testing "When there's only one existing, keys are added on a new line"
+ (is (= "
+{:a 1
+ :b 1}"
+ (str (r/assoc
+ (r/parse-string "
+{:a 1}")
+ :b 1)))))
+ (testing "Unless there are already keys on the same line"
+ (is (= "{:a 1 :b 2 :c 3}"
+ (str (r/assoc
+ (r/parse-string "{:a 1 :b 2}")
+ :c 3)))))
+ (testing "when map is already multi-line, new keys are added on new line"
+ (is (= "
+{:a 1
+ :b 2}
+;; this is a cool map"
+ (str (r/assoc
+ (r/parse-string "
+{:a 1}
+;; this is a cool map")
+ :b 2)))))
+ (testing "Updating existing val"
+ (is (= "{:a 2}"
+ (str (r/assoc
+ (r/parse-string "{:a 1}")
+ :a 2)))))
+ (testing "Something between key and val"
+ (is (= "{:a #_:something 2}"
+ (str (r/assoc
+ (r/parse-string "{:a #_:something 1}")
+ :a 2)))))
+ (testing "Comment at the end"
+ (is (= "{:a 2} ;; this is a cool map"
+ (str (r/assoc
+ (r/parse-string "{:a 1} ;; this is a cool map")
+ :a 2)))))
+ (testing "Vector index assoc"
+ (is (= "[9 8 99 7] ;; this is a cool vector"
+ (str (r/assoc
+ (r/parse-string "[9 8 3 7] ;; this is a cool vector")
+ 2 99)))))
+ (testing "Vector last index assoc"
+ (is (= "[9 8 3 99] ;; this is a cool vector"
+ (str (r/assoc
+ (r/parse-string "[9 8 3 7] ;; this is a cool vector")
+ 3 99)))))
+ (testing "Vector assoc out of bounds"
+ (is (try
+ (r/assoc (r/parse-string "[9 8 3 7] ;; this is a cool vector") 9 99)
+ false
+ (catch java.lang.IndexOutOfBoundsException _ true))))
+ (testing "Vector assoc out of bounds with ignored"
+ (is (try
+ (r/assoc (r/parse-string "[9 8 3 #_99 #_213 7] ;; this is a cool vector") 4 99)
+ false
+ (catch java.lang.IndexOutOfBoundsException _ true)))))
+
+(deftest update-test
+ (is (= "{:a #_:foo 2}"
+ (str (r/update
+ (r/parse-string "{:a #_:foo 1}")
+ :a (fn [node]
+ (inc (r/sexpr node))))))))
+
+(defn qualify-sym-node [sym-node]
+ (let [sym (r/sexpr sym-node)]
+ (if (or (not (symbol? sym))
+ (qualified-symbol? sym))
+ sym-node
+ (symbol (str sym) (str sym)))))
+
+(deftest map-keys-test
+ (is (= "
+{foo/foo 1
+ bar/bar 2}"
+ (str (r/map-keys qualify-sym-node
+ (r/parse-string "
+{foo 1
+ bar 2}"))))))
+
+(deftest update-deps-test
+ (is (= "{:deps {foo/foo {:mvn/version \"0.1.0\"}}}"
+ (str (r/update (r/parse-string "{:deps {foo {:mvn/version \"0.1.0\"}}}")
+ :deps
+ (fn [deps-map-node]
+ (r/map-keys qualify-sym-node deps-map-node)))))))
+
+(deftest assoc-in-test
+ (is (= "{:a {:b {:c 2}}}"
+ (str (r/assoc-in (r/parse-string "{}")
+ [:a :b :c] 2))))
+ (is (= "{:a {:b {:c 2}}}"
+ (str (r/assoc-in (r/parse-string "nil")
+ [:a :b :c] 2))))
+ (is (= "{:deps {foo/foo {:mvn/version \"0.2.0\"}}}"
+ (str (r/assoc-in (r/parse-string "{:deps {foo/foo {:mvn/version \"0.1.0\"}}}")
+ [:deps 'foo/foo :mvn/version]
+ "0.2.0"))))
+ (is (= "{:a 1 :b {:c 1}}"
+ (str (r/assoc-in (r/parse-string "{:a 1}") [:b :c] 1)))))
+
+(deftest update-in-test
+ (is (= "{:deps {foo/foo {:mvn/version \"0.2.0\"}}}"
+ (str (r/update-in (r/parse-string "{:deps {foo/foo {:mvn/version \"0.1.0\"}}}")
+ [:deps 'foo/foo]
+ #(r/assoc % :mvn/version "0.2.0")))))
+ (is (= "{:a {:b {:c 1}}}"
+ (str (r/update-in (r/parse-string "{}")
+ [:a :b :c]
+ (comp (fnil inc 0) r/sexpr)))))
+ (is (= "{:a {:b {:c 1}}}"
+ (str (r/update-in (r/parse-string "nil")
+ [:a :b :c]
+ (comp (fnil inc 0) r/sexpr))))))
+
+(deftest dissoc-test
+ (is (= "{}" (str (r/dissoc (r/parse-string "{:a 1}") :a))))
+ (is (= "{:a 1}" (str (r/dissoc (r/parse-string "{:a 1 \n\n:b 2}") :b))))
+ (is (= "{:a 1\n:c 3}" (str (r/dissoc (r/parse-string "{:a 1\n:b 2\n:c 3}") :b))))
+ (is (= "{:deps {foo/bar {}}}" (str (r/update (r/parse-string "{:deps {foo/bar {} foo/baz {}}}")
+ :deps #(r/dissoc % 'foo/baz))))))
diff --git a/test-resources/lib_tests/camel_snake_kebab/core_test.cljc b/test-resources/lib_tests/camel_snake_kebab/core_test.cljc
index ab65cb53..cf426706 100644
--- a/test-resources/lib_tests/camel_snake_kebab/core_test.cljc
+++ b/test-resources/lib_tests/camel_snake_kebab/core_test.cljc
@@ -47,7 +47,11 @@
(testing "handling of blank input string"
(is (= "" (csk/->kebab-case "")))
- (is (= "" (csk/->kebab-case " ")))))
+ (is (= "" (csk/->kebab-case " "))))
+
+ (testing "handling of input consisting of only separator(s)"
+ (is (= "" (csk/->kebab-case "a" :separator \a)))
+ (is (= "" (csk/->kebab-case "aa" :separator \a)))))
(deftest http-header-case-test
(are [x y] (= x (csk/->HTTP-Header-Case y))
diff --git a/test-resources/lib_tests/camel_snake_kebab/extras_test.cljc b/test-resources/lib_tests/camel_snake_kebab/extras_test.cljc
new file mode 100644
index 00000000..81d0c08f
--- /dev/null
+++ b/test-resources/lib_tests/camel_snake_kebab/extras_test.cljc
@@ -0,0 +1,16 @@
+(ns camel-snake-kebab.extras-test
+ (:require [camel-snake-kebab.core :as csk]
+ [camel-snake-kebab.extras :refer [transform-keys]]
+ #?(:clj [clojure.test :refer :all]
+ :cljs [cljs.test :refer-macros [deftest testing is are]])))
+
+(deftest transform-keys-test
+ (are [x y] (= x (transform-keys csk/->kebab-case-keyword y))
+ nil nil
+ {} {}
+ [] []
+ {:total-books 0 :all-books []} {'total_books 0 "allBooks" []}
+ [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}]
+ [{'the-Author "Dr. Seuss" "The_Title" "Green Eggs and Ham"}]
+ {:total-books 1 :all-books [{:the-author "Dr. Seuss" :the-title "Green Eggs and Ham"}]}
+ {'total_books 1 "allBooks" [{'THE_AUTHOR "Dr. Seuss" "the_Title" "Green Eggs and Ham"}]}))
diff --git a/test-resources/lib_tests/camel_snake_kebab/internals/string_separator_test.cljc b/test-resources/lib_tests/camel_snake_kebab/internals/string_separator_test.cljc
new file mode 100644
index 00000000..31a5704a
--- /dev/null
+++ b/test-resources/lib_tests/camel_snake_kebab/internals/string_separator_test.cljc
@@ -0,0 +1,41 @@
+(ns camel-snake-kebab.internals.string-separator-test
+ (:require [camel-snake-kebab.internals.string-separator :refer [split generic-separator]]
+ #?(:clj [clojure.test :refer :all]
+ :cljs [cljs.test :refer-macros [deftest testing is are]])))
+
+(deftest split-test
+ (testing "regex, string and character separators"
+ (are [sep]
+ (and (= ["foo" "bar"] (split sep "foo.bar"))
+ (= [""] (split sep "")))
+ #"\." "." \.))
+
+ (testing "input consisting of separator(s)"
+ (is (empty? (split "x" "x")))
+ (is (empty? (split "x" "xx"))))
+
+ (testing "generic separator"
+ (are [x y]
+ (= x (split generic-separator y))
+
+ [""] ""
+ [""] " "
+ ["x"] " x "
+
+ ["foo" "bar"] "foo bar"
+ ["foo" "bar"] "foo\n\tbar"
+ ["foo" "bar"] "foo-bar"
+ ["foo" "Bar"] "fooBar"
+ ["Foo" "Bar"] "FooBar"
+ ["foo" "bar"] "foo_bar"
+ ["FOO" "BAR"] "FOO_BAR"
+
+ ["räksmörgås"] "räksmörgås"
+
+ ["IP" "Address"] "IPAddress"
+
+ ["Adler" "32"] "Adler32"
+ ["Inet" "4" "Address"] "Inet4Address"
+ ["Arc" "2" "D"] "Arc2D"
+ ["a" "123b"] "a123b"
+ ["A" "123" "B"] "A123B")))
diff --git a/test-resources/lib_tests/camel_snake_kebab/test_runner.cljs b/test-resources/lib_tests/camel_snake_kebab/test_runner.cljs
new file mode 100644
index 00000000..20b094bd
--- /dev/null
+++ b/test-resources/lib_tests/camel_snake_kebab/test_runner.cljs
@@ -0,0 +1,10 @@
+(ns camel-snake-kebab.test-runner
+ (:require [cljs.test :as test]
+ [doo.runner :refer-macros [doo-all-tests doo-tests]]
+ [camel-snake-kebab.core-test]
+ [camel-snake-kebab.extras-test]
+ [camel-snake-kebab.internals.string-separator-test]))
+
+(doo-tests 'camel-snake-kebab.core-test
+ 'camel-snake-kebab.extras-test
+ 'camel-snake-kebab.internals.string-separator-test)
diff --git a/test-resources/lib_tests/clj_http/lite/client_test.clj b/test-resources/lib_tests/clj_http/lite/client_test.clj
index d6a2ba89..86407b69 100644
--- a/test-resources/lib_tests/clj_http/lite/client_test.clj
+++ b/test-resources/lib_tests/clj_http/lite/client_test.clj
@@ -24,11 +24,12 @@
:accept :json
:throw-exceptions false})))))
-(deftest insecure-test
- (is (= 200 (:status (client/get "https://self-signed.badssl.com/" {:insecure? true})))))
-
(deftest exception-test
(try (client/get "https://site.com/broken")
(is false "should not reach here")
(catch Exception e
(is (:headers (ex-data e))))))
+
+;; BB-TEST-PATCH: Added test
+(deftest insecure-test
+ (is (= 200 (:status (client/get "https://self-signed.badssl.com/" {:insecure? true})))))
diff --git a/test-resources/lib_tests/clj_http/lite/test_runner.clj b/test-resources/lib_tests/clj_http/lite/test_runner.clj
new file mode 100644
index 00000000..b793c578
--- /dev/null
+++ b/test-resources/lib_tests/clj_http/lite/test_runner.clj
@@ -0,0 +1,10 @@
+(ns clj-http.lite.test-runner
+ (:require [clj-http.lite.client-test]
+ [clojure.test :as t]))
+
+(defn -main [& _]
+ (let [{:keys [fail error]} (t/run-tests 'clj-http.lite.client-test)]
+ (System/exit (if (or (pos? fail)
+ (pos? error))
+ 1 0))))
+
diff --git a/test-resources/lib_tests/clj_yaml/core_test.clj b/test-resources/lib_tests/clj_yaml/core_test.clj
index 0e75a4d6..cf705ece 100644
--- a/test-resources/lib_tests/clj_yaml/core_test.clj
+++ b/test-resources/lib_tests/clj_yaml/core_test.clj
@@ -1,8 +1,15 @@
(ns clj-yaml.core-test
(:require [clojure.test :refer (deftest testing is)]
[clojure.string :as string]
- [clj-yaml.core :refer [parse-string unmark generate-string]])
- (:import [java.util Date]))
+ [clojure.java.io :as io]
+ [clj-yaml.core :refer [parse-string unmark generate-string
+ parse-stream generate-stream]])
+ (:import [java.util Date]
+ (java.io ByteArrayOutputStream OutputStreamWriter ByteArrayInputStream)
+ java.nio.charset.StandardCharsets
+ (org.yaml.snakeyaml.error YAMLException)
+ ;; BB-TEST-PATCH: bb doesn't have these classes
+ #_(org.yaml.snakeyaml.constructor DuplicateKeyException)))
(def nested-hash-yaml
"root:\n childa: a\n childb: \n grandchild: \n greatgrandchild: bar\n")
@@ -27,7 +34,7 @@ items:
")
(def inline-list-yaml
-"--- # Shopping list
+ "--- # Shopping list
[milk, pumpkin pie, eggs, juice]
")
@@ -160,8 +167,8 @@ the-bin: !!binary 0101")
;; This test ensures that generate-string uses the older behavior by default, for the sake
;; of stability, i.e. backwards compatibility.
(is
- (= "{description: Big-picture diagram showing how our top-level systems and stakeholders interact}\n"
- (generate-string data))))))
+ (= "{description: Big-picture diagram showing how our top-level systems and stakeholders interact}\n"
+ (generate-string data))))))
(deftest dump-opts
(let [data [{:age 33 :name "jon"} {:age 44 :name "boo"}]]
@@ -170,9 +177,7 @@ the-bin: !!binary 0101")
(is (= "[{age: 33, name: jon}, {age: 44, name: boo}]\n"
(generate-string data :dumper-options {:flow-style :flow})))))
-;; TODO: this test is failing in GraalVM
-;; Could be related to https://github.com/oracle/graal/issues/2234
-#_(deftest parse-time
+(deftest parse-time
(testing "clj-time parses timestamps with more than millisecond precision correctly."
(let [timestamp "2001-11-23 15:02:31.123456 -04:00"
expected 1006542151123]
@@ -182,7 +187,7 @@ the-bin: !!binary 0101")
(let [parsed (parse-string hashes-lists-yaml)
[first second] (:items parsed)]
(is (= (keys first) '(:part_no :descrip :price :quantity)))
- (is (= (keys second)'(:part_no :descrip :price :quantity :owners)))))
+ (is (= (keys second) '(:part_no :descrip :price :quantity :owners)))))
(deftest nulls-are-fine
@@ -201,3 +206,90 @@ the-bin: !!binary 0101")
(testing "emoji in comments are OK too"
(let [yaml "# 💣 emoji in a comment\n42"]
(is (= 42 (parse-string yaml))))))
+
+(def too-many-aliases
+ (->> (range 51)
+ (map #(str "b" % ": *a"))
+ (cons "a: &a [\"a\",\"a\"]")
+ (string/join "\n")))
+
+(deftest max-aliases-for-collections-works
+ (is (thrown-with-msg? YAMLException #"Number of aliases" (parse-string too-many-aliases)))
+ (is (parse-string too-many-aliases :max-aliases-for-collections 51)))
+
+(def recursive-yaml "
+---
+&A
+- *A: *A
+")
+
+(deftest allow-recursive-works
+ (is (thrown-with-msg? YAMLException #"Recursive" (parse-string recursive-yaml)))
+ (is (parse-string recursive-yaml :allow-recursive-keys true)))
+
+(def duplicate-keys-yaml "
+a: 1
+a: 1
+")
+
+#_(deftest duplicate-keys-works
+ (is (parse-string duplicate-keys-yaml))
+ (is (thrown-with-msg? DuplicateKeyException #"found duplicate key" (parse-string duplicate-keys-yaml :allow-duplicate-keys false))))
+
+(def namespaced-keys-yaml "
+foo/bar: 42
+")
+
+(deftest namespaced-keys-works
+ (testing "namespaced keys in yaml can round trip through parse and generate"
+ (is (= {:foo/bar 42} (-> namespaced-keys-yaml
+ parse-string
+ generate-string
+ parse-string)))))
+
+(defn to-bytes
+ "Converts a string to a byte array."
+ [data]
+ (.getBytes ^String data StandardCharsets/UTF_8))
+
+(defn roundtrip
+ "Testing roundtrip of string and stream parser, and checking their equivalence."
+ [data-as-string]
+ (let [data (parse-string data-as-string)
+ data-stream (parse-stream (io/reader (ByteArrayInputStream. (to-bytes data-as-string))))
+ output-stream (ByteArrayOutputStream.)
+ writer (OutputStreamWriter. output-stream)
+ _ (generate-stream writer data)
+ reader (ByteArrayInputStream. (.toByteArray output-stream))]
+ (= data ;; string -> edn
+ (parse-string (generate-string data)) ;; edn -> string -> edn
+ (parse-stream (io/reader reader)) ;; edn -> stream -> edn
+ ;; stream -> edn
+ data-stream)))
+
+(deftest roundtrip-test
+ (testing "Roundtrip test"
+ (is (roundtrip duplicate-keys-yaml))
+ (is (roundtrip hashes-of-lists-yaml))
+ (is (roundtrip inline-hash-yaml))
+ (is (roundtrip inline-list-yaml))
+ (is (roundtrip list-of-hashes-yaml))
+ (is (roundtrip list-yaml))
+ (is (roundtrip nested-hash-yaml))))
+
+(def indented-yaml "todo:
+ - name: Fix issue
+ responsible:
+ name: Rita
+")
+
+;; BB-TEST-PATCH - bb generates different indents
+#_(deftest indentation-test
+ (testing "Can use indicator-indent and indent to achieve desired indentation"
+ (is (not= indented-yaml (generate-string (parse-string indented-yaml)
+ :dumper-options {:flow-style :block})))
+ (is (= indented-yaml
+ (generate-string (parse-string indented-yaml)
+ :dumper-options {:indent 5
+ :indicator-indent 2
+ :flow-style :block})))))
diff --git a/test-resources/lib_tests/clojure/data/generators_test.clj b/test-resources/lib_tests/clojure/data/generators_test.clj
index 8fa89e88..00628d83 100644
--- a/test-resources/lib_tests/clojure/data/generators_test.clj
+++ b/test-resources/lib_tests/clojure/data/generators_test.clj
@@ -32,4 +32,4 @@
(gen/reservoir-sample 10 coll))
sample-2 (binding [gen/*rnd* (java.util.Random. n)]
(gen/reservoir-sample 10 coll))]
- (is (= sample-1 sample-2)))))
\ No newline at end of file
+ (is (= sample-1 sample-2)))))
diff --git a/test-resources/lib_tests/clojure/data/json_test.clj b/test-resources/lib_tests/clojure/data/json_test.clj
index 61ac8380..0bed8eb4 100644
--- a/test-resources/lib_tests/clojure/data/json_test.clj
+++ b/test-resources/lib_tests/clojure/data/json_test.clj
@@ -414,6 +414,7 @@
(is (= x (json/read-str (with-out-str (json/pprint x)))))))
(deftest pretty-print-nonescaped-unicode
+ ;; BB-TEST-PATCH: Windows compatability
(is (= (str "\"\u1234\u4567\"" (System/lineSeparator))
(with-out-str
(json/pprint "\u1234\u4567" :escape-unicode false)))))
diff --git a/test-resources/lib_tests/clojure/data/zip_test.clj b/test-resources/lib_tests/clojure/data/zip_test.clj
new file mode 100644
index 00000000..7b494678
--- /dev/null
+++ b/test-resources/lib_tests/clojure/data/zip_test.clj
@@ -0,0 +1,17 @@
+(ns clojure.data.zip-test
+ (:require [clojure.test :as t :refer [deftest is]]
+ [clojure.data.xml :as xml]
+ [clojure.zip :as zip]
+ [clojure.data.zip.xml :refer [attr attr= xml1->]]))
+
+(def data (str ""
+ " "
+ " "
+ ""))
+
+(deftest xml1-test
+ (let [xml (zip/xml-zip (xml/parse (java.io.StringReader. data)))]
+ (is (= "person"
+ (xml1-> xml :character [(attr= :name "alice")] (attr :type))))
+ (is (= "march hare"
+ (xml1-> xml :character [(attr= :type "animal")] (attr :name))))))
diff --git a/test-resources/lib_tests/clojure/term/colors_test.clj b/test-resources/lib_tests/clojure/term/colors_test.clj
new file mode 100644
index 00000000..863f3f15
--- /dev/null
+++ b/test-resources/lib_tests/clojure/term/colors_test.clj
@@ -0,0 +1,29 @@
+(ns clojure.term.colors-test
+ (:require [clojure.test :refer :all]
+ [clojure.term.colors :refer :all]))
+
+(defn get-fn
+ "get function from symbol in clojure.term.colors package"
+ [fname]
+ (ns-resolve (the-ns 'clojure.term.colors)
+ (-> fname name symbol)))
+
+(defn test-colors-from-map
+ "test print colors from a color map"
+ [colormap & more]
+ (eval
+ `(do ~@(map (fn [[color _]]
+ `(println ((get-fn ~color)
+ (name ~color) (str ~@more))))
+ colormap))))
+
+(deftest color-test
+ (testing "Testing colors."
+ (test-colors-from-map *colors* " foreground.")
+ (test-colors-from-map *highlights* " background.")
+ (test-colors-from-map *attributes* " attributes."))
+
+ (testing "Testing disable colors."
+ (binding [*disable-colors* true]
+ (println \newline "When disabled-colors is set ...")
+ (test-colors-from-map *colors* " foreground."))))
diff --git a/test-resources/lib_tests/clojure/test_clojure/instr.clj b/test-resources/lib_tests/clojure/test_clojure/instr.clj
index e6b1e238..670a1e3c 100644
--- a/test-resources/lib_tests/clojure/test_clojure/instr.clj
+++ b/test-resources/lib_tests/clojure/test_clojure/instr.clj
@@ -95,6 +95,7 @@
(is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform to spec" (no-kwargs-fn 1 :not-num)))
(is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform to spec" (no-kwargs-fn 1 2 :not-num 3)))
+ ;; BB-TEST-PATCH: bb gets sci internals instead
#_(testing "that the ex-info data looks correct"
(try (fail-no-kwargs 1 :not-num)
(catch Exception ei
@@ -151,14 +152,15 @@
(is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform to spec" (kwargs-fn 1 :not-num)))
(is (thrown-with-msg? clojure.lang.ExceptionInfo #"did not conform to spec" (kwargs-fn 1 2 :a 1 {:b :not-num})))
+ ;; BB-TEST-PATCH: bb gets sci internals instead
#_(testing "that the ex-info data looks correct"
- (try (fail-kwargs 1 :not-num)
- (catch Exception ei
- (is (= 'clojure.test-clojure.instr/fail-kwargs (-> ei ex-data :clojure.spec.test.alpha/caller :var-scope)))))
+ (try (fail-kwargs 1 :not-num)
+ (catch Exception ei
+ (is (= 'clojure.test-clojure.instr/fail-kwargs (-> ei ex-data :clojure.spec.test.alpha/caller :var-scope)))))
- (try (fail-kwargs 1 2 :a 1 {:b :not-num})
- (catch Exception ei
- (is (= 'clojure.test-clojure.instr/fail-kwargs (-> ei ex-data :clojure.spec.test.alpha/caller :var-scope)))))))
+ (try (fail-kwargs 1 2 :a 1 {:b :not-num})
+ (catch Exception ei
+ (is (= 'clojure.test-clojure.instr/fail-kwargs (-> ei ex-data :clojure.spec.test.alpha/caller :var-scope)))))))
(testing "that the uninstrumented kwargs function operates as the raw function"
(stest/unstrument `kwargs-fn)
diff --git a/test-resources/lib_tests/clojure/test_clojure/spec.clj b/test-resources/lib_tests/clojure/test_clojure/spec.clj
index 290ad43d..8c6fb7e6 100644
--- a/test-resources/lib_tests/clojure/test_clojure/spec.clj
+++ b/test-resources/lib_tests/clojure/test_clojure/spec.clj
@@ -1,11 +1,3 @@
-; Copyright (c) Rich Hickey. All rights reserved.
-; The use and distribution terms for this software are covered by the
-; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
-; which can be found in the file epl-v10.html at the root of this distribution.
-; By using this software in any fashion, you are agreeing to be bound by
-; the terms of this license.
-; You must not remove this notice, or any other, from this software.
-
(ns clojure.test-clojure.spec
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]
@@ -167,6 +159,7 @@
(is (= (s/describe odd?) 'odd?))
(is (= (s/form odd?) 'clojure.core/odd?))
+ ;; BB-TEST-PATCH: Returns sci internal
#_(is (= (s/describe #(odd? %)) ::s/unknown))
#_(is (= (s/form #(odd? %)) ::s/unknown)))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj b/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj
new file mode 100644
index 00000000..5aedb27f
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/dependency_test.clj
@@ -0,0 +1,315 @@
+(ns clojure.tools.namespace.dependency-test
+ (:use clojure.test
+ clojure.tools.namespace.dependency))
+
+;; building a graph like:
+;;
+;; :a
+;; / |
+;; :b |
+;; \ |
+;; :c
+;; |
+;; :d
+;;
+(def g1 (-> (graph)
+ (depend :b :a) ; "B depends on A"
+ (depend :c :b) ; "C depends on B"
+ (depend :c :a) ; "C depends on A"
+ (depend :d :c))) ; "D depends on C"
+
+;; 'one 'five
+;; | |
+;; 'two |
+;; / \ |
+;; / \ |
+;; / \ /
+;; 'three 'four
+;; | /
+;; 'six /
+;; | /
+;; | /
+;; | /
+;; 'seven
+;;
+(def g2 (-> (graph)
+ (depend 'two 'one)
+ (depend 'three 'two)
+ (depend 'four 'two)
+ (depend 'four 'five)
+ (depend 'six 'three)
+ (depend 'seven 'six)
+ (depend 'seven 'four)))
+
+;; :level0
+;; / | | \
+;; ----- | | -----
+;; / | | \
+;; :level1a :level1b :level1c :level1d
+;; \ | | /
+;; ----- | | -----
+;; \ | | /
+;; :level2
+;; / | | \
+;; ----- | | -----
+;; / | | \
+;; :level3a :level3b :level3c :level3d
+;; \ | | /
+;; ----- | | -----
+;; \ | | /
+;; :level4
+;;
+;; ... and so on in a repeating pattern like that, up to :level26
+
+(def g3 (-> (graph)
+ (depend :level1a :level0)
+ (depend :level1b :level0)
+ (depend :level1c :level0)
+ (depend :level1d :level0)
+ (depend :level2 :level1a)
+ (depend :level2 :level1b)
+ (depend :level2 :level1c)
+ (depend :level2 :level1d)
+
+ (depend :level3a :level2)
+ (depend :level3b :level2)
+ (depend :level3c :level2)
+ (depend :level3d :level2)
+ (depend :level4 :level3a)
+ (depend :level4 :level3b)
+ (depend :level4 :level3c)
+ (depend :level4 :level3d)
+
+ (depend :level5a :level4)
+ (depend :level5b :level4)
+ (depend :level5c :level4)
+ (depend :level5d :level4)
+ (depend :level6 :level5a)
+ (depend :level6 :level5b)
+ (depend :level6 :level5c)
+ (depend :level6 :level5d)
+
+ (depend :level7a :level6)
+ (depend :level7b :level6)
+ (depend :level7c :level6)
+ (depend :level7d :level6)
+ (depend :level8 :level7a)
+ (depend :level8 :level7b)
+ (depend :level8 :level7c)
+ (depend :level8 :level7d)
+
+ (depend :level9a :level8)
+ (depend :level9b :level8)
+ (depend :level9c :level8)
+ (depend :level9d :level8)
+ (depend :level10 :level9a)
+ (depend :level10 :level9b)
+ (depend :level10 :level9c)
+ (depend :level10 :level9d)
+
+ (depend :level11a :level10)
+ (depend :level11b :level10)
+ (depend :level11c :level10)
+ (depend :level11d :level10)
+ (depend :level12 :level11a)
+ (depend :level12 :level11b)
+ (depend :level12 :level11c)
+ (depend :level12 :level11d)
+
+ (depend :level13a :level12)
+ (depend :level13b :level12)
+ (depend :level13c :level12)
+ (depend :level13d :level12)
+ (depend :level14 :level13a)
+ (depend :level14 :level13b)
+ (depend :level14 :level13c)
+ (depend :level14 :level13d)
+
+ (depend :level15a :level14)
+ (depend :level15b :level14)
+ (depend :level15c :level14)
+ (depend :level15d :level14)
+ (depend :level16 :level15a)
+ (depend :level16 :level15b)
+ (depend :level16 :level15c)
+ (depend :level16 :level15d)
+
+ (depend :level17a :level16)
+ (depend :level17b :level16)
+ (depend :level17c :level16)
+ (depend :level17d :level16)
+ (depend :level18 :level17a)
+ (depend :level18 :level17b)
+ (depend :level18 :level17c)
+ (depend :level18 :level17d)
+
+ (depend :level19a :level18)
+ (depend :level19b :level18)
+ (depend :level19c :level18)
+ (depend :level19d :level18)
+ (depend :level20 :level19a)
+ (depend :level20 :level19b)
+ (depend :level20 :level19c)
+ (depend :level20 :level19d)
+
+ (depend :level21a :level20)
+ (depend :level21b :level20)
+ (depend :level21c :level20)
+ (depend :level21d :level20)
+ (depend :level22 :level21a)
+ (depend :level22 :level21b)
+ (depend :level22 :level21c)
+ (depend :level22 :level21d)
+
+ (depend :level23a :level22)
+ (depend :level23b :level22)
+ (depend :level23c :level22)
+ (depend :level23d :level22)
+ (depend :level24 :level23a)
+ (depend :level24 :level23b)
+ (depend :level24 :level23c)
+ (depend :level24 :level23d)
+
+ (depend :level25a :level24)
+ (depend :level25b :level24)
+ (depend :level25c :level24)
+ (depend :level25d :level24)
+ (depend :level26 :level25a)
+ (depend :level26 :level25b)
+ (depend :level26 :level25c)
+ (depend :level26 :level25d)))
+
+(deftest t-transitive-dependencies
+ (is (= #{:a :c :b}
+ (transitive-dependencies g1 :d)))
+ (is (= '#{two four six one five three}
+ (transitive-dependencies g2 'seven))))
+
+(deftest t-transitive-dependencies-deep
+ (is (= #{:level0
+ :level1a :level1b :level1c :level1d
+ :level2
+ :level3a :level3b :level3c :level3d
+ :level4
+ :level5a :level5b :level5c :level5d
+ :level6
+ :level7a :level7b :level7c :level7d
+ :level8
+ :level9a :level9b :level9c :level9d
+ :level10
+ :level11a :level11b :level11c :level11d
+ :level12
+ :level13a :level13b :level13c :level13d
+ :level14
+ :level15a :level15b :level15c :level15d
+ :level16
+ :level17a :level17b :level17c :level17d
+ :level18
+ :level19a :level19b :level19c :level19d
+ :level20
+ :level21a :level21b :level21c :level21d
+ :level22
+ :level23a :level23b :level23c :level23d}
+ (transitive-dependencies g3 :level24)))
+ (is (= #{:level0
+ :level1a :level1b :level1c :level1d
+ :level2
+ :level3a :level3b :level3c :level3d
+ :level4
+ :level5a :level5b :level5c :level5d
+ :level6
+ :level7a :level7b :level7c :level7d
+ :level8
+ :level9a :level9b :level9c :level9d
+ :level10
+ :level11a :level11b :level11c :level11d
+ :level12
+ :level13a :level13b :level13c :level13d
+ :level14
+ :level15a :level15b :level15c :level15d
+ :level16
+ :level17a :level17b :level17c :level17d
+ :level18
+ :level19a :level19b :level19c :level19d
+ :level20
+ :level21a :level21b :level21c :level21d
+ :level22
+ :level23a :level23b :level23c :level23d
+ :level24
+ :level25a :level25b :level25c :level25d}
+ (transitive-dependencies g3 :level26))))
+
+
+(deftest t-transitive-dependents
+ (is (= '#{four seven}
+ (transitive-dependents g2 'five)))
+ (is (= '#{four seven six three}
+ (transitive-dependents g2 'two))))
+
+(defn- before?
+ "True if x comes before y in an ordered collection."
+ [coll x y]
+ (loop [[item & more] (seq coll)]
+ (cond (nil? item) true ; end of the seq
+ (= x item) true ; x comes first
+ (= y item) false
+ :else (recur more))))
+
+(deftest t-before
+ (is (true? (before? [:a :b :c :d] :a :b)))
+ (is (true? (before? [:a :b :c :d] :b :c)))
+ (is (false? (before? [:a :b :c :d] :d :c)))
+ (is (false? (before? [:a :b :c :d] :c :a))))
+
+(deftest t-topo-comparator-1
+ (let [sorted (sort (topo-comparator g1) [:d :a :b :foo])]
+ (are [x y] (before? sorted x y)
+ :a :b
+ :a :d
+ :a :foo
+ :b :d
+ :b :foo
+ :d :foo)))
+
+(deftest t-topo-comparator-2
+ (let [sorted (sort (topo-comparator g2) '[three seven nine eight five])]
+ (are [x y] (before? sorted x y)
+ 'three 'seven
+ 'three 'eight
+ 'three 'nine
+ 'five 'eight
+ 'five 'nine
+ 'seven 'eight
+ 'seven 'nine)))
+
+(deftest t-topo-sort
+ (let [sorted (topo-sort g2)]
+ (are [x y] (before? sorted x y)
+ 'one 'two
+ 'one 'three
+ 'one 'four
+ 'one 'six
+ 'one 'seven
+ 'two 'three
+ 'two 'four
+ 'two 'six
+ 'two 'seven
+ 'three 'six
+ 'three 'seven
+ 'four 'seven
+ 'five 'four
+ 'five 'seven
+ 'six 'seven)))
+
+(deftest t-no-cycles
+ (is (thrown? Exception
+ (-> (graph)
+ (depend :a :b)
+ (depend :b :c)
+ (depend :c :a)))))
+
+(deftest t-no-self-cycles
+ (is (thrown? Exception
+ (-> (graph)
+ (depend :a :b)
+ (depend :a :a)))))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj b/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj
new file mode 100644
index 00000000..60e56427
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/dir_test.clj
@@ -0,0 +1,20 @@
+(ns clojure.tools.namespace.dir-test
+ (:require [clojure.test :refer [deftest is]]
+ [clojure.tools.namespace.test-helpers :as help]
+ [clojure.tools.namespace.dir :as dir])
+ (:import
+ (java.io File)))
+
+;; Only run this test on Java 1.7+, where java.nio.file.Files is available.
+(when (try (Class/forName "java.nio.file.Files")
+ (catch ClassNotFoundException _ false))
+ (deftest t-scan-by-canonical-path
+ (let [dir (help/create-temp-dir "t-scan-by-canonical-path")
+ main-clj (help/create-source dir 'example.main :clj '[example.one])
+ one-cljc (help/create-source dir 'example.one :clj)
+ other-dir (help/create-temp-dir "t-scan-by-canonical-path-other")
+ link (File. other-dir "link")]
+ (java.nio.file.Files/createSymbolicLink (.toPath link) (.toPath dir)
+ (make-array java.nio.file.attribute.FileAttribute 0))
+ (is (= (::dir/files (dir/scan-dirs {} [dir]))
+ (::dir/files (dir/scan-dirs {} [link])))))))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/find_test.clj b/test-resources/lib_tests/clojure/tools/namespace/find_test.clj
new file mode 100644
index 00000000..d081e84a
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/find_test.clj
@@ -0,0 +1,29 @@
+(ns clojure.tools.namespace.find-test
+ (:require [clojure.test :refer [deftest is]]
+ [clojure.tools.namespace.test-helpers :as help]
+ [clojure.tools.namespace.find :as find])
+ (:import (java.io File)))
+
+(deftest t-find-clj-and-cljc-files
+ "main.clj depends on one.cljc which depends on two.clj.
+ two.cljs also exists but should not be returned"
+ (let [dir (help/create-temp-dir "t-find-clj-and-cljc-files")
+ main-clj (help/create-source dir 'example.main :clj '[example.one])
+ one-cljc (help/create-source dir 'example.one :cljc '[example.two])
+ two-clj (help/create-source dir 'example.two :clj)
+ two-cljs (help/create-source dir 'example.two :cljs)]
+ (is (help/same-files?
+ [main-clj one-cljc two-clj]
+ (find/find-sources-in-dir dir)))))
+
+(deftest t-find-cljs-and-cljc-files
+ "main.cljs depends on one.cljc which depends on two.cljs.
+ two.clj also exists but should not be returned"
+ (let [dir (help/create-temp-dir "t-find-cljs-and-cljc-files")
+ main-cljs (help/create-source dir 'example.main :cljs '[example.one])
+ one-cljc (help/create-source dir 'example.one :cljc '[example.two])
+ two-clj (help/create-source dir 'example.two :clj)
+ two-cljs (help/create-source dir 'example.two :cljs)]
+ (is (help/same-files?
+ [main-cljs one-cljc two-cljs]
+ (find/find-sources-in-dir dir find/cljs)))))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/move_test.clj b/test-resources/lib_tests/clojure/tools/namespace/move_test.clj
new file mode 100644
index 00000000..47de7ac4
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/move_test.clj
@@ -0,0 +1,52 @@
+(ns clojure.tools.namespace.move-test
+ (:require [clojure.java.io :as io]
+ [clojure.test :refer [deftest is]]
+ [clojure.tools.namespace.move :refer [move-ns]]
+ [clojure.tools.namespace.test-helpers :as help])
+ (:import (java.io File)))
+
+(defn- create-file-one [dir]
+ (help/create-source dir 'example.one :clj
+ '[example.two example.three]
+ '[(defn foo []
+ (example.a.four/foo))]))
+
+(defn- create-file-two [dir]
+ (help/create-source dir 'example.two :clj
+ '[example.three example.a.four]))
+
+(defn- create-file-three [dir]
+ (help/create-source dir 'example.three :clj
+ '[example.five]))
+
+(defn- create-file-four [dir]
+ (help/create-source dir 'example.a.four :clj))
+
+(deftest t-move-ns
+ (let [temp-dir (help/create-temp-dir "tools-namespace-t-move-ns")
+ src-dir (io/file temp-dir "src")
+ example-dir (io/file temp-dir "src" "example")
+ file-one (create-file-one src-dir)
+ file-two (create-file-two src-dir)
+ file-three (create-file-three src-dir)
+ old-file-four (create-file-four src-dir)
+ new-file-four (io/file example-dir "b" "four.clj")]
+
+ (let [file-three-last-modified (.lastModified file-three)]
+
+ (Thread/sleep 1500) ;; ensure file timestamps are different
+
+ (move-ns 'example.a.four 'example.b.four src-dir [src-dir])
+
+ (is (.exists new-file-four)
+ "new file should exist")
+ (is (not (.exists old-file-four))
+ "old file should not exist")
+ (is (not (.exists (.getParentFile old-file-four)))
+ "old empty directory should not exist")
+ (is (= file-three-last-modified (.lastModified file-three))
+ "unaffected file should not have been modified")
+ (is (not-any? #(.contains (slurp %) "example.a.four")
+ [file-one file-two file-three new-file-four]))
+ (is (every? #(.contains (slurp %) "example.b.four")
+ [file-one file-two new-file-four])))))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj b/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj
new file mode 100644
index 00000000..b6ac6aaa
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/parse_test.clj
@@ -0,0 +1,210 @@
+(ns clojure.tools.namespace.parse-test
+ (:use [clojure.test :only (deftest is)]
+ [clojure.tools.namespace.parse :only (deps-from-ns-decl
+ read-ns-decl)]))
+
+(def ns-decl-prefix-list
+ '(ns com.example.one
+ (:require (com.example two
+ [three :as three]
+ [four :refer (a b)])
+ (com.example.sub [five :as five]
+ six))
+ (:use (com.example seven
+ [eight :as eight]
+ (nine :only (c d))
+ [ten]))))
+
+;; Some people like to write prefix lists as vectors, not lists. The
+;; use/require functions accept this form.
+(def ns-decl-prefix-list-as-vector
+ '(ns com.example.one
+ (:require [com.example
+ two
+ [three :as three]
+ [four :refer (a b)]]
+ [com.example.sub
+ [five :as five]
+ six])
+ (:use [com.example
+ seven
+ [eight :as eight]
+ (nine :only (c d))
+ [ten]])))
+
+(def ns-decl-prefix-list-clauses-as-vectors
+ "Sometimes people even write the clauses inside ns as vectors, which
+ clojure.core/ns allows. See TNS-21."
+ '(ns com.example.one
+ [:require [com.example
+ two
+ [three :as three]
+ [four :refer (a b)]]
+ [com.example.sub
+ [five :as five]
+ six]]
+ [:use [com.example
+ seven
+ [eight :as eight]
+ (nine :only (c d))
+ [ten]]]))
+
+(def deps-from-prefix-list
+ '#{com.example.two
+ com.example.three
+ com.example.four
+ com.example.sub.five
+ com.example.sub.six
+ com.example.seven
+ com.example.eight
+ com.example.nine
+ com.example.ten})
+
+(deftest t-prefix-list
+ (is (= deps-from-prefix-list
+ (deps-from-ns-decl ns-decl-prefix-list))))
+
+(deftest t-prefix-list-as-vector
+ (is (= deps-from-prefix-list
+ (deps-from-ns-decl ns-decl-prefix-list-as-vector))))
+
+(deftest t-prefix-list-clauses-as-vectors
+ (is (= deps-from-prefix-list
+ (deps-from-ns-decl ns-decl-prefix-list-clauses-as-vectors))))
+
+(deftest t-no-deps-returns-empty-set
+ (is (= #{} (deps-from-ns-decl '(ns com.example.one)))))
+
+(def multiple-ns-decls
+ '((ns one)
+ (ns two (:require one))
+ (ns three (:require [one :as o] [two :as t]))))
+
+(def multiple-ns-decls-string
+" (println \"Code before first ns\")
+ (ns one)
+ (println \"Some code\")
+ (defn hello-world [] \"Hello, World!\")
+ (ns two (:require one))
+ (println \"Some more code\")
+ (ns three (:require [one :as o] [two :as t]))")
+
+(deftest t-read-multiple-ns-decls
+ (with-open [rdr (clojure.lang.LineNumberingPushbackReader.
+ (java.io.PushbackReader.
+ (java.io.StringReader. multiple-ns-decls-string)))]
+ (is (= multiple-ns-decls
+ (take-while identity (repeatedly #(read-ns-decl rdr)))))))
+
+(def ns-docstring-example
+ "The example ns declaration used in the docstring of clojure.core/ns"
+ '(ns foo.bar
+ (:refer-clojure :exclude [ancestors printf])
+ (:require (clojure.contrib sql combinatorics))
+ (:use (my.lib this that))
+ (:import (java.util Date Timer Random)
+ (java.sql Connection Statement))))
+
+(def deps-from-ns-docstring-example
+ '#{clojure.contrib.sql
+ clojure.contrib.combinatorics
+ my.lib.this
+ my.lib.that})
+
+(deftest t-ns-docstring-example
+ (is (= deps-from-ns-docstring-example
+ (deps-from-ns-decl ns-docstring-example))))
+
+(def require-docstring-example
+ "The example ns declaration used in the docstring of
+ clojure.core/require"
+ '(ns (:require (clojure zip [set :as s]))))
+
+(def deps-from-require-docstring-example
+ '#{clojure.zip
+ clojure.set})
+
+(deftest t-require-docstring-example
+ (is (= deps-from-require-docstring-example
+ (deps-from-ns-decl require-docstring-example))))
+
+(def multiple-clauses
+ "Example showing more than one :require or :use clause in one ns
+ declaration, which clojure.core/ns allows."
+ '(ns foo.bar
+ (:require com.example.one)
+ (:import java.io.File)
+ (:require (com.example two three))
+ (:use (com.example [four :only [x]]))
+ (:use (com.example (five :only [x])))))
+
+(def deps-from-multiple-clauses
+ '#{com.example.one
+ com.example.two
+ com.example.three
+ com.example.four
+ com.example.five})
+
+(deftest t-deps-from-multiple-clauses
+ (is (= deps-from-multiple-clauses
+ (deps-from-ns-decl multiple-clauses))))
+
+(def clauses-without-keywords
+ "Example of require/use clauses with symbols instead of keywords,
+ which clojure.core/ns allows."
+ '(ns foo.bar
+ (require com.example.one)
+ (import java.io.File)
+ (use (com.example (prefixes (two :only [x])
+ three)))))
+
+(def deps-from-clauses-without-keywords
+ '#{com.example.one
+ com.example.prefixes.two
+ com.example.prefixes.three})
+
+(deftest t-clauses-without-keywords
+ (is (= deps-from-clauses-without-keywords
+ (deps-from-ns-decl clauses-without-keywords))))
+
+(def reader-conditionals-string
+ "(ns com.examples.one
+ (:require #?(:clj clojure.string
+ :cljs goog.string)))")
+
+(deftest t-reader-conditionals
+ ;; TODO: the predicate wasn't added to bb yet, will come in version after 0.6.7
+ (when true #_(resolve 'clojure.core/reader-conditional?)
+ (let [actual (-> reader-conditionals-string
+ java.io.StringReader.
+ java.io.PushbackReader.
+ clojure.lang.LineNumberingPushbackReader.
+ read-ns-decl
+ deps-from-ns-decl)]
+ (is (= #{'clojure.string} actual)))))
+
+(def ns-with-npm-dependency
+ "(ns com.examples.one
+ (:require [\"foobar\"] [baz]))")
+
+(deftest npm-dependency
+ (let [actual (-> ns-with-npm-dependency
+ java.io.StringReader.
+ java.io.PushbackReader.
+ clojure.lang.LineNumberingPushbackReader.
+ read-ns-decl
+ deps-from-ns-decl)]
+ (is (= #{'baz} actual))))
+
+(def ns-with-require-macros
+ "(ns com.examples.one
+ (:require-macros [foo :refer [bar]]))")
+
+(deftest require-macros
+ (let [actual (-> ns-with-require-macros
+ java.io.StringReader.
+ java.io.PushbackReader.
+ clojure.lang.LineNumberingPushbackReader.
+ read-ns-decl
+ deps-from-ns-decl)]
+ (is (= #{'foo} actual))))
diff --git a/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj b/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj
new file mode 100644
index 00000000..62679e69
--- /dev/null
+++ b/test-resources/lib_tests/clojure/tools/namespace/test_helpers.clj
@@ -0,0 +1,82 @@
+(ns clojure.tools.namespace.test-helpers
+ "Utilities to help with testing files and namespaces."
+ (:require [clojure.java.io :as io]
+ [clojure.string :as string])
+ (:import (java.io Closeable File Writer PrintWriter)))
+
+(defn create-temp-dir
+ "Creates and returns a new temporary directory java.io.File."
+ [name]
+ (let [temp-file (File/createTempFile name nil)]
+ (.delete temp-file)
+ (.mkdirs temp-file)
+ (println "Temporary directory" (.getAbsolutePath temp-file))
+ temp-file))
+
+(defn- write-contents
+ "Writes contents into writer. Strings are written as-is via println,
+ other types written as by prn."
+ [^Writer writer contents]
+ {:pre [(sequential? contents)]}
+ (binding [*out* (PrintWriter. writer)]
+ (doseq [content contents]
+ (if (string? content)
+ (println content)
+ (prn content))
+ (newline))))
+
+(defn create-file
+ "Creates a file from a vector of path elements. Writes contents into
+ the file. Elements of contents may be data, written via prn, or
+ strings, written directly."
+ [path contents]
+ {:pre [(vector? path)]}
+ (let [^File file (apply io/file path)]
+ (when-let [parent (.getParentFile file)]
+ (.mkdirs parent))
+ (with-open [wtr (io/writer file)]
+ (write-contents wtr contents))
+ file))
+
+(defn- sym->path
+ "Converts a symbol name into a vector of path parts, not including
+ file extension."
+ [symbol]
+ {:pre [(symbol? symbol)]
+ :post [(vector? %)]}
+ (-> (name symbol)
+ (string/replace \- \_)
+ (string/split #"\.")))
+
+(defn- source-path
+ "Returns a vector of path components for namespace named sym,
+ with given file extension (keyword)."
+ [sym extension]
+ (let [path (sym->path sym)
+ basename (peek path)
+ filename (str basename \. (name extension))]
+ (conj (pop path) filename)))
+
+(defn create-source
+ "Creates a file at the correct path under base-dir for a namespace
+ named sym, with file extension (keyword), containing a ns
+ declaration which :require's the dependencies (symbols). Optional
+ contents written after the ns declaration as by write-contents."
+ ([base-dir sym extension]
+ (create-source base-dir sym extension nil nil))
+ ([base-dir sym extension dependencies]
+ (create-source base-dir sym extension dependencies nil))
+ ([base-dir sym extension dependencies contents]
+ (let [full-path (into [base-dir] (source-path sym extension))
+ ns-decl (if (seq dependencies)
+ (list 'ns sym (list* :require dependencies))
+ (list 'ns sym))]
+ (create-file full-path (into [ns-decl] contents)))))
+
+(defn same-files?
+ "True if files-a and files-b contain the same canonical File's,
+ regardless of order."
+ [files-a files-b]
+ (= (sort (map #(.getCanonicalPath ^File %) files-a))
+ (sort (map #(.getCanonicalPath ^File %) files-b))))
+
diff --git a/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj b/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj
new file mode 100644
index 00000000..1792beae
--- /dev/null
+++ b/test-resources/lib_tests/cognitect/test_runner/sample_property_test.clj
@@ -0,0 +1,10 @@
+(ns cognitect.test-runner.sample-property-test
+ (:require [clojure.test.check :as tc]
+ [clojure.test.check.generators :as gen]
+ [clojure.test.check.properties :as prop]
+ [clojure.test.check.clojure-test :refer [defspec]]))
+
+(defspec first-element-is-min-after-sorting 100
+ (prop/for-all [v (gen/not-empty (gen/vector gen/int))]
+ (= (apply min v)
+ (first (sort v)))))
diff --git a/test-resources/lib_tests/cognitect/test_runner/samples_test.clj b/test-resources/lib_tests/cognitect/test_runner/samples_test.clj
new file mode 100644
index 00000000..e4ec385c
--- /dev/null
+++ b/test-resources/lib_tests/cognitect/test_runner/samples_test.clj
@@ -0,0 +1,14 @@
+(ns cognitect.test-runner.samples-test
+ (:require [clojure.test :as t :refer [deftest is testing]]))
+
+(deftest math-works
+ (testing "basic addition and subtraction"
+ (is (= 42 (+ 40 2)))
+ (is (= 42 (- 44 2)))))
+
+(deftest ^:integration test-i
+ (is (= 1 1)))
+
+
+
+
diff --git a/test-resources/lib_tests/cognitect/test_runner_test.clj b/test-resources/lib_tests/cognitect/test_runner_test.clj
new file mode 100644
index 00000000..e1ea8deb
--- /dev/null
+++ b/test-resources/lib_tests/cognitect/test_runner_test.clj
@@ -0,0 +1,23 @@
+(ns cognitect.test-runner-test
+ (:require
+ [clojure.test :refer :all]
+ [cognitect.test-runner :as tr]))
+
+(deftest ns-filters
+ (are [ns-names ns-regexes available selected]
+ (= selected (filter (#'tr/ns-filter {:namespace ns-names :namespace-regex ns-regexes}) available))
+
+ ;; default settings (no -n / -r, use default for -r)
+ nil nil nil []
+ nil nil '[ns1-test ns2-test] '[ns1-test ns2-test]
+ nil nil '[ns1-test ns2-test ns3 ns4 ns5] '[ns1-test ns2-test]
+
+ ;; specific namespaces
+ '#{ns3} nil '[ns1-test ns2-test] '[]
+ '#{ns3 ns4} nil '[ns1-test ns2-test ns3 ns4 ns5] '[ns3 ns4]
+
+ ;; regexes
+ nil #{#"ns1.*" #"ns3"} '[ns1-test ns2-test ns3 ns4] '[ns1-test ns3]
+
+ ;; both
+ '#{ns3} '#{#"ns1.*"} '[ns1-test ns2-test ns3 ns4] '[ns1-test ns3]))
\ No newline at end of file
diff --git a/test-resources/lib_tests/com/stuartsierra/component_test.clj b/test-resources/lib_tests/com/stuartsierra/component_test.clj
new file mode 100644
index 00000000..00602462
--- /dev/null
+++ b/test-resources/lib_tests/com/stuartsierra/component_test.clj
@@ -0,0 +1,307 @@
+(ns com.stuartsierra.component-test
+ (:require [clojure.test :refer (deftest is are)]
+ [clojure.set :refer (map-invert)]
+ [com.stuartsierra.component :as component]))
+
+(def ^:dynamic *log* nil)
+
+(defn- log [& args]
+ (when (thread-bound? #'*log*)
+ (set! *log* (conj *log* args))))
+
+(defn- ordering
+ "Given an ordered collection of messages, returns a map from the
+ head of each message to its index position in the collection."
+ [log]
+ (into {} (map-indexed (fn [i [message & _]] [message i]) log)))
+
+(defn before?
+ "In the collection of messages, does the message beginning with
+ symbol a come before the message begging with symbol b?"
+ [log sym-a sym-b]
+ (let [order (ordering log)]
+ (< (get order sym-a) (get order sym-b))))
+
+(defn started? [component]
+ (true? (::started? component)))
+
+(defn stopped? [component]
+ (false? (::started? component)))
+
+(defrecord ComponentA [state]
+ component/Lifecycle
+ (start [this]
+ (log 'ComponentA.start this)
+ (assoc this ::started? true))
+ (stop [this]
+ (log 'ComponentA.stop this)
+ (assoc this ::started? false)))
+
+(defn component-a []
+ (->ComponentA (rand-int Integer/MAX_VALUE)))
+
+(defrecord ComponentB [state a]
+ component/Lifecycle
+ (start [this]
+ (log 'ComponentB.start this)
+ (assert (started? a))
+ (assoc this ::started? true))
+ (stop [this]
+ (log 'ComponentB.stop this)
+ (assert (started? a))
+ (assoc this ::started? false)))
+
+(defn component-b []
+ (component/using
+ (map->ComponentB {:state (rand-int Integer/MAX_VALUE)})
+ [:a]))
+
+(defrecord ComponentC [state a b]
+ component/Lifecycle
+ (start [this]
+ (log 'ComponentC.start this)
+ (assert (started? a))
+ (assert (started? b))
+ (assoc this ::started? true))
+ (stop [this]
+ (log 'ComponentC.stop this)
+ (assert (started? a))
+ (assert (started? b))
+ (assoc this ::started? false)))
+
+(defn component-c []
+ (component/using
+ (map->ComponentC {:state (rand-int Integer/MAX_VALUE)})
+ [:a :b]))
+
+(defrecord ComponentD [state my-c b]
+ component/Lifecycle
+ (start [this]
+ (log 'ComponentD.start this)
+ (assert (started? b))
+ (assert (started? my-c))
+ (assoc this ::started? true))
+ (stop [this]
+ (log 'ComponentD.stop this)
+ (assert (started? b))
+ (assert (started? my-c))
+ (assoc this ::started? false)))
+
+(defn component-d []
+ (map->ComponentD {:state (rand-int Integer/MAX_VALUE)}))
+
+(defrecord ComponentE [state]
+ component/Lifecycle
+ (start [this]
+ (log 'ComponentE.start this)
+ (assoc this ::started? true))
+ (stop [this]
+ (log 'ComponentE.stop this)
+ (assoc this ::started? false)))
+
+(defn component-e []
+ (map->ComponentE {:state (rand-int Integer/MAX_VALUE)}))
+
+(defrecord System1 [d a e c b] ; deliberately scrambled order
+ component/Lifecycle
+ (start [this]
+ (log 'System1.start this)
+ (component/start-system this))
+ (stop [this]
+ (log 'System1.stop this)
+ (component/stop-system this)))
+
+(defn system-1 []
+ (map->System1 {:a (component-a)
+ :b (component-b)
+ :c (component-c)
+ :d (component/using (component-d)
+ {:b :b
+ :my-c :c})
+ :e (component-e)}))
+
+(defmacro with-log [& body]
+ `(binding [*log* []]
+ ~@body
+ *log*))
+
+(deftest components-start-in-order
+ (let [log (with-log (component/start (system-1)))]
+ (are [k1 k2] (before? log k1 k2)
+ 'ComponentA.start 'ComponentB.start
+ 'ComponentA.start 'ComponentC.start
+ 'ComponentB.start 'ComponentC.start
+ 'ComponentC.start 'ComponentD.start
+ 'ComponentB.start 'ComponentD.start)))
+
+(deftest all-components-started
+ (let [system (component/start (system-1))]
+ (doseq [component (vals system)]
+ (is (started? component)))))
+
+(deftest all-components-stopped
+ (let [system (component/stop (component/start (system-1)))]
+ (doseq [component (vals system)]
+ (is (stopped? component)))))
+
+(deftest dependencies-satisfied
+ (let [system (component/start (component/start (system-1)))]
+ (are [keys] (started? (get-in system keys))
+ [:b :a]
+ [:c :a]
+ [:c :b]
+ [:d :my-c])))
+
+(defrecord ErrorStartComponentC [state error a b]
+ component/Lifecycle
+ (start [this]
+ (throw error))
+ (stop [this]
+ this))
+
+(defn error-start-c [error]
+ (component/using
+ (map->ErrorStartComponentC {:error error})
+ [:a :b]))
+
+(defn setup-error
+ ([]
+ (setup-error (ex-info "Boom!" {})))
+ ([error]
+ (try (component/start
+ (assoc (system-1) :c (error-start-c error)))
+ (catch Exception e e))))
+
+(deftest error-thrown-with-partial-system
+ (let [ex (setup-error)]
+ (is (started? (-> ex ex-data :system :b :a)))))
+
+(deftest error-thrown-with-component-dependencies
+ (let [ex (setup-error)]
+ (is (started? (-> ex ex-data :component :a)))
+ (is (started? (-> ex ex-data :component :b)))))
+
+(deftest error-thrown-with-cause
+ (let [error (ex-info "Boom!" {})
+ ex (setup-error error)]
+ (is (identical? error (.getCause ^Exception ex)))))
+
+(deftest error-is-from-component
+ (let [error (ex-info "Boom!" {})
+ ex (setup-error error)]
+ (is (component/ex-component? ex))))
+
+(deftest error-is-not-from-component
+ (is (not (component/ex-component? (ex-info "Boom!" {})))))
+
+(deftest remove-components-from-error
+ (let [error (ex-info (str (rand-int Integer/MAX_VALUE)) {})
+ ^Exception ex (setup-error error)
+ ^Exception ex-without (component/ex-without-components ex)]
+ (is (contains? (ex-data ex) :component))
+ (is (contains? (ex-data ex) :system))
+ (is (not (contains? (ex-data ex-without) :component)))
+ (is (not (contains? (ex-data ex-without) :system)))
+ (is (= (.getMessage ex)
+ (.getMessage ex-without)))
+ (is (= (.getCause ex)
+ (.getCause ex-without)))
+ (is (java.util.Arrays/equals
+ (.getStackTrace ex)
+ (.getStackTrace ex-without)))))
+
+(defrecord System2b [one]
+ component/Lifecycle
+ (start [this]
+ (assert (started? (get-in one [:b :a])))
+ this)
+ (stop [this]
+ (assert (started? (get-in one [:b :a])))
+ this))
+
+(defn system-2 []
+ (component/system-map :alpha (system-1)
+ :beta (component/using (->System2b nil)
+ {:one :alpha})))
+
+(deftest composed-systems
+ (let [system (component/start (system-2))]
+ (is (started? (get-in system [:beta :one :d :my-c])))))
+
+(defn increment-all-components [system]
+ (component/update-system
+ system (keys system) update-in [:n] inc))
+
+(defn assert-increments [system]
+ (are [n keys] (= n (get-in system keys))
+ 11 [:a :n]
+ 11 [:b :a :n]
+ 11 [:c :a :n]
+ 11 [:c :b :a :n]
+ 11 [:e :d :b :a :n]
+ 21 [:b :n]
+ 21 [:c :b :n]
+ 21 [:d :b :n]
+ 31 [:c :n]
+ 41 [:d :n]
+ 51 [:e :n]))
+
+(deftest update-with-custom-function-on-maps
+ (let [system {:a {:n 10}
+ :b (component/using {:n 20} [:a])
+ :c (component/using {:n 30} [:a :b])
+ :d (component/using {:n 40} [:a :b])
+ :e (component/using {:n 50} [:b :c :d])}]
+ (assert-increments (increment-all-components system))))
+
+(deftest t-system-using
+ (let [dependency-map {:b [:a]
+ :c [:a :b]
+ :d {:a :a :b :b}
+ :e [:b :c :d]}
+ system {:a {:n 10}
+ :b {:n 20}
+ :c {:n 30}
+ :d {:n 40}
+ :e {:n 50}}
+ system (component/system-using system dependency-map)]
+ (assert-increments (increment-all-components system))))
+
+(defrecord ComponentWithoutLifecycle [state])
+
+;; BB-TEST-PATCH: No implementation of method errors for start and stop
+#_(deftest component-without-lifecycle
+ (let [c (->ComponentWithoutLifecycle nil)]
+ (is (= c (component/start c)))
+ (is (= c (component/stop c)))))
+
+(defrecord ComponentReturningNil [state]
+ component/Lifecycle
+ (start [this]
+ nil)
+ (stop [this]
+ nil))
+
+(deftest component-returning-nil
+ (let [a (->ComponentReturningNil nil)
+ s (component/system-map :a a :b (component-b))
+ e (try (component/start s)
+ false
+ (catch Exception e e))]
+ (is (= ::component/nil-component (:reason (ex-data e))))))
+
+(deftest missing-dependency-error
+ (let [system-key ::system-b
+ local-key ::local-b
+ a (component/using (component-a) {local-key system-key})
+ system (component/system-map
+ :a a)
+ e (try (component/start system)
+ false
+ (catch Exception e e))
+ data (ex-data e)]
+ (is (= ::component/missing-dependency (:reason data)))
+ (is (= system-key (:system-key data)))
+ (is (= local-key (:dependency-key data)))
+ (is (= a (:component data)))
+ (is (= system (:system data)))))
diff --git a/test-resources/lib_tests/component/component_test.clj b/test-resources/lib_tests/component/component_test.clj
deleted file mode 100644
index 32b715f8..00000000
--- a/test-resources/lib_tests/component/component_test.clj
+++ /dev/null
@@ -1,41 +0,0 @@
-(ns component.component-test
- (:require [clojure.test :refer [deftest is testing]]
- [com.stuartsierra.component :as component]))
-
-
-(def syslog (atom []))
-
-(defn log [msg]
- (swap! syslog conj msg))
-
-(defrecord FakeDB [state]
- component/Lifecycle
- (start [_]
- (log "start DB"))
- (stop [_]
- (log "stop DB")))
-
-(defrecord FakeApp [db]
- component/Lifecycle
- (start [_]
- (log "start app"))
- (stop [_]
- (log "stop app")))
-
-(defn base-app []
- (map->FakeApp {}))
-
-(def sm
- (component/system-map
- :db (->FakeDB :foo)
- :app (component/using (base-app) [:db])))
-
-(component/start sm)
-
-;; do useful stuff here
-
-(component/stop sm)
-
-(deftest ordering-test
- (testing "components are started and stopped in expected order"
- (is (= ["start DB" "start app" "stop app" "stop DB"] @syslog))))
diff --git a/test-resources/lib_tests/contajners/core_test.clj b/test-resources/lib_tests/contajners/core_test.clj
new file mode 100644
index 00000000..fb3aa8de
--- /dev/null
+++ b/test-resources/lib_tests/contajners/core_test.clj
@@ -0,0 +1,22 @@
+(ns contajners.core-test
+ (:require [clojure.test :as t]
+ [contajners.core :as c]))
+
+(t/deftest docker-tests
+ (let [image "busybox:musl"
+ client (c/client {:engine :docker
+ :version "v1.41"
+ :category :images
+ :conn {:uri "unix:///var/run/docker.sock"}})]
+ (t/testing "pull an image"
+ (c/invoke client
+ {:op :ImageCreate
+ :params {:fromImage image}})
+ (let [images (c/invoke client {:op :ImageList})]
+ (t/is (contains? (->> images
+ (mapcat :RepoTags)
+ (into #{}))
+ image)))
+ (c/invoke client
+ {:op :ImageDelete
+ :params {:name image}}))))
diff --git a/test-resources/lib_tests/contajners/impl_test.clj b/test-resources/lib_tests/contajners/impl_test.clj
new file mode 100644
index 00000000..58808220
--- /dev/null
+++ b/test-resources/lib_tests/contajners/impl_test.clj
@@ -0,0 +1,45 @@
+(ns contajners.impl-test
+ (:require
+ [clojure.test :as t]
+ [contajners.impl :as impl]))
+
+(t/deftest meta-cleanup
+ (t/testing "remove internal namespace"
+ (t/is (= [:foo]
+ (impl/remove-internal-meta [:contajners/foo :foo])))))
+
+(t/deftest param-gathering
+ (t/testing "gathering params as header query and path"
+ (t/is (= {:header {:a 1 :b 2}
+ :query {:c 3 :d 4}
+ :path {:e 5 :f 6}}
+ (reduce (partial impl/gather-params {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6})
+ {}
+ [{:name "a" :in :header}
+ {:name "b" :in :header}
+ {:name "c" :in :query}
+ {:name "d" :in :query}
+ {:name "e" :in :path}
+ {:name "f" :in :path}])))))
+
+(t/deftest body-serialization
+ (t/testing "body serialization when a map"
+ (t/is (= {:headers {"content-type" "application/json"}
+ :body "{\"a\":42}"}
+ (impl/maybe-serialize-body {:body {:a 42}}))))
+ (t/testing "body serialization when not a map"
+ (t/is (= {:body "yes"}
+ (impl/maybe-serialize-body {:body "yes"})))))
+
+(t/deftest path-interpolation
+ (t/testing "path interpolation"
+ (t/is (= "/a/{w}/b/41/42"
+ (impl/interpolate-path "/a/{w}/b/{x}/{y}" {:x 41 :y 42 :z 43})))))
+
+(t/deftest json-parsing
+ (t/testing "successful json parsing"
+ (t/is (= {:a 42}
+ (impl/try-json-parse "{\"a\":42}"))))
+ (t/testing "failed json parsing"
+ (t/is (= "yes"
+ (impl/try-json-parse "yes")))))
diff --git a/test-resources/lib_tests/cprop/smoke_test.clj b/test-resources/lib_tests/cprop/smoke_test.clj
new file mode 100644
index 00000000..9f97d66a
--- /dev/null
+++ b/test-resources/lib_tests/cprop/smoke_test.clj
@@ -0,0 +1,7 @@
+(ns cprop.smoke-test
+ (:require [clojure.test :as t :refer [deftest is]]
+ [cprop.core]
+ [cprop.source :refer [from-env]]))
+
+(deftest from-env-test
+ (println (:cprop-env (from-env))))
diff --git a/test-resources/lib_tests/docopt/core_test.clj b/test-resources/lib_tests/docopt/core_test.clj
new file mode 100644
index 00000000..a881b9b4
--- /dev/null
+++ b/test-resources/lib_tests/docopt/core_test.clj
@@ -0,0 +1,88 @@
+(ns docopt.core-test
+ (:require [cheshire.core :as json]
+ [clojure.string :as s]
+ [clojure.test :refer :all]
+ [docopt.core :as d]
+ [docopt.match :as m]))
+
+(def doc-block-regex
+ (let [doc-begin "r\\\"{3}"
+ doc-body "((?:\\\"{0,2}[^\\\"]+)*)"
+ separator "\\\"{3}\n+"
+ tests "((?:[^r]|r(?!\\\"{3}))*)"]
+ (re-pattern (str doc-begin doc-body separator tests))))
+
+(def test-block-regex
+ (let [input-begin "(?:\\A|\\n+)\\s*\\$\\s*prog"
+ input-body "(.*)"
+ separator "\\n"
+ tests "((?:.+\\n)*)"]
+ (re-pattern (str input-begin input-body separator tests))))
+
+(defn load-test-cases
+ "Loads language-agnostic docopt tests from file (such as testcases.docopt)."
+ [path]
+ (into [] (mapcat (fn [[_ doc tests]]
+ (map (fn [[_ args result]]
+ [doc (into [] (filter seq (s/split (or args "") #"\s+"))) (json/parse-string result)])
+ (re-seq test-block-regex tests)))
+ (re-seq doc-block-regex (s/replace (slurp path) #"#.*" "")))))
+
+(defn test-case-error-report
+ "Returns a report of all failed test cases"
+ [doc in out]
+ (let [docinfo (try (d/parse doc)
+ (catch Exception e (.getMessage e)))]
+ (if (string? docinfo)
+ (str "\n" (s/trim-newline doc) "\n" docinfo)
+ (let [result (or (m/match-argv docinfo in) "user-error")]
+ (when (not= result out)
+ (str "\n" (s/trim-newline doc)
+ "\n$ prog " (s/join " " in)
+ "\nexpected: " (json/generate-string out)
+ "\nobtained: " (json/generate-string result) "\n\n"))))))
+
+(defn valid?
+ "Validates all test cases found in the file named 'test-cases-file-name'."
+ [test-cases-file-name]
+ (let [test-cases (load-test-cases test-cases-file-name)]
+ (when-let [eseq (seq (remove nil? (map (partial apply test-case-error-report) test-cases)))]
+ (println "Failed" (count eseq) "/" (count test-cases) "tests loaded from '" test-cases-file-name "'.\n")
+ (throw (Exception. (apply str eseq))))
+ (println "Successfully passed" (count test-cases) "tests loaded from '" test-cases-file-name "'.\n")
+ true))
+
+(deftest docopt-test
+ (testing "2-arity version"
+ (is (= {"" "a"}
+ (d/docopt "usage: prog " ["a"]))))
+
+ (testing "3-arity version"
+ (is (= "a"
+ (d/docopt "usage: prog " ["a"] #(get % "")))))
+
+ (testing "4-arity version"
+ (is (= "usage: prog "
+ (d/docopt "usage: prog " [] identity identity))))
+
+ ;; Adding this test here since it seems testcases file doesn't support quoted args
+ (testing "should parse quoted args correctly"
+ (is (= {"-f" "a b"}
+ (d/docopt "usage: prog [options]\noptions: -f " ["-f" "a b"])))
+ (is (= {"--foo" "a\nb"}
+ (d/docopt "usage: prog [options]\noptions: --foo " ["--foo" "a\nb"])))
+ (is (= {"" "a b c "}
+ (d/docopt "usage: prog " ["a b c "])))
+ (is (= {"" "a\tb\nc"}
+ (d/docopt "usage: prog " ["a\tb\nc"])))
+ (binding [docopt.match/*sep-table* {\ "FOO"
+ \newline "BAR"
+ \tab "QUX"
+ \backspace "QUZ"}]
+ (is (= {"" "a b\nc\td\b"}
+ (d/docopt "usage: prog " ["aFOObBARcQUXdQUZ"]))))))
+
+(deftest language-agnostic-test
+ (is (valid? "https://raw.github.com/docopt/docopt/511d1c57b59cd2ed663a9f9e181b5160ce97e728/testcases.docopt"))
+ ;; BB-TEST-PATCH: Modified test path
+ (is (valid? "test-resources/lib_tests/docopt/extra_testcases.docopt")))
diff --git a/test-resources/lib_tests/docopt/extra_testcases.docopt b/test-resources/lib_tests/docopt/extra_testcases.docopt
new file mode 100644
index 00000000..c73eac15
--- /dev/null
+++ b/test-resources/lib_tests/docopt/extra_testcases.docopt
@@ -0,0 +1,57 @@
+# Should output the same things as docopt/docopt for language agnostic tests
+
+# Testing `--`
+
+r"""Usage: prog foo -- ...
+
+"""
+
+$ prog foo
+"user-error"
+
+$ prog foo -- --bar
+{"--":true, "": ["--bar"], "foo":true}
+
+r"""Usage: prog foo [--] ...
+
+"""
+
+$ prog foo
+"user-error"
+# Wrong, should be
+# {"foo": true, "--": false, "": []}
+
+$ prog foo -- --bar
+{"foo": true, "--": true, "": ["--bar"]}
+
+r"""Complex command
+
+Usage:
+ prog [options] -- ...
+ prog [options] -- ...
+ prog [options]
+ prog [options]
+
+Options:
+ -f --foo Foo
+ --bar Bar
+
+"""
+
+$ prog x y --foo
+{"--":false,"--bar":null,"--foo":true,"":[],"":null,"":null,"":null,"":null,"":"x","":"y"}
+
+$ prog a b c d
+{"--":false,"--bar":null,"--foo":false,"":[],"":"a","":"b","":"c","":"d","":null,"":null}
+
+$ prog a b c d --foo --bar bar
+{"--":false,"--bar":"bar","--foo":true,"":[],"":"a","":"b","":"c","":"d","":null,"":null}
+
+$ prog x y --bar bar -- extra
+{"--bar": "bar", "--foo": false, "": "x", "": "y", "--": true, "": ["extra"], "": null, "": null, "": null, "": null}
+
+$ prog a b c d --foo --bar bar -- extra
+{"--foo": true, "--bar": "bar", "": null, "": null, "--": true, "": ["extra"], "": "a", "": "b", "": "c", "": "d"}
+
+$ prog x y -- e1 e2 e3 e4
+{"--bar": null, "--foo": false, "": "x", "": "y", "--": true, "": ["e1", "e2", "e3", "e4"], "": null, "": null, "": null, "": null}
diff --git a/test-resources/lib_tests/doric/test/core.clj b/test-resources/lib_tests/doric/test/core.clj
index 21a327aa..777d2c28 100644
--- a/test-resources/lib_tests/doric/test/core.clj
+++ b/test-resources/lib_tests/doric/test/core.clj
@@ -2,8 +2,7 @@
(:refer-clojure :exclude [format name when])
(:use [doric.core]
[clojure.test]
- [doric.org :only [th td render]])
- (:require [clojure.string :as str]))
+ [doric.org :only [th td render]]))
(deftest test-title-case
(is (= "Foo" (title-case "foo")))
@@ -73,6 +72,7 @@
;; TODO (deftest test-body)
(deftest test-render
+ ;; BB-TEST-PATCH: Switch to set as .contains isn't supported in bb as it's atypical Clojure
(let [rendered (set (render [["1" "2"]["3" "4"]]))]
(is (contains? rendered "| 1 | 2 |"))
(is (contains? rendered "| 3 | 4 |"))
diff --git a/test-resources/lib_tests/doric/test/doctest.clj b/test-resources/lib_tests/doric/test/doctest.clj
new file mode 100644
index 00000000..f05f1c0f
--- /dev/null
+++ b/test-resources/lib_tests/doric/test/doctest.clj
@@ -0,0 +1,93 @@
+(ns doric.test.doctest
+ (:use [clojure.java.io :only [file]]
+ [clojure.test])
+ (:import (java.io PushbackReader StringReader)))
+
+(defn fenced-blocks
+ "detect and extract github-style fenced blocks in a file"
+ [s]
+ (map second
+ (re-seq #"(?m)(?s)^```clojure\n(.*?)\n^```" s)))
+
+(def prompt
+ ;; regex for finding 'foo.bar>' repl prompts
+ "(?m)\n*^\\S*>\\s*")
+
+(defn skip?
+ "is a result skippable?"
+ ;; if it's a comment, the answer is yes
+ [s]
+ (.startsWith s ";"))
+
+(defn reps
+ "given a string of read-eval-print sequences, separate the different
+ 'r-e-p's from each other"
+ [prompt s]
+ (rest (.split s prompt)))
+
+(defn markdown-tests
+ "extract all the tests from a markdown file"
+ [f]
+ (->> f
+ slurp
+ fenced-blocks
+ (mapcat (partial reps prompt))))
+
+(defn repl-tests
+ "extract all the tests from a repl-session-like file"
+ [f]
+ (->> f
+ slurp
+ (reps prompt)))
+
+(defn temp-ns
+ "create a temporary ns, and return its name"
+ []
+ (binding [*ns* *ns*]
+ (in-ns (gensym))
+ (use 'clojure.core)
+ ;; BB-TEST-PATCH: bb can't .getName on ns
+ (str *ns*)))
+
+(defn eval-in-ns
+ "evaluate a form inside the given ns-name"
+ [ns form]
+ (binding [*ns* *ns*]
+ (in-ns ns)
+ (eval form)))
+
+(defn run-doctest
+ "run a single doctest, reporting success or failure"
+ [file idx ns test]
+ (let [r (PushbackReader. (StringReader. test))
+ form (read r)
+ expected (.trim (slurp r))
+ actual (when-not (skip? expected)
+ (.trim (try
+ (with-out-str
+ (pr (eval-in-ns ns form))
+ (flush))
+ (catch Exception _
+ (println _)
+ (.toString (gensym))))))]
+ (if (or (skip? expected)
+ (= actual expected))
+ (report {:type :pass})
+ (report {:type :fail
+ :file file :line idx
+ :expected expected :actual actual}))))
+
+(defn run-doctests
+ "use text-extract-fn to get all the tests out of file, and run them
+ all, reporting success or failure"
+ [test-extract-fn file]
+ (let [ns (temp-ns)]
+ (doseq [[idx t] (map-indexed vector (test-extract-fn file))]
+ (run-doctest file idx ns t))
+ (remove-ns ns)))
+
+
+(comment
+ ;; example usage
+ (deftest bar-repl
+ (run-doctests repl-tests "test/bar.repl")))
diff --git a/test-resources/lib_tests/doric/test/readme.clj b/test-resources/lib_tests/doric/test/readme.clj
new file mode 100644
index 00000000..615459ed
--- /dev/null
+++ b/test-resources/lib_tests/doric/test/readme.clj
@@ -0,0 +1,6 @@
+(ns doric.test.readme
+ (:use [clojure.test]
+ [doric.test.doctest]))
+
+(deftest readme
+ (run-doctests markdown-tests "README.md"))
diff --git a/test-resources/lib_tests/expound/alpha_test.cljc b/test-resources/lib_tests/expound/alpha_test.cljc
new file mode 100644
index 00000000..5e663419
--- /dev/null
+++ b/test-resources/lib_tests/expound/alpha_test.cljc
@@ -0,0 +1,4350 @@
+(ns expound.alpha-test
+ (:require #?@(:clj
+ ;; just to include the specs
+ [[clojure.core.specs.alpha]
+ [ring.core.spec]
+ [onyx.spec]])
+
+ ;; Deps for specs that generate specs, which are currently disabled
+ #_[clojure.test.check.random :as random]
+ #_[clojure.test.check.rose-tree :as rose]
+
+ [clojure.set :as set]
+ [clojure.spec.alpha :as s]
+ [clojure.spec.test.alpha :as st]
+ [clojure.string :as string]
+ [clojure.test :as ct :refer [is testing deftest use-fixtures]]
+ [clojure.test.check.generators :as gen]
+
+ [clojure.walk :as walk]
+ [com.gfredericks.test.chuck :as chuck]
+ [com.gfredericks.test.chuck.clojure-test :refer [checking]]
+ [expound.alpha :as expound]
+ [expound.ansi :as ansi]
+ [expound.printer :as printer]
+ [expound.problems :as problems]
+ [expound.spec-gen :as sg]
+ [expound.test-utils :as test-utils]
+ [spec-tools.data-spec :as ds]
+ #?(:clj [orchestra.spec.test :as orch.st]
+ :cljs [orchestra-cljs.spec.test :as orch.st])))
+
+;;;; override specs and add generators
+;;;; this allows us to load expound with babaska and spartan.spec
+(s/def :expound.printer/value-str-fn (s/with-gen ifn?
+ #(gen/return (fn [_ _ _ _] "NOT IMPLEMENTED"))))
+
+(s/def :expound.spec/spec (s/or
+ :set set?
+ :pred (s/with-gen ifn?
+ #(gen/elements [boolean? string? int? keyword? symbol?]))
+ :kw qualified-keyword?
+ :spec (s/with-gen s/spec?
+ #(gen/elements
+ (for [pr [boolean? string? int? keyword? symbol?]]
+ (s/spec pr))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(def num-tests 5)
+
+(use-fixtures :once
+ test-utils/check-spec-assertions
+ test-utils/instrument-all)
+
+;; Missing onyx specs
+(s/def :trigger/materialize any?)
+(s/def :flow/short-circuit any?)
+
+(defn pf
+ "Fixes platform-specific namespaces and also formats using printf syntax"
+ [s & args]
+ (apply printer/format
+ #?(:cljs (string/replace s "pf." "cljs.")
+ :clj (string/replace s "pf." "clojure."))
+ args))
+
+(defn take-lines [n s]
+ (string/join "\n" (take n (string/split-lines s))))
+
+(defn formatted-exception [printer-options f]
+ (let [printer (expound/custom-printer printer-options)
+ exception-data (binding [s/*explain-out* printer]
+ (try
+ (f)
+ (catch #?(:cljs :default :clj Exception)
+ e
+ #?(:cljs {:message (.-message e)
+ :data (.-data e)}
+
+ :clj (Throwable->map e)))))
+ ed #?(:cljs (-> exception-data :data)
+ :clj (-> exception-data :via last :data))
+ cause# (-> #?(:cljs (:message exception-data)
+ :clj (:cause exception-data))
+ (clojure.string/replace #"Call to (.*) did not conform to spec:"
+ "Call to #'$1 did not conform to spec."))]
+
+ (str cause#
+ (if (re-find #"Detected \d+ error" cause#)
+ ""
+ (str "\n"
+ (with-out-str (printer ed)))))))
+
+(defn orch-unstrument-test-fns [f]
+ (orch.st/unstrument [`results-str-fn1
+ `results-str-fn2
+ `results-str-fn4
+ `results-str-fn7])
+ (f))
+
+(def inverted-ansi-codes
+ (reduce
+ (fn [m [k v]]
+ (assoc m (str v) k))
+ {}
+ ansi/sgr-code))
+
+(defn readable-ansi [s]
+ (string/replace
+ s
+ #"\x1b\[([0-9]*)m"
+ #(str "<" (string/upper-case (name (get inverted-ansi-codes (second %)))) ">")))
+
+;; https://github.com/bhb/expound/issues/8
+(deftest expound-output-ends-in-newline
+ (is (= "\n" (str (last (expound/expound-str string? 1)))))
+ (is (= "\n" (str (last (expound/expound-str string? ""))))))
+
+(deftest expound-prints-expound-str
+ (is (=
+ (expound/expound-str string? 1)
+ (with-out-str (expound/expound string? 1)))))
+
+(deftest predicate-spec
+ (is (= (pf "-- Spec failed --------------------
+
+ 1
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str string? 1))))
+
+(s/def :simple-type-based-spec/str string?)
+
+(deftest simple-type-based-spec
+ (testing "valid value"
+ (is (= "Success!\n"
+ (expound/expound-str :simple-type-based-spec/str ""))))
+
+ (testing "invalid value"
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ 1
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:simple-type-based-spec/str:
+ pf.core/string?
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :simple-type-based-spec/str 1)))))
+
+(s/def :set-based-spec/tag #{:foo :bar})
+(s/def :set-based-spec/nilable-tag (s/nilable :set-based-spec/tag))
+(s/def :set-based-spec/set-of-one #{:foobar})
+
+(s/def :set-based-spec/one-or-two (s/or
+ :one (s/cat :a #{:one})
+ :two (s/cat :b #{:two})))
+
+(deftest set-based-spec
+ (testing "prints valid options"
+ (is (= "-- Spec failed --------------------
+
+ :baz
+
+should be one of: :bar, :foo
+
+-- Relevant specs -------
+
+:set-based-spec/tag:
+ #{:bar :foo}
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str :set-based-spec/tag :baz))))
+
+ (testing "prints combined options for various specs"
+ (is (= (pf "-- Spec failed --------------------
+
+ [:three]
+ ^^^^^^
+
+should be one of: :one, :two
+
+-- Relevant specs -------
+
+:set-based-spec/one-or-two:
+ (pf.spec.alpha/or
+ :one
+ (pf.spec.alpha/cat :a #{:one})
+ :two
+ (pf.spec.alpha/cat :b #{:two}))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :set-based-spec/one-or-two [:three]))))
+
+ (testing "nilable version"
+ (is (= (pf "-- Spec failed --------------------
+
+ :baz
+
+should be one of: :bar, :foo
+
+or
+
+should satisfy
+
+ nil?
+
+-- Relevant specs -------
+
+:set-based-spec/tag:
+ #{:bar :foo}
+:set-based-spec/nilable-tag:
+ (pf.spec.alpha/nilable :set-based-spec/tag)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :set-based-spec/nilable-tag :baz))))
+ (testing "single element spec"
+ (is (= (pf "-- Spec failed --------------------
+
+ :baz
+
+should be: :foobar
+
+-- Relevant specs -------
+
+:set-based-spec/set-of-one:
+ #{:foobar}
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :set-based-spec/set-of-one :baz)))))
+
+(s/def :nested-type-based-spec/str string?)
+(s/def :nested-type-based-spec/strs (s/coll-of :nested-type-based-spec/str))
+
+(deftest nested-type-based-spec
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ [... ... 33]
+ ^^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:nested-type-based-spec/str:
+ pf.core/string?
+:nested-type-based-spec/strs:
+ (pf.spec.alpha/coll-of :nested-type-based-spec/str)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :nested-type-based-spec/strs ["one" "two" 33]))))
+
+(s/def :nested-type-based-spec-special-summary-string/int int?)
+(s/def :nested-type-based-spec-special-summary-string/ints (s/coll-of :nested-type-based-spec-special-summary-string/int))
+
+(deftest nested-type-based-spec-special-summary-string
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ [... ... \"...\"]
+ ^^^^^
+
+should satisfy
+
+ int?
+
+-- Relevant specs -------
+
+:nested-type-based-spec-special-summary-string/int:
+ pf.core/int?
+:nested-type-based-spec-special-summary-string/ints:
+ (pf.spec.alpha/coll-of
+ :nested-type-based-spec-special-summary-string/int)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :nested-type-based-spec-special-summary-string/ints [1 2 "..."]))))
+
+(s/def :or-spec/str-or-int (s/or :int int? :str string?))
+(s/def :or-spec/vals (s/coll-of :or-spec/str-or-int))
+
+(s/def :or-spec/str string?)
+(s/def :or-spec/int int?)
+(s/def :or-spec/m-with-str (s/keys :req [:or-spec/str]))
+(s/def :or-spec/m-with-int (s/keys :req [:or-spec/int]))
+(s/def :or-spec/m-with-str-or-int (s/or :m-with-str :or-spec/m-with-str
+ :m-with-int :or-spec/m-with-int))
+
+(deftest or-spec
+ (testing "simple value"
+ (is (= (pf "-- Spec failed --------------------
+
+ :kw
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-- Relevant specs -------
+
+:or-spec/str-or-int:
+ (pf.spec.alpha/or :int pf.core/int? :str pf.core/string?)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :or-spec/str-or-int :kw))))
+ (testing "collection of values"
+ (is (= (pf "-- Spec failed --------------------
+
+ [... ... :kw ...]
+ ^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-- Relevant specs -------
+
+:or-spec/str-or-int:
+ (pf.spec.alpha/or :int pf.core/int? :str pf.core/string?)
+:or-spec/vals:
+ (pf.spec.alpha/coll-of :or-spec/str-or-int)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :or-spec/vals [0 "hi" :kw "bye"]))))
+ (is (= "-- Spec failed --------------------
+
+ 50
+
+should satisfy
+
+ coll?
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str (s/or
+ :strs (s/coll-of string?)
+ :ints (s/coll-of int?))
+ 50)))
+ (is (= "-- Spec failed --------------------
+
+ 50
+
+should be one of: \"a\", \"b\", 1, 2
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str
+ (s/or
+ :letters #{"a" "b"}
+ :ints #{1 2})
+ 50)))
+ (is (= (pf "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :or-spec/int, :or-spec/str
+
+| key | spec |
+|==============+=========|
+| :or-spec/int | int? |
+|--------------+---------|
+| :or-spec/str | string? |
+
+-- Relevant specs -------
+
+:or-spec/m-with-int:
+ (pf.spec.alpha/keys :req [:or-spec/int])
+:or-spec/m-with-str:
+ (pf.spec.alpha/keys :req [:or-spec/str])
+:or-spec/m-with-str-or-int:
+ (pf.spec.alpha/or
+ :m-with-str
+ :or-spec/m-with-str
+ :m-with-int
+ :or-spec/m-with-int)
+
+-------------------------
+Detected 1 error
+")
+ (expound/expound-str :or-spec/m-with-str-or-int {})))
+ (testing "de-dupes keys"
+ (is (= "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :or-spec/str
+
+| key | spec |
+|==============+=========|
+| :or-spec/str | string? |
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str (s/or :m-with-str1 (s/keys :req [:or-spec/str])
+ :m-with-int2 (s/keys :req [:or-spec/str])) {})))))
+
+(s/def :and-spec/name (s/and string? #(pos? (count %))))
+(s/def :and-spec/names (s/coll-of :and-spec/name))
+(deftest and-spec
+ (testing "simple value"
+ (is (= (pf "-- Spec failed --------------------
+
+ \"\"
+
+should satisfy
+
+ (fn [%%] (pos? (count %%)))
+
+-- Relevant specs -------
+
+:and-spec/name:
+ (pf.spec.alpha/and
+ pf.core/string?
+ (pf.core/fn [%%] (pf.core/pos? (pf.core/count %%))))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :and-spec/name ""))))
+
+ (testing "shows both failures in order"
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ [... ... \"\" ...]
+ ^^
+
+should satisfy
+
+ %s
+
+-- Relevant specs -------
+
+:and-spec/name:
+ (pf.spec.alpha/and
+ pf.core/string?
+ (pf.core/fn [%%] (pf.core/pos? (pf.core/count %%))))
+:and-spec/names:
+ (pf.spec.alpha/coll-of :and-spec/name)
+
+-- Spec failed --------------------
+
+ [... ... ... 1]
+ ^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:and-spec/name:
+ (pf.spec.alpha/and
+ pf.core/string?
+ (pf.core/fn [%%] (pf.core/pos? (pf.core/count %%))))
+:and-spec/names:
+ (pf.spec.alpha/coll-of :and-spec/name)
+
+-------------------------
+Detected 2 errors\n"
+ #?(:cljs "(fn [%] (pos? (count %)))"
+ :clj "(fn [%] (pos? (count %)))"))
+ (expound/expound-str :and-spec/names ["bob" "sally" "" 1])))))
+
+(s/def :coll-of-spec/big-int-coll (s/coll-of int? :min-count 10))
+
+(deftest coll-of-spec
+ (testing "min count"
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ []
+
+should satisfy
+
+ (<= 10 (count %%) %s)
+
+-- Relevant specs -------
+
+:coll-of-spec/big-int-coll:
+ (pf.spec.alpha/coll-of pf.core/int? :min-count 10)
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "9007199254740991"
+ :clj "Integer/MAX_VALUE"))
+ (expound/expound-str :coll-of-spec/big-int-coll [])))))
+
+(s/def :cat-spec/kw (s/cat :k keyword? :v any?))
+(s/def :cat-spec/set (s/cat :type #{:foo :bar} :str string?))
+(s/def :cat-spec/alt* (s/alt :s string? :i int?))
+(s/def :cat-spec/alt (s/+ :cat-spec/alt*))
+(s/def :cat-spec/alt-inline (s/+ (s/alt :s string? :i int?)))
+(s/def :cat-spec/any (s/cat :x (s/+ any?))) ;; Not a useful spec, but worth testing
+(deftest cat-spec
+ (testing "too few elements"
+ (is (= (pf "-- Syntax error -------------------
+
+ []
+
+should have additional elements. The next element \":k\" should satisfy
+
+ keyword?
+
+-- Relevant specs -------
+
+:cat-spec/kw:
+ (pf.spec.alpha/cat :k pf.core/keyword? :v pf.core/any?)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/kw [])))
+ (is (= (pf "-- Syntax error -------------------
+
+ []
+
+should have additional elements. The next element \":type\" should be one of: :bar, :foo
+
+-- Relevant specs -------
+
+:cat-spec/set:
+ (pf.spec.alpha/cat :type #{:bar :foo} :str pf.core/string?)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/set [])))
+ (is (= (pf "-- Syntax error -------------------
+
+ [:foo]
+
+should have additional elements. The next element \":v\" should satisfy
+
+ any?
+
+-- Relevant specs -------
+
+:cat-spec/kw:
+ (pf.spec.alpha/cat :k pf.core/keyword? :v pf.core/any?)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/kw [:foo])))
+ ;; This isn't ideal, but requires a fix from clojure
+ ;; https://clojure.atlassian.net/browse/CLJ-2364
+ (is (= (pf "-- Syntax error -------------------
+
+ []
+
+should have additional elements. The next element should satisfy
+
+ (pf.spec.alpha/alt :s string? :i int?)
+
+-- Relevant specs -------
+
+:cat-spec/alt*:
+ (pf.spec.alpha/alt :s pf.core/string? :i pf.core/int?)
+:cat-spec/alt:
+ (pf.spec.alpha/+ :cat-spec/alt*)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/alt [])))
+ (is (= (pf "-- Syntax error -------------------
+
+ []
+
+should have additional elements. The next element should satisfy
+
+ (pf.spec.alpha/alt :s string? :i int?)
+
+-- Relevant specs -------
+
+:cat-spec/alt-inline:
+ (pf.spec.alpha/+
+ (pf.spec.alpha/alt :s pf.core/string? :i pf.core/int?))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/alt-inline [])))
+ (is (= (pf "-- Syntax error -------------------
+
+ []
+
+should have additional elements. The next element \":x\" should satisfy
+
+ any?
+
+-- Relevant specs -------
+
+:cat-spec/any:
+ (pf.spec.alpha/cat :x (pf.spec.alpha/+ pf.core/any?))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/any []))))
+ (testing "too many elements"
+ (is (= (pf "-- Syntax error -------------------
+
+ [... ... :bar ...]
+ ^^^^
+
+has extra input
+
+-- Relevant specs -------
+
+:cat-spec/kw:
+ (pf.spec.alpha/cat :k pf.core/keyword? :v pf.core/any?)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-spec/kw [:foo 1 :bar :baz])))))
+
+(s/def :keys-spec/name string?)
+(s/def :keys-spec/age int?)
+(s/def :keys-spec/user (s/keys :req [:keys-spec/name]
+ :req-un [:keys-spec/age]))
+
+(s/def :key-spec/state string?)
+(s/def :key-spec/city string?)
+(s/def :key-spec/zip pos-int?)
+
+(s/def :keys-spec/user2 (s/keys :req [(and :keys-spec/name
+ :keys-spec/age)]
+ :req-un [(or
+ :key-spec/zip
+ (and
+ :key-spec/state
+ :key-spec/city))]))
+
+(s/def :keys-spec/user3 (s/keys :req-un [(or
+ :key-spec/zip
+ (and
+ :key-spec/state
+ :key-spec/city))]))
+
+(s/def :keys-spec/user4 (s/keys :req []))
+
+(defmulti key-spec-mspec :tag)
+(defmethod key-spec-mspec :int [_] (s/keys :req-un [::tag ::i]))
+(defmethod key-spec-mspec :string [_] (s/keys :req-un [::tag ::s]))
+(deftest keys-spec
+ (testing "missing keys"
+ (is (= (pf "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :age, :keys-spec/name
+
+| key | spec |
+|=================+=========|
+| :age | int? |
+|-----------------+---------|
+| :keys-spec/name | string? |
+
+-- Relevant specs -------
+
+:keys-spec/user:
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "(cljs.spec.alpha/keys :req [:keys-spec/name] :req-un [:keys-spec/age])"
+ :clj "(clojure.spec.alpha/keys\n :req\n [:keys-spec/name]\n :req-un\n [:keys-spec/age])"))
+ (expound/expound-str :keys-spec/user {}))))
+ (testing "missing compound keys"
+ (is (= (pf "-- Spec failed --------------------
+
+ {}
+
+should contain keys:
+
+(and (and :keys-spec/name :keys-spec/age) (or :zip (and :state :city)))
+
+| key | spec |
+|=================+==========|
+| :city | string? |
+|-----------------+----------|
+| :state | string? |
+|-----------------+----------|
+| :zip | pos-int? |
+|-----------------+----------|
+| :keys-spec/age | int? |
+|-----------------+----------|
+| :keys-spec/name | string? |
+
+-- Relevant specs -------
+
+:keys-spec/user2:
+ (pf.spec.alpha/keys
+ :req
+ [(and :keys-spec/name :keys-spec/age)]
+ :req-un
+ [(or :key-spec/zip (and :key-spec/state :key-spec/city))])
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :keys-spec/user2 {})))
+ (is (= (pf "-- Spec failed --------------------
+
+ {}
+
+should contain keys:
+
+(or :zip (and :state :city))
+
+| key | spec |
+|========+==========|
+| :city | string? |
+|--------+----------|
+| :state | string? |
+|--------+----------|
+| :zip | pos-int? |
+
+-- Relevant specs -------
+
+:keys-spec/user3:
+ (pf.spec.alpha/keys
+ :req-un
+ [(or :key-spec/zip (and :key-spec/state :key-spec/city))])
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :keys-spec/user3 {}))))
+
+ (testing "inline spec with req-un"
+ (is (= (pf "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :age, :name
+
+| key | spec |
+|=======+=========|
+| :age | int? |
+|-------+---------|
+| :name | string? |
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "(cljs.spec.alpha/keys :req [:keys-spec/name] :req-un [:keys-spec/age])"
+ :clj "(clojure.spec.alpha/keys\n :req\n [:keys-spec/name]\n :req-un\n [:keys-spec/age])"))
+ (expound/expound-str (s/keys :req-un [:keys-spec/name :keys-spec/age]) {})))
+ (s/def :key-spec/mspec (s/multi-spec key-spec-mspec :tag))
+ (s/def :key-spec/i int?)
+ (s/def :key-spec/s string?)
+ ;; We can't inspect the contents of a multi-spec (to figure out
+ ;; which spec we mean by :i), so this is the best we can do.
+ (is (= "-- Spec failed --------------------
+
+ {:tag :int}
+
+should contain key: :i
+
+| key | spec |
+|=====+===================================================|
+| :i | |
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ :key-spec/mspec
+ {:tag :int}
+ {:print-specs? false}))))
+
+ (testing "invalid key"
+ (is (= (pf "-- Spec failed --------------------
+
+ {:age ..., :keys-spec/name :bob}
+ ^^^^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:keys-spec/name:
+ pf.core/string?
+:keys-spec/user:
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "(cljs.spec.alpha/keys :req [:keys-spec/name] :req-un [:keys-spec/age])"
+ :clj "(clojure.spec.alpha/keys\n :req\n [:keys-spec/name]\n :req-un\n [:keys-spec/age])"))
+ (expound/expound-str :keys-spec/user {:age 1 :keys-spec/name :bob}))))
+ (testing "contains compound specs"
+ (s/def :keys-spec/states (s/coll-of :key-spec/state :kind vector?))
+ (s/def :keys-spec/address (s/keys :req [:key-spec/city :key-space/state]))
+ (s/def :keys-spec/cities (s/coll-of :key-spec/city :kind set?))
+ (s/def :keys-spec/locations (s/keys :req-un [:keys-spec/states
+ :keys-spec/address
+ :keys-spec/locations]))
+ (is (=
+ "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :address, :locations, :states
+
+| key | spec |
+|============+===============================================================|
+| :address | (keys :req [:key-spec/city :key-space/state]) |
+|------------+---------------------------------------------------------------|
+| :locations | (keys |
+| | :req-un |
+| | [:keys-spec/states :keys-spec/address :keys-spec/locations]) |
+|------------+---------------------------------------------------------------|
+| :states | (coll-of :key-spec/state :kind vector?) |
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str :keys-spec/locations {} {:print-specs? false})))))
+
+(s/def :keys-spec/foo string?)
+(s/def :keys-spec/bar string?)
+(s/def :keys-spec/baz string?)
+(s/def :keys-spec/qux (s/or :string string?
+ :int int?))
+(s/def :keys-spec/child-1 (s/keys :req-un [:keys-spec/baz :keys-spec/qux]))
+(s/def :keys-spec/child-2 (s/keys :req-un [:keys-spec/bar :keys-spec/child-1]))
+
+(s/def :keys-spec/map-spec-1 (s/keys :req-un [:keys-spec/foo
+ :keys-spec/bar
+ :keys-spec/baz]))
+(s/def :keys-spec/map-spec-2 (s/keys :req-un [:keys-spec/foo
+ :keys-spec/bar
+ :keys-spec/qux]))
+(s/def :keys-spec/map-spec-3 (s/keys :req-un [:keys-spec/foo
+ :keys-spec/child-2]))
+
+(deftest grouping-and-key-specs
+ (is (= (pf
+ "-- Spec failed --------------------
+
+ {:foo 1.2, :bar ..., :baz ...}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ..., :bar 123, :baz ...}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ..., :bar ..., :baz true}
+ ^^^^
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 3 errors\n")
+ (expound/expound-str :keys-spec/map-spec-1 {:foo 1.2
+ :bar 123
+ :baz true}
+ {:print-specs? false})))
+ (is (= (pf
+ "-- Spec failed --------------------
+
+ {:foo 1.2, :bar ..., :qux ...}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ..., :bar 123, :qux ...}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ..., :bar ..., :qux false}
+ ^^^^^
+
+should satisfy
+
+ string?
+
+or
+
+ int?
+
+-------------------------
+Detected 3 errors\n")
+ (expound/expound-str :keys-spec/map-spec-2 {:foo 1.2
+ :bar 123
+ :qux false}
+ {:print-specs? false})))
+
+ (is (=
+ "-- Spec failed --------------------
+
+ {:foo 1.2, :child-2 ...}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ..., :child-2 {:bar 123, :child-1 ...}}
+ ^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ...,
+ :child-2
+ {:bar ..., :child-1 {:baz true, :qux ...}}}
+ ^^^^
+
+should satisfy
+
+ string?
+
+-- Spec failed --------------------
+
+ {:foo ...,
+ :child-2
+ {:bar ..., :child-1 {:baz ..., :qux false}}}
+ ^^^^^
+
+should satisfy
+
+ string?
+
+or
+
+ int?
+
+-------------------------
+Detected 4 errors\n"
+ (expound/expound-str :keys-spec/map-spec-3 {:foo 1.2
+ :child-2 {:bar 123
+ :child-1 {:baz true
+ :qux false}}}
+ {:print-specs? false}))))
+
+(s/def :multi-spec/value string?)
+(s/def :multi-spec/children vector?)
+(defmulti el-type :multi-spec/el-type)
+(defmethod el-type :text [_x]
+ (s/keys :req [:multi-spec/value]))
+(defmethod el-type :group [_x]
+ (s/keys :req [:multi-spec/children]))
+(s/def :multi-spec/el (s/multi-spec el-type :multi-spec/el-type))
+
+(defmulti multi-spec-bar-spec :type)
+(defmethod multi-spec-bar-spec ::b [_] (s/keys :req [::b]))
+(deftest multi-spec
+ (testing "missing dispatch key"
+ (is (=
+ (pf "-- Missing spec -------------------
+
+Cannot find spec for
+
+ {}
+
+with
+
+ Spec multimethod: `expound.alpha-test/el-type`
+ Dispatch value: `nil`
+
+-- Relevant specs -------
+
+:multi-spec/el:
+ (pf.spec.alpha/multi-spec
+ expound.alpha-test/el-type
+ :multi-spec/el-type)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :multi-spec/el {}))))
+ (testing "invalid dispatch value"
+ (is (=
+ (pf "-- Missing spec -------------------
+
+Cannot find spec for
+
+ {:multi-spec/el-type :image}
+
+with
+
+ Spec multimethod: `expound.alpha-test/el-type`
+ Dispatch value: `:image`
+
+-- Relevant specs -------
+
+:multi-spec/el:
+ (pf.spec.alpha/multi-spec
+ expound.alpha-test/el-type
+ :multi-spec/el-type)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :multi-spec/el {:multi-spec/el-type :image}))))
+
+ (testing "valid dispatch value, but other error"
+ (is (=
+ (pf "-- Spec failed --------------------
+
+ {:multi-spec/el-type :text}
+
+should contain key: :multi-spec/value
+
+| key | spec |
+|===================+=========|
+| :multi-spec/value | string? |
+
+-- Relevant specs -------
+
+:multi-spec/el:
+ (pf.spec.alpha/multi-spec
+ expound.alpha-test/el-type
+ :multi-spec/el-type)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :multi-spec/el {:multi-spec/el-type :text}))))
+
+ ;; https://github.com/bhb/expound/issues/122
+ (testing "when re-tag is a function"
+ (s/def :multi-spec/b string?)
+ (s/def :multi-spec/bar (s/multi-spec multi-spec-bar-spec (fn [val tag] (assoc val :type tag))))
+ (is (= "-- Missing spec -------------------
+
+Cannot find spec for
+
+ {}
+
+with
+
+ Spec multimethod: `expound.alpha-test/multi-spec-bar-spec`
+ Dispatch value: `nil`
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str :multi-spec/bar {} {:print-specs? false})))))
+
+(s/def :recursive-spec/tag #{:text :group})
+(s/def :recursive-spec/on-tap (s/coll-of map? :kind vector?))
+(s/def :recursive-spec/props (s/keys :opt-un [:recursive-spec/on-tap]))
+(s/def :recursive-spec/el (s/keys :req-un [:recursive-spec/tag]
+ :opt-un [:recursive-spec/props :recursive-spec/children]))
+(s/def :recursive-spec/children (s/coll-of (s/nilable :recursive-spec/el) :kind vector?))
+
+(s/def :recursive-spec/tag-2 (s/or :text (fn [n] (= n :text))
+ :group (fn [n] (= n :group))))
+(s/def :recursive-spec/on-tap-2 (s/coll-of map? :kind vector?))
+(s/def :recursive-spec/props-2 (s/keys :opt-un [:recursive-spec/on-tap-2]))
+(s/def :recursive-spec/el-2 (s/keys :req-un [:recursive-spec/tag-2]
+ :opt-un [:recursive-spec/props-2
+ :recursive-spec/children-2]))
+(s/def :recursive-spec/children-2 (s/coll-of (s/nilable :recursive-spec/el-2) :kind vector?))
+
+(deftest recursive-spec
+ (testing "only shows problem with data at 'leaves' (not problems with all parents in tree)"
+ (is (= (pf
+ "-- Spec failed --------------------
+
+ {:tag ..., :children [{:tag :group, :children [{:tag :group, :props {:on-tap {}}}]}]}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+should satisfy
+
+ nil?
+
+or value
+
+ {:tag ...,
+ :children [{:tag ..., :children [{:tag :group, :props {:on-tap {}}}]}]}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+should satisfy
+
+ nil?
+
+or value
+
+ {:tag ...,
+ :children
+ [{:tag ...,
+ :children
+ [{:tag ..., :props {:on-tap {}}}]}]}
+ ^^
+
+should satisfy
+
+ vector?
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str
+ :recursive-spec/el
+ {:tag :group
+ :children [{:tag :group
+ :children [{:tag :group
+ :props {:on-tap {}}}]}]}
+ {:print-specs? false}))))
+ (testing "test that our new recursive spec grouping function works with
+ alternative paths"
+ (is (= (pf
+ "-- Spec failed --------------------
+
+ {:tag-2 ..., :children-2 [{:tag-2 :group, :children-2 [{:tag-2 :group, :props-2 {:on-tap-2 {}}}]}]}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+should satisfy
+
+ nil?
+
+or value
+
+ {:tag-2 ...,
+ :children-2 [{:tag-2 ..., :children-2 [{:tag-2 :group, :props-2 {:on-tap-2 {}}}]}]}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+should satisfy
+
+ nil?
+
+or value
+
+ {:tag-2 ...,
+ :children-2
+ [{:tag-2 ...,
+ :children-2
+ [{:tag-2 ..., :props-2 {:on-tap-2 {}}}]}]}
+ ^^
+
+should satisfy
+
+ vector?
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str
+ :recursive-spec/el-2
+ {:tag-2 :group
+ :children-2 [{:tag-2 :group
+ :children-2 [{:tag-2 :group
+ :props-2 {:on-tap-2 {}}}]}]}
+ {:print-specs? false})))))
+
+(s/def :cat-wrapped-in-or-spec/kv (s/and
+ sequential?
+ (s/cat :k keyword? :v any?)))
+(s/def :cat-wrapped-in-or-spec/type #{:text})
+(s/def :cat-wrapped-in-or-spec/kv-or-string (s/or
+ :map (s/keys :req [:cat-wrapped-in-or-spec/type])
+ :kv :cat-wrapped-in-or-spec/kv))
+
+(deftest cat-wrapped-in-or-spec
+ (is (= (pf "-- Spec failed --------------------
+
+ {\"foo\" \"hi\"}
+
+should contain key: :cat-wrapped-in-or-spec/type
+
+| key | spec |
+|==============================+==========|
+| :cat-wrapped-in-or-spec/type | #{:text} |
+
+or
+
+should satisfy
+
+ sequential?
+
+-- Relevant specs -------
+
+:cat-wrapped-in-or-spec/kv:
+ (pf.spec.alpha/and
+ pf.core/sequential?
+ (pf.spec.alpha/cat :k pf.core/keyword? :v pf.core/any?))
+:cat-wrapped-in-or-spec/kv-or-string:
+ (pf.spec.alpha/or
+ :map
+ (pf.spec.alpha/keys :req [:cat-wrapped-in-or-spec/type])
+ :kv
+ :cat-wrapped-in-or-spec/kv)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :cat-wrapped-in-or-spec/kv-or-string {"foo" "hi"}))))
+
+(s/def :map-of-spec/name string?)
+(s/def :map-of-spec/age pos-int?)
+(s/def :map-of-spec/name->age (s/map-of :map-of-spec/name :map-of-spec/age))
+(deftest map-of-spec
+ (is (= (pf "-- Spec failed --------------------
+
+ {\"Sally\" \"30\"}
+ ^^^^
+
+should satisfy
+
+ pos-int?
+
+-- Relevant specs -------
+
+:map-of-spec/age:
+ pf.core/pos-int?
+:map-of-spec/name->age:
+ (pf.spec.alpha/map-of :map-of-spec/name :map-of-spec/age)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :map-of-spec/name->age {"Sally" "30"})))
+ (is (= (pf "-- Spec failed --------------------
+
+ {:sally ...}
+ ^^^^^^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:map-of-spec/name:
+ pf.core/string?
+:map-of-spec/name->age:
+ (pf.spec.alpha/map-of :map-of-spec/name :map-of-spec/age)
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :map-of-spec/name->age {:sally 30}))))
+
+(deftest generated-simple-spec
+ (checking
+ "simple spec"
+ (chuck/times num-tests)
+ [simple-spec sg/simple-spec-gen
+ form gen/any-printable]
+ (is (string? (expound/expound-str simple-spec form)))))
+
+(deftest generated-coll-of-specs
+ (checking
+ "'coll-of' spec"
+ (chuck/times num-tests)
+ [simple-spec sg/simple-spec-gen
+ every-args (s/gen :specs/every-args)
+ :let [spec (sg/apply-coll-of simple-spec every-args)]
+ form gen/any-printable]
+ (is (string? (expound/expound-str spec form)))))
+
+(deftest generated-and-specs
+ (checking
+ "'and' spec"
+ (chuck/times num-tests)
+ [simple-spec1 sg/simple-spec-gen
+ simple-spec2 sg/simple-spec-gen
+ :let [spec (s/and simple-spec1 simple-spec2)]
+ form gen/any-printable]
+ (is (string? (expound/expound-str spec form)))))
+
+(deftest generated-or-specs
+ (checking
+ "'or' spec generates string"
+ (chuck/times num-tests)
+ [simple-spec1 sg/simple-spec-gen
+ simple-spec2 sg/simple-spec-gen
+ :let [spec (s/or :or1 simple-spec1 :or2 simple-spec2)]
+ form gen/any-printable]
+ (is (string? (expound/expound-str spec form))))
+ (checking
+ "nested 'or' spec reports on all problems"
+ (chuck/times num-tests)
+ [simple-specs (gen/vector-distinct
+ (gen/elements [:specs/string
+ :specs/vector
+ :specs/int
+ :specs/boolean
+ :specs/keyword
+ :specs/map
+ :specs/symbol
+ :specs/pos-int
+ :specs/neg-int
+ :specs/zero])
+ {:num-elements 4})
+ :let [[simple-spec1
+ simple-spec2
+ simple-spec3
+ simple-spec4] simple-specs
+ spec (s/or :or1
+ (s/or :or1.1
+ simple-spec1
+ :or1.2
+ simple-spec2)
+ :or2
+ (s/or :or2.1
+ simple-spec3
+ :or2.2
+ simple-spec4))
+ sp-form (s/form spec)]
+ form gen/any-printable]
+ (let [ed (s/explain-data spec form)]
+ (when-not (zero? (count (::s/problems ed)))
+ (is (= (dec (count (::s/problems ed)))
+ (count (re-seq #"\nor\n" (expound/expound-str spec form))))
+ (str "Failed to print out all problems\nspec: " sp-form "\nproblems: " (printer/pprint-str (::s/problems ed)) "\nmessage: " (expound/expound-str spec form)))))))
+
+(deftest generated-map-of-specs
+ (checking
+ "'map-of' spec"
+ (chuck/times num-tests)
+ [simple-spec1 sg/simple-spec-gen
+ simple-spec2 sg/simple-spec-gen
+ simple-spec3 sg/simple-spec-gen
+ every-args1 (s/gen :specs/every-args)
+ every-args2 (s/gen :specs/every-args)
+ :let [spec (sg/apply-map-of simple-spec1 (sg/apply-map-of simple-spec2 simple-spec3 every-args1) every-args2)]
+ form test-utils/any-printable-wo-nan]
+ (is (string? (expound/expound-str spec form)))))
+
+(s/def :expound.ds/spec-key (s/or :kw keyword?
+ :req (s/tuple
+ #{:expound.ds/req-key}
+ (s/map-of
+ #{:k}
+ keyword?
+ :count 1))
+ :opt (s/tuple
+ #{:expound.ds/opt-key}
+ (s/map-of
+ #{:k}
+ keyword?
+ :count 1))))
+
+(defn real-spec [form]
+ (walk/prewalk
+ (fn [x]
+ (if (vector? x)
+ (case (first x)
+ :expound.ds/opt-key
+ (ds/map->OptionalKey (second x))
+
+ :expound.ds/req-key
+ (ds/map->RequiredKey (second x))
+
+ :expound.ds/maybe-spec
+ (ds/maybe (second x))
+
+ x)
+ x))
+ form))
+
+(s/def :expound.ds/maybe-spec
+ (s/tuple
+ #{:expound.ds/maybe-spec}
+ :expound.ds/spec))
+
+(s/def :expound.ds/simple-specs
+ #{string?
+ vector?
+ int?
+ boolean?
+ keyword?
+ map?
+ symbol?
+ pos-int?
+ neg-int?
+ nat-int?})
+
+(s/def :expound.ds/vector-spec (s/coll-of
+ :expound.ds/spec
+ :count 1
+ :kind vector?))
+
+(s/def :expound.ds/set-spec (s/coll-of
+ :expound.ds/spec
+ :count 1
+ :kind set?))
+
+(s/def :expound.ds/map-spec
+ (s/map-of :expound.ds/spec-key
+ :expound.ds/spec))
+
+(s/def :expound.ds/spec
+ (s/or
+ :map :expound.ds/map-spec
+ :vector :expound.ds/vector-spec
+ :set :expound.ds/set-spec
+ :simple :expound.ds/simple-specs
+ :maybe :expound.ds/maybe-spec))
+
+(deftest generated-data-specs
+ (checking
+ "generated data specs"
+ (chuck/times num-tests)
+ [data-spec (s/gen :expound.ds/spec)
+ form test-utils/any-printable-wo-nan
+ prefix (s/gen qualified-keyword?)
+ :let [gen-spec (ds/spec prefix (real-spec data-spec))]]
+ (is (string? (expound/expound-str gen-spec form)))))
+
+;; FIXME - keys
+;; FIXME - cat + alt, + ? *
+;; FIXME - nilable
+;; FIXME - test coll-of that is a set . can i should a bad element of a set?
+
+(s/def :test-assert/name string?)
+(deftest test-assert
+ (testing "assertion passes"
+ (is (= "hello"
+ (s/assert :test-assert/name "hello"))))
+ (testing "assertion fails"
+ #?(:cljs
+ (try
+ (binding [s/*explain-out* expound/printer]
+ (s/assert :test-assert/name :hello))
+ (catch :default e
+ (is (= "Spec assertion failed\n-- Spec failed --------------------
+
+ :hello
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:test-assert/name:
+ cljs.core/string?
+
+-------------------------
+Detected 1 error\n"
+ (.-message e)))))
+ :clj
+ (try
+ (binding [s/*explain-out* expound/printer]
+ (s/assert :test-assert/name :hello))
+ (catch Exception e
+ (is (= "Spec assertion failed
+-- Spec failed --------------------
+
+ :hello
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:test-assert/name:
+ clojure.core/string?
+
+-------------------------
+Detected 1 error\n"
+ ;; FIXME - move assertion out of catch, similar to instrument tests
+ (:cause (Throwable->map e)))))))))
+
+(s/def :test-explain-str/name string?)
+(deftest test-explain-str
+ (is (= (pf "-- Spec failed --------------------
+
+ :hello
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:test-explain-str/name:
+ pf.core/string?
+
+-------------------------
+Detected 1 error\n")
+ (binding [s/*explain-out* expound/printer]
+ (s/explain-str :test-explain-str/name :hello)))))
+
+(s/fdef test-instrument-adder
+ :args (s/cat :x int? :y int?)
+ :fn #(> (:ret %) (-> % :args :x))
+ :ret pos-int?)
+(defn test-instrument-adder [& args]
+ (let [[x y] args]
+ (+ x y)))
+
+(defn no-linum [s]
+ (string/replace s #"(.cljc?):\d+" "$1:LINUM"))
+
+(deftest test-instrument
+ (st/instrument `test-instrument-adder)
+ #?(:cljs (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" ...)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (formatted-exception {:print-specs? false} #(test-instrument-adder "" :x))))
+ :clj
+ (is (= "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" ...)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception {:print-specs? false :show-valid-values? false} #(test-instrument-adder "" :x))))))
+ (st/unstrument `test-instrument-adder))
+
+(deftest test-instrument-with-orchestra-args-spec-failure
+ (orch.st/instrument `test-instrument-adder)
+ #?(:cljs (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" ...)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum (formatted-exception {:print-specs? false} #(test-instrument-adder "" :x)))))
+ :clj (is (= "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" ...)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception
+ {}
+ #(test-instrument-adder "" :x))))))
+
+ (orch.st/unstrument `test-instrument-adder))
+
+;; Note - you may need to comment out this test out when
+;; using figwheel.main for testing, since the compilation
+;; warning seems to impact the building of other tests
+(deftest test-instrument-with-orchestra-args-syntax-failure
+ (orch.st/instrument `test-instrument-adder)
+ #?(:cljs (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Syntax error -------------------
+
+Function arguments
+
+ (1)
+
+should have additional elements. The next element \":y\" should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum (formatted-exception {:print-specs? false} #(test-instrument-adder 1)))))
+ :clj
+ (is (= "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Syntax error -------------------
+
+Function arguments
+
+ (1)
+
+should have additional elements. The next element \":y\" should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception
+ {:print-specs? false}
+ #(test-instrument-adder 1))))))
+ (orch.st/unstrument `test-instrument-adder))
+
+(deftest test-instrument-with-orchestra-ret-failure
+ (orch.st/instrument `test-instrument-adder)
+ #?(:cljs (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Spec failed --------------------
+
+Return value
+
+ -3
+
+should satisfy
+
+ pos-int?
+
+-------------------------
+Detected 1 error\n"
+ (formatted-exception {}
+ #(test-instrument-adder -1 -2))
+ #_(.-message (try
+ (binding [s/*explain-out* expound/printer]
+ (test-instrument-adder -1 -2))
+ (catch :default e e)))))
+ :clj
+ (is (= "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Spec failed --------------------
+
+Return value
+
+ -3
+
+should satisfy
+
+ pos-int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception
+ {:print-specs? false}
+ #(test-instrument-adder -1 -2))))))
+ (orch.st/unstrument `test-instrument-adder))
+
+(deftest test-instrument-with-orchestra-fn-failure
+ (orch.st/instrument `test-instrument-adder)
+ #?(:cljs (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Spec failed --------------------
+
+Function arguments and return value
+
+ {:ret 1, :args {:x 1, :y 0}}
+
+should satisfy
+
+ (fn [%] (> (:ret %) (-> % :args :x)))
+
+-------------------------
+Detected 1 error\n"
+ (formatted-exception {} #(test-instrument-adder 1 0))))
+ :clj
+ (is (= "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Spec failed --------------------
+
+Function arguments and return value
+
+ {:ret 1, :args {:x 1, :y 0}}
+
+should satisfy
+
+ (fn
+ [%]
+ (> (:ret %) (-> % :args :x)))
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception {:print-specs? false} #(test-instrument-adder 1 0))))))
+
+ (orch.st/unstrument `test-instrument-adder))
+
+(deftest test-instrument-with-custom-value-printer
+ (st/instrument `test-instrument-adder)
+ #?(:cljs
+ (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+:
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" :x)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception {:print-specs? false :show-valid-values? true} #(test-instrument-adder "" :x)))))
+ :clj
+ (is (=
+ "Call to #'expound.alpha-test/test-instrument-adder did not conform to spec.
+alpha_test.cljc:LINUM
+
+-- Spec failed --------------------
+
+Function arguments
+
+ (\"\" :x)
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (no-linum
+ (formatted-exception {:print-specs? false :show-valid-values? true} #(test-instrument-adder "" :x))))))
+
+ (st/unstrument `test-instrument-adder))
+
+(s/def :custom-printer/strings (s/coll-of string?))
+(deftest custom-printer
+ (testing "custom value printer"
+ (is (= (pf "-- Spec failed --------------------
+
+
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:custom-printer/strings:
+ (pf.spec.alpha/coll-of pf.core/string?)
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* (expound/custom-printer {:value-str-fn (fn [_spec-name _form _path _val] " ")})]
+ (s/explain-str :custom-printer/strings ["a" "b" :c]))))))
+
+(s/def :alt-spec/int-alt-str (s/alt :int int? :string string?))
+
+(s/def :alt-spec/num-types (s/alt :int int? :float float?))
+(s/def :alt-spec/str-types (s/alt :int (fn [n] (= n "int"))
+ :float (fn [n] (= n "float"))))
+(s/def :alt-spec/num-or-str (s/alt :num :alt-spec/num-types
+ :str :alt-spec/str-types))
+
+(s/def :alt-spec/i int?)
+(s/def :alt-spec/s string?)
+(s/def :alt-spec/alt-or-map (s/or :i :alt-spec/i
+ :s :alt-spec/s
+ :k (s/keys :req-un [:alt-spec/i :alt-spec/s])))
+
+(defmulti alt-spec-mspec :tag)
+(s/def :alt-spec/mspec (s/multi-spec alt-spec-mspec :tag))
+(defmethod alt-spec-mspec :x [_] (s/keys :req-un [:alt-spec/one-many-int]))
+
+(deftest alt-spec
+ (testing "alternatives at different paths in spec"
+ (is (=
+ "-- Spec failed --------------------
+
+ [\"foo\"]
+
+should satisfy
+
+ int?
+
+or value
+
+ [\"foo\"]
+ ^^^^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ (s/or
+ :i int?
+ :seq (s/cat :x1 int? :x2 int?))
+ ["foo"]
+ {:print-specs? false})))
+ (s/def :alt-spec/one-many-int (s/cat :bs (s/alt :one int?
+ :many (s/spec (s/+ int?)))))
+ (is (= (pf "-- Spec failed --------------------
+
+ [[\"1\"]]
+ ^^^^^
+
+should satisfy
+
+ int?
+
+or value
+
+ [[\"1\"]]
+ ^^^
+
+should satisfy
+
+ int?
+
+-- Relevant specs -------
+
+:alt-spec/one-many-int:
+ (pf.spec.alpha/cat
+ :bs
+ (pf.spec.alpha/alt
+ :one
+ pf.core/int?
+ :many
+ (pf.spec.alpha/spec (pf.spec.alpha/+ pf.core/int?))))
+
+-------------------------
+Detected 1 error\n")
+ (binding [s/*explain-out* (expound/custom-printer {})]
+ (s/explain-str
+ :alt-spec/one-many-int
+ [["1"]]))))
+ (s/def :alt-spec/one-many-int-or-str (s/cat :bs (s/alt :one :alt-spec/int-alt-str
+ :many (s/spec (s/+ :alt-spec/int-alt-str)))))
+ (is (= "-- Spec failed --------------------
+
+ [[:one]]
+ ^^^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+or value
+
+ [[:one]]
+ ^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str
+ :alt-spec/one-many-int-or-str
+ [[:one]]))))
+ (s/def :alt-spec/int-or-str (s/or :i int?
+ :s string?))
+ (s/def :alt-spec/one-many-int-or-str (s/cat :bs (s/alt :one :alt-spec/int-or-str
+ :many (s/spec (s/+ :alt-spec/int-or-str)))))
+ (is (= "-- Spec failed --------------------
+
+ [[:one]]
+ ^^^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+or value
+
+ [[:one]]
+ ^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str
+ :alt-spec/one-many-int-or-str
+ [[:one]])))))
+ (is (= (pf "-- Spec failed --------------------
+
+ [:hi]
+ ^^^
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-- Relevant specs -------
+
+:alt-spec/int-alt-str:
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:clj "(clojure.spec.alpha/alt
+ :int
+ clojure.core/int?
+ :string
+ clojure.core/string?)"
+ :cljs "(cljs.spec.alpha/alt :int cljs.core/int? :string cljs.core/string?)"))
+ (expound/expound-str :alt-spec/int-alt-str [:hi])))
+
+ (is (= "-- Spec failed --------------------
+
+ {:i \"\", :s 1}
+
+should satisfy
+
+ int?
+
+or
+
+ string?
+
+-- Spec failed --------------------
+
+ {:i \"\", :s ...}
+ ^^
+
+should satisfy
+
+ int?
+
+-- Spec failed --------------------
+
+ {:i ..., :s 1}
+ ^
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 3 errors
+"
+
+ (expound/expound-str
+ :alt-spec/alt-or-map
+ {:i "" :s 1}
+ {:print-specs? false})))
+
+ (is (= "-- Spec failed --------------------
+
+ [true]
+ ^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ float?
+
+or
+
+ (fn [n] (= n \"int\"))
+
+or
+
+ (fn [n] (= n \"float\"))
+
+-------------------------
+Detected 1 error\n" (expound/expound-str :alt-spec/num-or-str [true] {:print-specs? false})))
+ ;; If two s/alt specs have the same tags, we shouldn't confuse them.
+ (is (= "-- Spec failed --------------------
+
+ {:num-types [true], :str-types ...}
+ ^^^^
+
+should satisfy
+
+ int?
+
+or
+
+ float?
+
+-- Spec failed --------------------
+
+ {:num-types ..., :str-types [false]}
+ ^^^^^
+
+should satisfy
+
+ (fn [n] (= n \"int\"))
+
+or
+
+ (fn [n] (= n \"float\"))
+
+-------------------------
+Detected 2 errors\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str (s/keys :req-un [:alt-spec/num-types :alt-spec/str-types])
+ {:num-types [true] :str-types [false]}))))
+
+ (is (=
+ "-- Spec failed --------------------
+
+ [\"\"]
+
+should satisfy
+
+ nil?
+
+or value
+
+ [\"\"]
+ ^^
+
+should satisfy
+
+ int?
+
+or
+
+ float?
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str
+ (s/nilable (s/cat :n (s/alt :int int? :float float?)))
+ [""]
+ {:print-specs? false})))
+ (is (=
+ ;; This output is not what we want: ideally, the two alternates
+ ;; should be grouped into a single problem.
+ ;; I'm adding it as a spec to avoid regressions and to keep it as
+ ;; an example of something I could improve.
+ ;; The reason we can't do better is that we can't reliably look
+ ;; at the form of a multi-spec. It would be nice if spec inserted
+ ;; the actual spec form that was returned by the multi-spec, but
+ ;; as it stands today, we'd have to figure out how to call the multi-
+ ;; method with the actual value. That would be complicated and
+ ;; potentially have unknown side effects from running arbitrary code.
+
+ "-- Spec failed --------------------
+
+ {:mspec {:tag ..., :one-many-int [[\"1\"]]}}
+ ^^^^^
+
+should satisfy
+
+ int?
+
+-- Spec failed --------------------
+
+ {:mspec {:tag ..., :one-many-int [[\"1\"]]}}
+ ^^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 2 errors\n"
+
+ (expound/expound-str
+ (s/keys
+ :req-un [:alt-spec/mspec])
+ {:mspec
+ {:tag :x
+ :one-many-int [["1"]]}}
+
+ {:print-specs? false}))))
+
+(defn mutate-coll [x]
+ (cond
+ (map? x)
+ (into [] x)
+
+ (vector? x)
+ (into #{} x)
+
+ (set? x)
+ (reverse (into '() x))
+
+ (list? x)
+ (into {} (map vec (partition 2 x)))
+
+ :else
+ x))
+
+(defn mutate-type [x]
+ (cond
+ (number? x)
+ (str x)
+
+ (string? x)
+ (keyword x)
+
+ (keyword? x)
+ (str x)
+
+ (boolean? x)
+ (str x)
+
+ (symbol? x)
+ (str x)
+
+ (char? x)
+ #?(:cljs (.charCodeAt x)
+ :clj (int x))
+
+ (uuid? x)
+ (str x)
+
+ :else
+ x))
+
+(defn mutate [form path]
+ (let [[head & rst] path]
+ (cond
+ (empty? path)
+ (if (coll? form)
+ (mutate-coll form)
+ (mutate-type form))
+
+ (map? form)
+ (if (empty? form)
+ (mutate-coll form)
+ (let [k (nth (keys form) (mod head (count (keys form))))]
+ (assoc form k
+ (mutate (get form k) rst))))
+
+ (vector? form)
+ (if (empty? form)
+ (mutate-coll form)
+ (let [idx (mod head (count form))]
+ (assoc form idx
+ (mutate (nth form idx) rst))))
+
+ (not (coll? form))
+ (mutate-type form)
+
+ :else
+ (mutate-coll form))))
+
+(deftest test-assert2
+ (is (thrown-with-msg?
+ #?(:cljs :default :clj Exception)
+ #"\"Key must be integer\"\n\nshould be one of: \"Extra input\", \"Insufficient input\", \"no method"
+ (binding [s/*explain-out* expound/printer]
+ (try
+ (s/check-asserts true)
+ (s/assert (s/nilable #{"Insufficient input" "Extra input" "no method"}) "Key must be integer")
+ (finally (s/check-asserts false)))))))
+
+(defn inline-specs [keyword]
+ (walk/postwalk
+ (fn [x]
+ (if (contains? (s/registry) x)
+ (s/form x)
+ x))
+ (s/form keyword)))
+
+#?(:clj
+ (deftest real-spec-tests
+ (checking
+ "for any real-world spec and any data, explain-str returns a string"
+ ;; At 50, it might find a bug in failures for the
+ ;; :ring/handler spec, but keep it plugged in, since it
+ ;; takes a long time to shrink
+ (chuck/times num-tests)
+ [spec sg/spec-gen
+ form gen/any-printable]
+ ;; Can't reliably test fspecs until
+ ;; https://dev.clojure.org/jira/browse/CLJ-2258 is fixed
+ ;; because the algorithm to fix up the 'in' paths depends
+ ;; on the non-conforming value existing somewhere within
+ ;; the top-level form
+ (when-not
+ ;; a conformer generally won't work against any arbitrary value
+ ;; e.g. we can't conform 0 with the conformer 'seq'
+ (or (contains? #{:conformers-test/string-AB} spec)
+ (some
+ #{"clojure.spec.alpha/fspec"}
+ (->> spec
+ inline-specs
+ (tree-seq coll? identity)
+ (map str))))
+ (is (string? (expound/expound-str spec form)))))))
+
+#?(:clj
+ (deftest assert-on-real-spec-tests
+ (checking
+ "for any real-world spec and any data, assert returns an error that matches explain-str"
+ (chuck/times num-tests)
+ [spec sg/spec-gen
+ form gen/any-printable]
+ ;; Can't reliably test fspecs until
+ ;; https://dev.clojure.org/jira/browse/CLJ-2258 is fixed
+ ;; because the algorithm to fix up the 'in' paths depends
+ ;; on the non-conforming value existing somewhere within
+ ;; the top-level form
+ (when-not (some
+ #{"clojure.spec.alpha/fspec"}
+ (->> spec
+ inline-specs
+ (tree-seq coll? identity)
+ (map str)))
+ (when-not (s/valid? spec form)
+ (let [expected-err-msg (str "Spec assertion failed\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? true})]
+ (s/explain-str spec form)))]
+ (is (thrown-with-msg?
+ #?(:cljs :default :clj Exception)
+ (re-pattern (java.util.regex.Pattern/quote expected-err-msg))
+ (binding [s/*explain-out* expound/printer]
+ (try
+ (s/check-asserts true)
+ (s/assert spec form)
+ (finally
+ (s/check-asserts false)))))
+ (str "Expected: " expected-err-msg))))))))
+
+(deftest test-mutate
+ (checking
+ "mutation alters data structure"
+ (chuck/times num-tests)
+ [form gen/any-printable
+ mutate-path (gen/vector gen/nat 1 10)]
+ (is (not= form
+ (mutate form mutate-path)))))
+
+#?(:clj
+ 1
+ #_(deftest real-spec-tests-mutated-valid-value
+ ;; FIXME - we need to use generate mutated value, instead
+ ;; of adding randomness to test
+ #_(checking
+ "for any real-world spec and any mutated valid data, explain-str returns a string"
+ (chuck/times num-tests)
+ [spec sg/spec-gen
+ mutate-path (gen/vector gen/pos-int)]
+ (when-not (some
+ #{"clojure.spec.alpha/fspec"}
+ (->> spec
+ inline-specs
+ (tree-seq coll? identity)
+ (map str)))
+ (when (contains? (s/registry) spec)
+ (try
+ (let [valid-form (first (s/exercise spec 1))
+ invalid-form (mutate valid-form mutate-path)]
+ (is (string? (expound/expound-str spec invalid-form))))
+ (catch clojure.lang.ExceptionInfo e
+ (when (not= :no-gen (::s/failure (ex-data e)))
+ (when (not= "Couldn't satisfy such-that predicate after 100 tries." (.getMessage e))
+ (throw e))))))))))
+
+;; Using conformers for transformation should not crash by default, or at least give useful error message.
+(defn numberify [s]
+ (cond
+ (number? s) s
+ (re-matches #"^\d+$" s) #?(:cljs (js/parseInt s 10)
+ :clj (Integer. s))
+ :else ::s/invalid))
+
+(s/def :conformers-test/number (s/conformer numberify))
+
+(defn conform-by
+ [tl-key payload-key]
+ (s/conformer (fn [m]
+ (let [id (get m tl-key)]
+ (if (and id (map? (get m payload-key)))
+ (assoc-in m [payload-key tl-key] id)
+ ::s/invalid)))))
+
+(s/def :conformers-test.query/id qualified-keyword?)
+
+(defmulti query-params :conformers-test.query/id)
+(s/def :conformers-test.query/params (s/multi-spec query-params :conformers-test.query/id))
+(s/def :user/id string?)
+
+(defmethod query-params :conformers-test/lookup-user [_]
+ (s/keys :req [:user/id]))
+
+(s/def :conformers-test/query
+ (s/and
+ (conform-by :conformers-test.query/id :conformers-test.query/params)
+ (s/keys :req [:conformers-test.query/id
+ :conformers-test.query/params])))
+
+(s/def :conformers-test/string-AB-seq (s/cat :a #{\A} :b #{\B}))
+
+(s/def :conformers-test/string-AB
+ (s/and
+ ;; conform as sequence (seq function)
+ (s/conformer #(if (seqable? %) (seq %) %))
+ ;; re-use previous sequence spec
+ :conformers-test/string-AB-seq))
+
+(defn parse-csv [s]
+ (map string/upper-case (string/split s #",")))
+
+(deftest conformers-test
+ ;; Example from http://cjohansen.no/a-unified-specification/
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})
+ *print-namespace-maps* false]
+ (testing "conform string to int"
+ (is (string?
+ (s/explain-str :conformers-test/number "123a"))))
+ ;; Example from https://github.com/bhb/expound/issues/15#issuecomment-326838879
+ (testing "conform maps"
+ (is (string? (s/explain-str :conformers-test/query {})))
+ (is (= "-- Spec failed --------------------
+
+Part of the value
+
+ {:conformers-test.query/id :conformers-test/lookup-user, :conformers-test.query/params {}}
+
+when conformed as
+
+ {:conformers-test.query/id :conformers-test/lookup-user}
+
+should contain key: :user/id
+
+| key | spec |
+|==========+=========|
+| :user/id | string? |
+
+-------------------------
+Detected 1 error\n"
+ (s/explain-str :conformers-test/query {:conformers-test.query/id :conformers-test/lookup-user
+ :conformers-test.query/params {}}))))
+ ;; Minified example based on https://github.com/bhb/expound/issues/15
+ ;; This doesn't look ideal, but really, it's not a good idea to use spec
+ ;; for string parsing, so I'm OK with it
+ (testing "conform string to seq"
+ (is (=
+ ;; clojurescript doesn't have a character type
+ #?(:cljs "-- Spec failed --------------------\n\n \"A\"C\"\"\n ^^^\n\nshould be: \"B\"\n\n-------------------------\nDetected 1 error\n"
+ :clj "-- Spec failed --------------------
+
+ \"A\\C\"
+ ^^
+
+should be: \\B
+
+-------------------------
+Detected 1 error
+")
+ (s/explain-str :conformers-test/string-AB "AC"))))
+ (testing "s/cat"
+ (s/def :conformers-test/sorted-pair (s/and (s/cat :x int? :y int?) #(< (-> % :x) (-> % :y))))
+ (is (= (pf "-- Spec failed --------------------
+
+ [1 0]
+
+when conformed as
+
+ {:x 1, :y 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error
+"
+ #?(:cljs "(fn [%] (< (-> % :x) (-> % :y)))"
+ :clj "(fn
+ [%]
+ (< (-> % :x) (-> % :y)))"))
+ (expound/expound-str :conformers-test/sorted-pair [1 0] {:print-specs? false})))
+ (is (= (pf "-- Spec failed --------------------
+
+ [... [1 0]]
+ ^^^^^
+
+when conformed as
+
+ {:x 1, :y 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "(fn [%] (< (-> % :x) (-> % :y)))"
+ :clj "(fn
+ [%]
+ (< (-> % :x) (-> % :y)))"))
+ (expound/expound-str (s/coll-of :conformers-test/sorted-pair) [[0 1] [1 0]] {:print-specs? false})))
+ (is (= (pf "-- Spec failed --------------------
+
+ {:a [1 0]}
+ ^^^^^
+
+when conformed as
+
+ {:x 1, :y 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:cljs "(fn [%] (< (-> % :x) (-> % :y)))"
+ :clj "(fn
+ [%]
+ (< (-> % :x) (-> % :y)))"))
+ (expound/expound-str (s/map-of keyword? :conformers-test/sorted-pair) {:a [1 0]} {:print-specs? false})))
+ (is (= (pf "-- Spec failed --------------------
+
+ [... \"a\"]
+ ^^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :conformers-test/sorted-pair [1 "a"] {:print-specs? false}))))
+ (testing "conformers that modify path of values"
+ (s/def :conformers-test/vals (s/coll-of (s/and string?
+ #(re-matches #"[A-G]+" %))))
+ (s/def :conformers-test/csv (s/and string?
+ (s/conformer parse-csv)
+ :conformers-test/vals))
+ (is (= "-- Spec failed --------------------
+
+Part of the value
+
+ \"abc,def,ghi\"
+
+when conformed as
+
+ \"GHI\"
+
+should satisfy
+
+ (fn [%] (re-matches #\"[A-G]+\" %))
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str :conformers-test/csv "abc,def,ghi" {:print-specs? false}))))
+
+ ;; this is NOT recommended!
+ ;; so I'm not inclined to make this much nicer than
+ ;; the default spec output
+ (s/def :conformers-test/coerced-kw (s/and (s/conformer #(if (string? %)
+ (keyword %)
+ ::s/invalid))
+ keyword?))
+ (testing "coercion"
+ (is (= (pf "-- Spec failed --------------------
+
+ nil
+
+should satisfy
+
+ (pf.spec.alpha/conformer
+ (fn
+ [%%]
+ (if
+ (string? %%)
+ (keyword %%)
+ :pf.spec.alpha/invalid)))
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str :conformers-test/coerced-kw nil))))
+
+ (is (= (pf "-- Spec failed --------------------
+
+ [... ... ... 0]
+ ^
+
+should satisfy
+
+ (pf.spec.alpha/conformer
+ (fn
+ [%%]
+ (if
+ (string? %%)
+ (keyword %%)
+ :pf.spec.alpha/invalid)))
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str (s/coll-of :conformers-test/coerced-kw) ["a" "b" "c" 0])))))
+ ;; Also not recommended
+ (s/def :conformers-test/str-kw? (s/and (s/conformer #(if (string? %)
+ (keyword %)
+ ::s/invalid)
+ name) keyword?))
+ (testing "coercion with unformer"
+ (is (= (pf "-- Spec failed --------------------
+
+ nil
+
+should satisfy
+
+ (pf.spec.alpha/conformer
+ (fn
+ [%%]
+ (if
+ (string? %%)
+ (keyword %%)
+ :pf.spec.alpha/invalid)))
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str :conformers-test/coerced-kw nil))))
+
+ (is (= (pf "-- Spec failed --------------------
+
+ [... ... ... 0]
+ ^
+
+should satisfy
+
+ (pf.spec.alpha/conformer
+ (fn
+ [%%]
+ (if
+ (string? %%)
+ (keyword %%)
+ :pf.spec.alpha/invalid)))
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str (s/coll-of :conformers-test/coerced-kw) ["a" "b" "c" 0])))))
+
+ (s/def :conformers-test/name string?)
+ (s/def :conformers-test/age pos-int?)
+ (s/def :conformers-test/person (s/keys* :req-un [:conformers-test/name
+ :conformers-test/age]))
+ ;; FIXME: Implementation could be simpler once
+ ;; https://dev.clojure.org/jira/browse/CLJ-2406 is fixed
+ (testing "spec defined with keys*"
+ (is (= "-- Spec failed --------------------
+
+ [... ... ... :Stan]
+ ^^^^^
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 1 error
+"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str :conformers-test/person [:age 30 :name :Stan])))))
+
+ (testing "spec defined with keys* and copies of bad value elsewhere in the data"
+ (is (= "-- Spec failed --------------------
+
+Part of the value
+
+ [:Stan [:age 30 :name :Stan]]
+
+when conformed as
+
+ :Stan
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str (s/tuple
+ keyword?
+ :conformers-test/person) [:Stan [:age 30 :name :Stan]])))))
+
+ (testing "ambiguous value"
+ (is (= (pf "-- Spec failed --------------------
+
+ {[0 1] ..., [1 0] ...}
+ ^^^^^
+
+when conformed as
+
+ {:x 1, :y 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error
+"
+ #?(:cljs "(fn [%] (< (-> % :x) (-> % :y)))"
+ :clj "(fn
+ [%]
+ (< (-> % :x) (-> % :y)))"))
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (s/explain-str (s/map-of :conformers-test/sorted-pair any?) {[0 1] [1 0]
+ [1 0] [1 0]})))))))
+
+(s/def :duplicate-preds/str-or-str (s/or
+ ;; Use anonymous functions to assure
+ ;; non-equality
+ :str1 #(string? %)
+ :str2 #(string? %)))
+(deftest duplicate-preds
+ (testing "duplicate preds only appear once"
+ (is (= (pf "-- Spec failed --------------------
+
+ 1
+
+should satisfy
+
+ (fn [%%] (string? %%))
+
+-- Relevant specs -------
+
+:duplicate-preds/str-or-str:
+ (pf.spec.alpha/or
+ :str1
+ (pf.core/fn [%%] (pf.core/string? %%))
+ :str2
+ (pf.core/fn [%%] (pf.core/string? %%)))
+
+-------------------------
+Detected 1 error
+")
+ (expound/expound-str :duplicate-preds/str-or-str 1)))))
+
+(s/def :fspec-test/div (s/fspec
+ :args (s/cat :x int? :y pos-int?)))
+
+(defn my-div [x y]
+ (assert (not (zero? (/ x y)))))
+
+(defn until-unsuccessful [f]
+ (let [nil-or-failure #(if (= "Success!
+" %)
+ nil
+ %)]
+ (or (nil-or-failure (f))
+ (nil-or-failure (f))
+ (nil-or-failure (f))
+ (nil-or-failure (f))
+ (nil-or-failure (f)))))
+
+(deftest fspec-exception-test
+ (testing "args that throw exception"
+ (is (= (pf "-- Exception ----------------------
+
+ expound.alpha-test/my-div
+
+threw exception
+
+ \"Assert failed: (not (zero? (/ x y)))\"
+
+with args:
+
+ 0, 1
+
+-- Relevant specs -------
+
+:fspec-test/div:
+ (pf.spec.alpha/fspec
+ :args
+ (pf.spec.alpha/cat :x pf.core/int? :y pf.core/pos-int?)
+ :ret
+ pf.core/any?
+ :fn
+ nil)
+
+-------------------------
+Detected 1 error\n")
+
+ ;;
+ (until-unsuccessful #(expound/expound-str :fspec-test/div my-div))))
+
+ (is (= (pf "-- Exception ----------------------
+
+ [expound.alpha-test/my-div]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+threw exception
+
+ \"Assert failed: (not (zero? (/ x y)))\"
+
+with args:
+
+ 0, 1
+
+-- Relevant specs -------
+
+:fspec-test/div:
+ (pf.spec.alpha/fspec
+ :args
+ (pf.spec.alpha/cat :x pf.core/int? :y pf.core/pos-int?)
+ :ret
+ pf.core/any?
+ :fn
+ nil)
+
+-------------------------
+Detected 1 error\n")
+ (until-unsuccessful #(expound/expound-str (s/coll-of :fspec-test/div) [my-div]))))))
+
+(s/def :fspec-ret-test/my-int pos-int?)
+(s/def :fspec-ret-test/plus (s/fspec
+ :args (s/cat :x int? :y pos-int?)
+ :ret :fspec-ret-test/my-int))
+
+(defn my-plus [x y]
+ (+ x y))
+
+(deftest fspec-ret-test
+ (testing "invalid ret"
+ (is (= (pf "-- Function spec failed -----------
+
+ expound.alpha-test/my-plus
+
+returned an invalid value
+
+ 0
+
+should satisfy
+
+ pos-int?
+
+-------------------------
+Detected 1 error\n")
+ (until-unsuccessful #(expound/expound-str :fspec-ret-test/plus my-plus {:print-specs? false}))))
+
+ (is (= (pf "-- Function spec failed -----------
+
+ [expound.alpha-test/my-plus]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+returned an invalid value
+
+ 0
+
+should satisfy
+
+ pos-int?
+
+-------------------------
+Detected 1 error\n")
+ (until-unsuccessful #(expound/expound-str (s/coll-of :fspec-ret-test/plus) [my-plus] {:print-specs? false}))))
+ (s/def :fspec-ret-test/return-map (s/fspec
+ :args (s/cat)
+ :ret (s/keys :req-un [:fspec-ret-test/my-int])))
+ (is (= (pf "-- Function spec failed -----------
+
+
+
+returned an invalid value
+
+ {}
+
+should contain key: :my-int
+
+| key | spec |
+|=========+==========|
+| :my-int | pos-int? |
+
+-------------------------
+Detected 1 error
+")
+ (until-unsuccessful #(expound/expound-str :fspec-ret-test/return-map
+ (fn [] {})
+ {:print-specs? false}))))))
+
+(s/def :fspec-fn-test/minus (s/fspec
+ :args (s/cat :x int? :y int?)
+ :fn (s/and
+ #(< (:ret %) (-> % :args :x))
+ #(< (:ret %) (-> % :args :y)))))
+
+(defn my-minus [x y]
+ (- x y))
+
+(deftest fspec-fn-test
+ (testing "invalid ret"
+ (is (= (pf "-- Function spec failed -----------
+
+ expound.alpha-test/my-minus
+
+failed spec. Function arguments and return value
+
+ {:args {:x 0, :y 0}, :ret 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error\n"
+
+ #?(:clj
+ "(fn
+ [%]
+ (< (:ret %) (-> % :args :x)))"
+ :cljs "(fn [%] (< (:ret %) (-> % :args :x)))"))
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (until-unsuccessful #(s/explain-str :fspec-fn-test/minus my-minus)))))
+
+ (is (= (pf "-- Function spec failed -----------
+
+ [expound.alpha-test/my-minus]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+failed spec. Function arguments and return value
+
+ {:args {:x 0, :y 0}, :ret 0}
+
+should satisfy
+
+ %s
+
+-------------------------
+Detected 1 error\n"
+ #?(:clj
+ "(fn
+ [%]
+ (< (:ret %) (-> % :args :x)))"
+ :cljs "(fn [%] (< (:ret %) (-> % :args :x)))"))
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (until-unsuccessful #(s/explain-str (s/coll-of :fspec-fn-test/minus) [my-minus])))))))
+
+(deftest ifn-fspec-test
+ (testing "keyword ifn / ret failure"
+ (is (= "-- Function spec failed -----------
+
+ [:foo]
+ ^^^^
+
+returned an invalid value
+
+ nil
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (until-unsuccessful #(s/explain-str (s/coll-of (s/fspec :args (s/cat :x int?) :ret int?))
+ [:foo])))))
+ (testing "set ifn / ret failure"
+ (is (= "-- Function spec failed -----------
+
+ [#{}]
+ ^^^
+
+returned an invalid value
+
+ nil
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (until-unsuccessful #(s/explain-str (s/coll-of (s/fspec :args (s/cat :x int?) :ret int?))
+ [#{}])))))))
+ #?(:clj
+ (testing "vector ifn / exception failure"
+ (is (= "-- Exception ----------------------
+
+ [[]]
+ ^^
+
+threw exception
+
+ nil
+
+with args:
+
+ 0
+
+-------------------------
+Detected 1 error\n"
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (until-unsuccessful #(s/explain-str (s/coll-of (s/fspec :args (s/cat :x int?) :ret int?))
+ [[]]))))))))
+
+#?(:clj
+ (deftest form-containing-incomparables
+ (checking
+ "for any value including NaN, or Infinity, expound returns a string"
+ (chuck/times num-tests)
+ [form (gen/frequency
+ [[1 (gen/elements
+ [Double/NaN
+ Double/POSITIVE_INFINITY
+ Double/NEGATIVE_INFINITY
+ '(Double/NaN Double/POSITIVE_INFINITY Double/NEGATIVE_INFINITY)
+ [Double/NaN Double/POSITIVE_INFINITY Double/NEGATIVE_INFINITY]
+ {Double/NaN Double/NaN
+ Double/POSITIVE_INFINITY Double/POSITIVE_INFINITY
+ Double/NEGATIVE_INFINITY Double/NEGATIVE_INFINITY}])]
+ [5 gen/any-printable]])]
+ (is (string? (expound/expound-str (constantly false) form))))))
+
+#?(:cljs
+ (deftest form-containing-incomparables
+ (checking
+ "for any value including NaN, or Infinity, expound returns a string"
+ (chuck/times num-tests)
+ [form (gen/frequency
+ [[1 (gen/elements
+ [js/NaN
+ js/Infinity
+ js/-Infinity
+ '(js/NaN js/Infinity js/-Infinity)
+ [js/NaN js/Infinity js/-Infinity]
+ {js/NaN js/NaN
+ js/Infinity js/Infinity
+ js/-Infinity js/-Infinity}])]
+ [5 gen/any-printable]])]
+ (is (string? (expound/expound-str (constantly false) form))))))
+
+(defmulti pet :pet/type)
+(defmethod pet :dog [_]
+ (s/keys))
+(defmethod pet :cat [_]
+ (s/keys))
+
+(defmulti animal :animal/type)
+(defmethod animal :dog [_]
+ (s/keys))
+(defmethod animal :cat [_]
+ (s/keys))
+
+(s/def :multispec-in-compound-spec/pet1 (s/and
+ map?
+ (s/multi-spec pet :pet/type)))
+
+(s/def :multispec-in-compound-spec/pet2 (s/or
+ :map1 (s/multi-spec pet :pet/type)
+ :map2 (s/multi-spec animal :animal/type)))
+
+(deftest multispec-in-compound-spec
+ (testing "multispec combined with s/and"
+ (is (= (pf "-- Missing spec -------------------
+
+Cannot find spec for
+
+ {:pet/type :fish}
+
+with
+
+ Spec multimethod: `expound.alpha-test/pet`
+ Dispatch value: `:fish`
+
+-- Relevant specs -------
+
+:multispec-in-compound-spec/pet1:
+ (pf.spec.alpha/and
+ pf.core/map?
+ (pf.spec.alpha/multi-spec expound.alpha-test/pet :pet/type))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :multispec-in-compound-spec/pet1 {:pet/type :fish}))))
+ ;; FIXME - improve this, maybe something like:
+ ;;;;;;;;;;;;;;;;;;;
+
+ ;; {:pet/type :fish}
+
+ ;; should be described by a spec multimethod, but
+
+ ;; expound.alpha-test/pet
+
+ ;; is missing a method for value
+
+ ;; (:pet/type {:pet/type :fish}) ; => :fish
+
+ ;; or
+
+ ;; should be described by a spec multimethod, but
+
+ ;; expound.alpha-test/pet
+
+ ;; is missing a method for value
+
+ ;; (:animal/type {:pet/type :fish}) ; => nil
+ (testing "multispec combined with s/or"
+ (is (= (pf "-- Missing spec -------------------
+
+Cannot find spec for
+
+ {:pet/type :fish}
+
+with
+
+ Spec multimethod: `expound.alpha-test/pet`
+ Dispatch value: `:fish`
+
+or with
+
+ Spec multimethod: `expound.alpha-test/animal`
+ Dispatch value: `nil`
+
+-- Relevant specs -------
+
+:multispec-in-compound-spec/pet2:
+ (pf.spec.alpha/or
+ :map1
+ (pf.spec.alpha/multi-spec expound.alpha-test/pet :pet/type)
+ :map2
+ (pf.spec.alpha/multi-spec expound.alpha-test/animal :animal/type))
+
+-------------------------
+Detected 1 error\n")
+ (expound/expound-str :multispec-in-compound-spec/pet2 {:pet/type :fish})))))
+
+(expound/def :predicate-messages/string string? "should be a string")
+(expound/def :predicate-messages/vector vector? "should be a vector")
+
+(deftest predicate-messages
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (testing "predicate with error message"
+ (is (= "-- Spec failed --------------------
+
+ :hello
+
+should be a string
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str :predicate-messages/string :hello))))
+ (testing "predicate within a collection"
+ (is (= "-- Spec failed --------------------
+
+ [... :foo]
+ ^^^^
+
+should be a string
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str (s/coll-of :predicate-messages/string) ["" :foo]))))
+ (testing "two predicates with error messages"
+ (is (= "-- Spec failed --------------------
+
+ 1
+
+should be a string
+
+or
+
+should be a vector
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str (s/or :s :predicate-messages/string
+ :v :predicate-messages/vector) 1))))
+ (testing "one predicate with error message, one without"
+ (is (= "-- Spec failed --------------------
+
+ foo
+
+should satisfy
+
+ pos-int?
+
+or
+
+ vector?
+
+or
+
+should be a string
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str (s/or :p pos-int?
+ :s :predicate-messages/string
+ :v vector?) 'foo))))
+ (testing "compound predicates"
+ (let [email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$"]
+ (expound/def :predicate-messages/email (s/and string? #(re-matches email-regex %)) "should be a valid email address")
+ (is (= "-- Spec failed --------------------
+
+ \"sally@\"
+
+should be a valid email address
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str
+ :predicate-messages/email
+ "sally@"))))
+ (expound/def :predicate-messages/score (s/int-in 0 100) "should be between 0 and 100")
+ (is (= "-- Spec failed --------------------
+
+ 101
+
+should be between 0 and 100
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str
+ :predicate-messages/score
+ 101))))))
+
+(s/fdef results-str-fn1
+ :args (s/cat :x nat-int? :y nat-int?)
+ :ret pos?)
+(defn results-str-fn1 [x y]
+ #?(:clj (+' x y)
+ :cljs (+ x y)))
+
+(s/fdef results-str-fn2
+ :args (s/cat :x nat-int? :y nat-int?)
+ :fn #(let [x (-> % :args :x)
+ ret (-> % :ret)]
+ (< x ret)))
+(defn results-str-fn2 [x y]
+ (+ x y))
+
+(s/fdef results-str-fn3
+ :args (s/cat :x #{0} :y #{0})
+ :ret nat-int?)
+(defn results-str-fn3 [x y]
+ (+ x y))
+
+(s/fdef results-str-fn4
+ :args (s/cat :x int?)
+ :ret (s/coll-of int?))
+(defn results-str-fn4 [x]
+ [x :not-int])
+
+(s/fdef results-str-fn5
+ :args (s/cat :x #{1} :y #{1})
+ :ret string?)
+(defn results-str-fn5
+ [_x _y]
+ #?(:clj (throw (Exception. "Ooop!"))
+ :cljs (throw (js/Error. "Oops!"))))
+
+(s/fdef results-str-fn6
+ :args (s/cat :f fn?)
+ :ret any?)
+(defn results-str-fn6
+ [f]
+ (f 1))
+
+(s/def :results-str-fn7/k string?)
+(s/fdef results-str-fn7
+ :args (s/cat :m (s/keys))
+ :ret (s/keys :req-un [:results-str-fn7/k]))
+(defn results-str-fn7
+ [m]
+ m)
+
+(s/fdef results-str-missing-fn
+ :args (s/cat :x int?))
+
+(s/fdef results-str-missing-args-spec
+ :ret int?)
+(defn results-str-missing-args-spec [] 1)
+
+(deftest explain-results
+ (testing "explaining results with non-expound printer"
+ (is (thrown-with-msg?
+ #?(:cljs :default :clj Exception)
+ #"Cannot print check results"
+ (binding [s/*explain-out* s/explain-printer]
+ (expound/explain-results-str (st/check `results-str-fn1))))))
+
+ (testing "single bad result (failing return spec)"
+ (is (= (pf
+ "== Checked expound.alpha-test/results-str-fn1
+
+-- Function spec failed -----------
+
+ (expound.alpha-test/results-str-fn1 0 0)
+
+returned an invalid value.
+
+ 0
+
+should satisfy
+
+ pos?
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (orch-unstrument-test-fns #(st/check `results-str-fn1))))))
+ (is (= (pf
+ "== Checked expound.alpha-test/results-str-fn7
+
+-- Function spec failed -----------
+
+ (expound.alpha-test/results-str-fn7 {})
+
+returned an invalid value.
+
+ {}
+
+should contain key: :k
+
+| key | spec |
+|=====+=========|
+| :k | string? |
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (orch-unstrument-test-fns #(st/check `results-str-fn7)))))))
+ (testing "single bad result (failing fn spec)"
+ (is (= (pf "== Checked expound.alpha-test/results-str-fn2
+
+-- Function spec failed -----------
+
+ (expound.alpha-test/results-str-fn2 0 0)
+
+failed spec. Function arguments and return value
+
+ {:args {:x 0, :y 0}, :ret 0}
+
+should satisfy
+
+ (fn
+ [%%]
+ (let
+ [x (-> %% :args :x) ret (-> %% :ret)]
+ (< x ret)))
+
+-------------------------
+Detected 1 error
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (orch-unstrument-test-fns #(st/check `results-str-fn2)))))))
+ (testing "single valid result"
+ (is (= "== Checked expound.alpha-test/results-str-fn3
+
+Success!
+"
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (st/check `results-str-fn3))))))
+ #?(:clj
+ (testing "multiple results"
+ (is (= "== Checked expound.alpha-test/results-str-fn2
+
+-- Function spec failed -----------
+
+ (expound.alpha-test/results-str-fn2 0 0)
+
+failed spec. Function arguments and return value
+
+ {:args {:x 0, :y 0}, :ret 0}
+
+should satisfy
+
+ (fn
+ [%]
+ (let
+ [x (-> % :args :x) ret (-> % :ret)]
+ (< x ret)))
+
+-------------------------
+Detected 1 error
+
+
+== Checked expound.alpha-test/results-str-fn3
+
+Success!
+"
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (orch-unstrument-test-fns #(st/check [`results-str-fn2 `results-str-fn3]))))))))
+
+ (testing "check-fn"
+ (is (= "== Checked ========================
+
+-- Function spec failed -----------
+
+ ( 0 0)
+
+failed spec. Function arguments and return value
+
+ {:args {:x 0, :y 0}, :ret 0}
+
+should satisfy
+
+ (fn
+ [%]
+ (let
+ [x (-> % :args :x) ret (-> % :ret)]
+ (< x ret)))
+
+-------------------------
+Detected 1 error
+"
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-result-str (st/check-fn `results-str-fn1 (s/spec `results-str-fn2)))))))
+ #?(:clj (testing "custom printer"
+ (is (= "== Checked expound.alpha-test/results-str-fn4
+
+-- Function spec failed -----------
+
+ (expound.alpha-test/results-str-fn4 0)
+
+returned an invalid value.
+
+ [0 :not-int]
+ ^^^^^^^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error
+"
+ (binding [s/*explain-out* (expound/custom-printer {:show-valid-values? true})]
+ (expound/explain-results-str (orch-unstrument-test-fns #(st/check `results-str-fn4))))))))
+ (testing "exceptions raised during check"
+ (is (= "== Checked expound.alpha-test/results-str-fn5
+
+ (expound.alpha-test/results-str-fn5 1 1)
+
+ threw error"
+ (binding [s/*explain-out* expound/printer]
+ (take-lines 5 (expound/explain-results-str (st/check `results-str-fn5)))))))
+ (testing "colorized output"
+ (is (= (pf "== Checked expound.alpha-test/results-str-fn5
+
+ (expound.alpha-test/results-str-fn5 1 1)
+
+ threw error")
+ (binding [s/*explain-out* (expound/custom-printer {:theme :figwheel-theme})]
+ (readable-ansi (take-lines 5 (expound/explain-results-str (st/check `results-str-fn5))))))))
+
+ (testing "failure to generate"
+ (is (=
+ #?(:clj "== Checked expound.alpha-test/results-str-fn6
+
+Unable to construct generator for [:f] in
+
+ (clojure.spec.alpha/cat :f clojure.core/fn?)
+"
+ ;; CLJS doesn't contain correct data for check failure
+
+ :cljs "== Checked expound.alpha-test/results-str-fn6
+
+Unable to construct gen at: [:f] for: fn? in
+
+ (cljs.spec.alpha/cat :f cljs.core/fn?)
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (st/check `results-str-fn6))))))
+ (testing "no-fn failure"
+ (is (= #?(:clj "== Checked expound.alpha-test/results-str-missing-fn
+
+Failed to check function.
+
+ expound.alpha-test/results-str-missing-fn
+
+is not defined
+"
+ :cljs "== Checked ========================
+
+Failed to check function.
+
+
+
+is not defined
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (st/check `results-str-missing-fn))))))
+ (testing "no args spec"
+ (is (= (pf "== Checked expound.alpha-test/results-str-missing-args-spec
+
+Failed to check function.
+
+ (pf.spec.alpha/fspec :ret pf.core/int?)
+
+should contain an :args spec
+")
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str (st/with-instrument-disabled (st/check `results-str-missing-args-spec))))))))
+
+#?(:clj (deftest explain-results-gen
+ (checking
+ "all functions can be checked and printed"
+ (chuck/times num-tests)
+ [sym-to-check (gen/elements (remove
+ ;; these functions print to stdout, but return
+ ;; nothing
+ #{`expound/explain-results
+ `expound/explain-result
+ `expound/expound
+ `expound/printer}
+ (st/checkable-syms)))]
+ ;; Just confirm an error is not thrown
+ (is (string?
+ (binding [s/*explain-out* expound/printer]
+ (expound/explain-results-str
+ (st/with-instrument-disabled
+ (st/check sym-to-check
+ {:clojure.spec.test.check/opts {:num-tests 10}})))))
+ (str "Failed to check " sym-to-check)))))
+
+(s/def :colorized-output/strings (s/coll-of string?))
+(deftest colorized-output
+ (is (= (pf "-- Spec failed --------------------
+
+ [... :a ...]
+ ^^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:colorized-output/strings:
+ (pf.spec.alpha/coll-of pf.core/string?)
+
+-------------------------
+Detected 1 error
+")
+ (expound/expound-str :colorized-output/strings ["" :a ""] {:theme :none})))
+ (is (= (pf "-- Spec failed --------------------
+
+ [... :a ...]
+ ^^
+
+should satisfy
+
+ string?
+
+-- Relevant specs -------
+
+:colorized-output/strings:
+ (pf.spec.alpha/coll-of pf.core/string?)
+
+-------------------------
+Detected 1 error
+")
+ (readable-ansi (expound/expound-str :colorized-output/strings ["" :a ""] {:theme :figwheel-theme})))))
+
+(s/def ::spec-name (s/with-gen
+ qualified-keyword?
+ #(gen/let [kw gen/keyword]
+ (keyword (str "expound-generated-spec/" (name kw))))))
+
+(s/def ::fn-spec (s/with-gen
+ (s/or
+ :sym symbol?
+ :anon (s/cat :fn #{`fn `fn*}
+ :args-list (s/coll-of any? :kind vector?)
+ :body (s/* any?))
+ :form (s/cat :comp #{`comp `partial}
+ :args (s/+ any?)))
+ #(gen/return `any?)))
+
+(s/def ::pred-spec
+ (s/with-gen
+ ::fn-spec
+ #(gen/elements
+ [`any?
+ `boolean?
+ `bytes?
+ `double?
+ `ident?
+ `indexed?
+ `int?
+ `keyword?
+ `map?
+ `nat-int?
+ `neg-int?
+ `pos-int?
+ `qualified-ident?
+ `qualified-keyword?
+ `qualified-symbol?
+ `seqable?
+ `simple-ident?
+ `simple-keyword?
+ `simple-symbol?
+ `string?
+ `symbol?
+ `uri?
+ `uuid?
+ `vector?])))
+
+(s/def ::and-spec (s/cat
+ :and #{`s/and}
+ :branches (s/+
+ ::spec)))
+
+(s/def ::or-spec (s/cat
+ :or #{`s/or}
+ :branches (s/+
+ (s/cat
+ :kw keyword?
+ :spec ::spec))))
+
+(s/def ::set-spec (s/with-gen
+ (s/coll-of
+ any?
+ :kind set?
+ :min-count 1)
+ #(s/gen (s/coll-of
+ (s/or
+ :s string?
+ :i int?
+ :b boolean?
+ :k keyword?)
+ :kind set?))))
+
+(s/def ::spec (s/or
+ :amp ::amp-spec
+ :alt ::alt-spec
+ :and ::and-spec
+ :cat ::cat-spec
+ :coll ::coll-spec
+ :defined-spec ::spec-name
+ :every ::every-spec
+ :fspec ::fspec-spec
+ :keys ::keys-spec
+ :map ::map-of-spec
+ :merge ::merge-spec
+ :multi ::multispec-spec
+ :nilable ::nilable-spec
+ :or ::or-spec
+ :regex-unary ::regex-unary-spec
+ :set ::set-spec
+ :simple ::pred-spec
+ :spec-wrapper (s/cat :wrapper #{`s/spec} :spec ::spec)
+ :conformer (s/cat
+ :conformer #{`s/conformer}
+ :f ::fn-spec
+ :unf ::fn-spec)
+ :with-gen (s/cat
+ :with-gen #{`s/with-gen}
+ :spec ::spec
+ :f ::fn-spec)
+ :tuple-spec ::tuple-spec))
+
+(s/def ::every-opts (s/*
+ (s/alt
+ :kind (s/cat
+ :k #{:kind}
+ :v #{nil
+ vector? set? map? list?
+ `vector? `set? `map? `list?})
+ :count (s/cat
+ :k #{:count}
+ :v (s/nilable nat-int?))
+ :min-count (s/cat
+ :k #{:min-count}
+ :v (s/nilable nat-int?))
+ :max-count (s/cat
+ :k #{:max-count}
+ :v (s/nilable nat-int?))
+ :distinct (s/cat
+ :k #{:distinct}
+ :v (s/nilable boolean?))
+ :into (s/cat
+ :k #{:into}
+ :v (s/or :coll #{[] {} #{}}
+ :list #{'()}))
+ :gen-max (s/cat
+ :k #{:gen-max}
+ :v nat-int?))))
+
+(s/def ::every-spec (s/cat
+ :every #{`s/every}
+ :spec ::spec
+ :opts ::every-opts))
+
+(s/def ::coll-spec (s/cat
+ :coll-of #{`s/coll-of}
+ :spec (s/spec ::spec)
+ :opts ::every-opts))
+
+(s/def ::map-of-spec (s/cat
+ :map-of #{`s/map-of}
+ :k ::spec
+ :w ::spec
+ :opts ::every-opts))
+
+(s/def ::nilable-spec (s/cat
+ :nilable #{`s/nilable}
+ :spec ::spec))
+
+(s/def ::name-combo
+ (s/or
+ :one ::spec-name
+ :combo (s/cat
+ :operator #{'and 'or}
+ :operands
+ (s/+
+ ::name-combo))))
+
+(s/def ::keys-spec (s/cat
+ :keys #{`s/keys `s/keys*}
+
+ :reqs (s/*
+ (s/cat
+ :op #{:req :req-un}
+ :names (s/coll-of
+ ::name-combo
+ :kind vector?)))
+ :opts (s/*
+ (s/cat
+ :op #{:opt :opt-un}
+ :names (s/coll-of
+ ::spec-name
+ :kind vector?)))))
+
+(s/def ::amp-spec
+ (s/cat :op #{`s/&}
+ :spec ::spec
+ :preds (s/*
+ (s/with-gen
+ (s/or :pred ::pred-spec
+ :defined ::spec-name)
+ #(gen/return `any?)))))
+
+(s/def ::alt-spec
+ (s/cat :op #{`s/alt}
+ :key-pred-forms (s/+
+ (s/cat
+ :key keyword?
+ :pred (s/spec ::spec)))))
+
+(s/def ::regex-unary-spec
+ (s/cat :op #{`s/+ `s/* `s/?} :pred (s/spec ::spec)))
+
+(s/def ::cat-pred-spec
+ (s/or
+ :spec (s/spec ::spec)
+ :regex-unary ::regex-unary-spec
+ :amp ::amp-spec
+ :alt ::alt-spec))
+
+(defmulti fake-multimethod :fake-tag)
+
+(s/def ::multispec-spec
+ (s/cat
+ :mult-spec #{`s/multi-spec}
+ :mm (s/with-gen
+ symbol?
+ #(gen/return `fake-multimethod))
+ :tag (s/with-gen
+ (s/or :sym symbol?
+ :k keyword?)
+ #(gen/return :fake-tag))))
+
+(s/def ::cat-spec (s/cat
+ :cat #{`s/cat}
+ :key-pred-forms
+ (s/*
+ (s/cat
+ :key keyword?
+ :pred ::cat-pred-spec))))
+
+(s/def ::fspec-spec (s/cat
+ :cat #{`s/fspec}
+ :args (s/cat
+ :args #{:args}
+ :spec ::spec)
+ :ret (s/?
+ (s/cat
+ :ret #{:ret}
+ :spec ::spec))
+ :fn (s/?
+ (s/cat
+ :fn #{:fn}
+ :spec (s/nilable ::spec)))))
+
+(s/def ::tuple-spec (s/cat
+ :tuple #{`s/tuple}
+ :preds (s/+
+ ::spec)))
+
+(s/def ::merge-spec (s/cat
+ :merge #{`s/merge}
+ :pred-forms (s/* ::spec)))
+
+(s/def ::spec-def (s/cat
+ :def #{`s/def}
+ :name ::spec-name
+ :spec (s/spec ::spec)))
+
+#?(:clj (s/def ::spec-defs (s/coll-of ::spec-def
+ :min-count 1
+ :gen-max 3)))
+
+(defn exercise-count [spec]
+ (case spec
+ (::spec-def ::fspec-spec ::regex-unary-spec ::spec-defs ::alt-spec) 1
+
+ (::cat-spec ::merge-spec ::and-spec ::every-spec ::spec ::coll-spec ::map-of-spec ::or-spec ::tuple-spec ::keys-spec) 2
+
+ 4))
+
+(deftest spec-specs-can-generate
+ (doseq [spec-spec (filter keyword? (sg/topo-sort (filter #(= "expound.alpha-test" (namespace %))
+ (keys (s/registry)))))]
+ (is
+ (doall (s/exercise spec-spec (exercise-count spec-spec)))
+ (str "Failed to generate examples for spec " spec-spec))))
+
+#_(defn sample-seq
+ "Return a sequence of realized values from `generator`."
+ [generator seed]
+ (s/assert some? generator)
+ (let [max-size 1
+ r (if seed
+ (random/make-random seed)
+ (random/make-random))
+ size-seq (gen/make-size-range-seq max-size)]
+ (map #(rose/root (gen/call-gen generator %1 %2))
+ (gen/lazy-random-states r)
+ size-seq)))
+
+#_(defn missing-specs [spec-defs]
+ (let [defined (set (map second spec-defs))
+ used (set
+ (filter
+ #(and (qualified-keyword? %)
+ (= "expound-generated-spec" (namespace %)))
+ (tree-seq coll? seq spec-defs)))]
+ (set/difference used defined)))
+
+#?(:clj 1 #_(deftest eval-gen-test
+ ;; FIXME - this is a useful test but not 100% reliable yet
+ ;; so I'm disabling to get this PR in
+ (binding [s/*recursion-limit* 2]
+ (checking
+ "expound returns string"
+ 5 ;; Hard-code at 5, since generating specs explodes in size quite quickly
+ [spec-defs (s/gen ::spec-defs)
+ pred-specs (gen/vector (s/gen ::pred-spec) 5)
+ seed (s/gen pos-int?)
+ mutate-path (gen/vector gen/pos-int)]
+ (try
+ (doseq [[spec-name spec] (map vector (missing-specs spec-defs) (cycle pred-specs))]
+ (eval `(s/def ~spec-name ~spec)))
+ (doseq [spec-def spec-defs]
+ (eval spec-def))
+
+ (let [spec (second (last spec-defs))
+ form (last (last spec-defs))
+ disallowed #{"clojure.spec.alpha/fspec"
+ "clojure.spec.alpha/multi-spec"
+ "clojure.spec.alpha/with-gen"}]
+ (when-not (or (some
+ disallowed
+ (map str (tree-seq coll? identity form)))
+ (some
+ disallowed
+ (->> spec
+ inline-specs
+ (tree-seq coll? identity)
+ (map str))))
+ (let [valid-form (first (sample-seq (s/gen spec) seed))
+ invalid-form (mutate valid-form mutate-path)]
+ (try
+ (is (string?
+ (expound/expound-str spec invalid-form)))
+ (is (not
+ (string/includes?
+ (expound/expound-str (second (last spec-defs)) invalid-form)
+ "should contain keys")))
+ (catch Exception e
+ (is (or
+ (string/includes?
+ (:cause (Throwable->map e))
+ "Method code too large!")
+ (string/includes?
+ (:cause (Throwable->map e))
+ "Cannot convert path."))))))))
+ (finally
+ ;; Get access to private atom in clojure.spec
+ (def spec-reg (deref #'s/registry-ref))
+ (doseq [k (filter
+ (fn [k] (= "expound-generated-spec" (namespace k)))
+ (keys (s/registry)))]
+ (swap! spec-reg dissoc k))))))))
+
+(deftest clean-registry
+ (testing "only base spec remains"
+ (is (<= (count (filter
+ (fn [k] (= "expound-generated-spec" (namespace k)))
+ (keys (s/registry))))
+ 1)
+ (str "Found leftover specs: " (vec (filter
+ (fn [k] (= "expound-generated-spec" (namespace k)))
+ (keys (s/registry))))))))
+
+(deftest valid-spec-spec
+ (checking
+ "spec for specs validates against real specs"
+ (chuck/times num-tests)
+ [sp (gen/elements
+ (sg/topo-sort
+ (remove
+ (fn [k]
+ (string/includes? (pr-str (s/form (s/get-spec k))) "clojure.core.specs.alpha/quotable"))
+ (filter
+ (fn [k] (or
+ (string/starts-with? (namespace k) "clojure")
+ (string/starts-with? (namespace k) "expound")
+ (string/starts-with? (namespace k) "onyx")
+ (string/starts-with? (namespace k) "ring")))
+ (keys (s/registry))))))]
+ (is (s/valid? ::spec (s/form (s/get-spec sp)))
+ (str
+ "Spec name: " sp "\n"
+ "Error: "
+ (binding [s/*explain-out* (expound/custom-printer {:show-valid-values? true
+ :print-specs? false
+ :theme :figwheel-theme})]
+ (s/explain-str ::spec (s/form (s/get-spec sp))))))))
+
+(defmethod expound/problem-group-str ::test-problem1 [_type _spec-name _val _path _problems _opts]
+ "fake-problem-group-str")
+
+(defmethod expound/problem-group-str ::test-problem2 [type spec-name val path problems opts]
+ (str "fake-problem-group-str\n"
+ (expound/expected-str type spec-name val path problems opts)))
+
+(defmethod expound/expected-str ::test-problem2 [_type _spec-name _val _path _problems _opts]
+ "fake-expected-str")
+
+(deftest extensibility-test
+ (testing "can overwrite entire message"
+ (let [printer-str #'expound/printer-str
+ ed (assoc-in (s/explain-data int? "")
+ [::s/problems 0 :expound.spec.problem/type]
+ ::test-problem1)]
+
+ (is (= "fake-problem-group-str\n\n-------------------------\nDetected 1 error\n"
+ (printer-str {:print-specs? false} ed)))))
+ (testing "can overwrite 'expected' str"
+ (let [printer-str #'expound/printer-str
+ ed (assoc-in (s/explain-data int? "")
+ [::s/problems 0 :expound.spec.problem/type]
+ ::test-problem2)]
+
+ (is (= "fake-problem-group-str\nfake-expected-str\n\n-------------------------\nDetected 1 error\n"
+ (printer-str {:print-specs? false} ed)))))
+ (testing "if type has no mm implemented, throw an error"
+ (let [printer-str #'expound/printer-str
+ ed (assoc-in (s/explain-data int? "")
+ [::s/problems 0 :expound.spec.problem/type]
+ ::test-problem3)]
+
+ (is (thrown-with-msg?
+ #?(:cljs :default :clj Exception)
+ #"No method in multimethod"
+ (printer-str {:print-specs? false} ed))))))
+
+#?(:clj (deftest macroexpansion-errors
+ (let [actual (formatted-exception {:print-specs? false} #(macroexpand '(clojure.core/let [a] 2)))]
+ (is (or
+ (= "Call to #'clojure.core/let did not conform to spec.
+-- Spec failed --------------------
+
+ ([a] ...)
+ ^^^
+
+should satisfy
+
+ even-number-of-forms?
+
+-------------------------
+Detected 1 error\n"
+ actual)
+ (= "Call to clojure.core/let did not conform to spec.
+-- Spec failed --------------------
+
+ ([a] ...)
+ ^^^
+
+should satisfy
+
+ even-number-of-forms?
+
+-------------------------
+Detected 1 error\n"
+ actual))))
+ (let [ed (try
+ (macroexpand '(clojure.core/let [a] 2))
+ (catch Exception e
+ (-> (Throwable->map e) :via last :data)))]
+ (is (= "-- Spec failed --------------------
+
+ ([a] ...)
+ ^^^
+
+should satisfy
+
+ even-number-of-forms?
+
+-------------------------
+Detected 1 error\n"
+ (with-out-str ((expound/custom-printer {:print-specs? false})
+
+ ed)))))))
+
+(deftest sorted-map-values
+ (is (= "-- Spec failed --------------------
+
+ {\"bar\" 1}
+
+should satisfy
+
+ number?
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ number?
+ (sorted-map "bar" 1))))
+ (is (= "-- Spec failed --------------------
+
+ {:foo {\"bar\" 1}}
+
+should satisfy
+
+ number?
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ number?
+ {:foo (sorted-map "bar"
+
+ 1)}))))
+
+(defn select-expound-info [spec value]
+ (->> (s/explain-data spec value)
+ (problems/annotate)
+ (:expound/problems)
+ (map #(select-keys % [:expound.spec.problem/type :expound/in]))
+ (set)))
+
+#?(:clj
+ (deftest or-includes-problems-for-each-branch
+ (let [p1 (select-expound-info :ring.sync/handler (fn handler [_req] {}))
+ p2 (select-expound-info :ring.async/handler (fn handler [_req] {}))
+ p3 (select-expound-info :ring.sync+async/handler (fn handler [_req] {}))
+ all-problems (select-expound-info :ring/handler (fn handler [_req] {}))]
+
+ (is (set/subset? p1 all-problems) {:extra (set/difference p1 all-problems)})
+ (is (set/subset? p2 all-problems) {:extra (set/difference p2 all-problems)})
+ (is (set/subset? p3 all-problems) {:extra (set/difference p3 all-problems)})))
+ :cljs
+ (set/index #{} [:x]) ; noop to keep clj-kondo happy
+ )
+
+(deftest defmsg-test
+ (s/def :defmsg-test/id1 string?)
+ (expound/defmsg :defmsg-test/id1 "should be a string ID")
+ (testing "messages for predicate specs"
+ (is (= "-- Spec failed --------------------
+
+ 123
+
+should be a string ID
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ :defmsg-test/id1
+ 123
+ {:print-specs? false}))))
+
+ (s/def :defmsg-test/id2 (s/and string?
+ #(<= 4 (count %))))
+ (expound/defmsg :defmsg-test/id2 "should be a string ID of length 4 or more")
+ (testing "messages for 'and' specs"
+ (is (= "-- Spec failed --------------------
+
+ \"123\"
+
+should be a string ID of length 4 or more
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ :defmsg-test/id2
+ "123"
+ {:print-specs? false}))))
+
+ (s/def :defmsg-test/statuses #{:ok :failed})
+ (expound/defmsg :defmsg-test/statuses "should be either :ok or :failed")
+ (testing "messages for set specs"
+ (is (= "-- Spec failed --------------------
+
+ :oak
+
+should be either :ok or :failed
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str
+ :defmsg-test/statuses
+ :oak
+ {:print-specs? false}))))
+ (testing "messages for alt specs"
+ (s/def ::x int?)
+ (s/def ::y int?)
+ (expound/defmsg ::x "must be an integer")
+ (is (=
+ "-- Spec failed --------------------
+
+ [\"\" ...]
+ ^^
+
+must be an integer
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str (s/alt :one
+ (s/cat :x ::x)
+ :two
+ (s/cat :x ::x
+ :y ::y))
+
+ ["" ""]
+ {:print-specs? false}))))
+
+ (testing "messages for alt specs (if user duplicates existing message)"
+ (s/def ::x int?)
+ (s/def ::y int?)
+ (expound/defmsg ::x "should satisfy\n\n int?")
+ (is (=
+ "-- Spec failed --------------------
+
+ [\"\"]
+ ^^
+
+should satisfy
+
+ int?
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str (s/alt :one
+ ::x
+ :two
+ ::y)
+ [""]
+ {:print-specs? false}))))
+ (testing "messages for alternatives and set specs"
+ (is (= "-- Spec failed --------------------
+
+ :oak
+
+should be either :ok or :failed
+
+or
+
+should satisfy
+
+ string?
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str
+ (s/or
+ :num
+ :defmsg-test/statuses
+ :s string?)
+ :oak
+ {:print-specs? false})))))
+
+(deftest printer
+ (st/instrument ['expound/printer])
+ (binding [s/*explain-out* expound/printer]
+ (is (string? (s/explain-str int? "a")))
+ (is (= "Success!\n" (s/explain-str int? 1)))
+ (is (= "Success!\n" (with-out-str (expound/printer (s/explain-data int? 1))))))
+ (st/unstrument ['expound/printer]))
+
+(deftest undefined-key
+ (is (= "-- Spec failed --------------------
+
+ {}
+
+should contain key: :undefined-key/does-not-exist
+
+| key | spec |
+|===============================+===============================|
+| :undefined-key/does-not-exist | :undefined-key/does-not-exist |
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str (s/keys :req [:undefined-key/does-not-exist])
+ {}
+ {:print-specs? false}))))
+
+#?(:clj
+ (deftype FakeDB [m]
+
+ clojure.lang.Seqable
+ (seq [_]
+ (seq m))
+
+ clojure.lang.IPersistentCollection
+
+ (count [_]
+ (count m))
+ (cons [_ _o]
+ (throw (Exception. "FakeDB doesn't implement 'cons'")))
+ (empty [_]
+ (FakeDB. {}))
+ (equiv [_ o]
+ (=
+ m
+ (:m o)))
+
+ clojure.lang.Associative
+ (containsKey [_ k] (contains? m k))
+ (entryAt [_ k] (get m k))
+
+ clojure.lang.IPersistentMap
+ (assoc [_this _k _v] (throw (Exception. "FakeDB doesn't implement 'assoc'")))
+ (assocEx [_this _k _v] (throw (Exception. "FakeDB doesn't implement 'assocEx'")))
+ (without [_this _k] (throw (Exception. "FakeDB doesn't implement 'without'")))
+
+ clojure.lang.ILookup
+ (valAt [_ k]
+ (get m k))
+ (valAt [_ k not-found]
+ (get m k not-found))))
+
+(s/def ::db-val (s/or :i int? :s string?))
+
+;; https://github.com/bhb/expound/issues/205
+#?(:clj (deftest unwalkable-values
+ ;; run bin/test-datomic for real test of datomic DB,
+ ;; but this at least simulates the failure. We should not
+ ;; try to walk arbitrary values
+ (let [db (FakeDB. {:a 1})]
+ (is (= true (map? db)))
+ (is (= "Success!\n"
+ (expound/expound-str some? db)))
+ (is (= "-- Spec failed --------------------
+
+ [{:a 1}]
+ ^^^^^^
+
+should contain key: :expound.alpha-test/db-val
+
+| key | spec |
+|============================+=========================|
+| :expound.alpha-test/db-val | (or :i int? :s string?) |
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str (s/cat :db (s/keys
+ :req [::db-val])) [db]))))))
+
+;; https://github.com/bhb/expound/issues/217
+(deftest small-values-for-print-length
+ (binding [*print-length* 5]
+ (is (= "-- Spec failed --------------------
+
+ 9
+
+ in
+
+ (0 1 2 3 4 ...)
+
+should satisfy
+
+ (fn [x] (< x 9))
+
+-------------------------
+Detected 1 error
+"
+ (expound/expound-str
+ (clojure.spec.alpha/coll-of (fn [x] (< x 9)))
+ (range 10))))))
+
+;; https://github.com/bhb/expound/issues/215
+(s/def :keys-within-operators.user/name string?)
+(s/def :keys-within-operators.user/age pos-int?)
+
+(deftest keys-within-operators
+
+ (is (= "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :age, :keys-within-operators.user/name
+
+| key | spec |
+|==================================+==========|
+| :age | pos-int? |
+|----------------------------------+----------|
+| :keys-within-operators.user/name | string? |
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str (s/and (s/keys :req [:keys-within-operators.user/name]
+ :req-un [:keys-within-operators.user/age])
+ #(contains? % :foo)) {} {:print-specs? false})))
+
+ (is (= "-- Spec failed --------------------
+
+ {}
+
+should contain keys: :age, :foo, :keys-within-operators.user/name
+
+| key | spec |
+|==================================+===================================================|
+| :age | pos-int? |
+|----------------------------------+---------------------------------------------------|
+| :foo | |
+|----------------------------------+---------------------------------------------------|
+| :keys-within-operators.user/name | string? |
+
+-------------------------
+Detected 1 error\n"
+ (expound/expound-str (s/or :k1 (s/keys :req [:keys-within-operators.user/name]
+ :req-un [:keys-within-operators.user/age])
+ :k2 #(contains? % :foo)) {} {:print-specs? false}))))
diff --git a/test-resources/lib_tests/expound/paths_test.cljc b/test-resources/lib_tests/expound/paths_test.cljc
new file mode 100644
index 00000000..e64a5136
--- /dev/null
+++ b/test-resources/lib_tests/expound/paths_test.cljc
@@ -0,0 +1,39 @@
+(ns expound.paths-test
+ (:require [clojure.test :as ct :refer [is deftest use-fixtures]]
+ [clojure.test.check.generators :as gen]
+ [com.gfredericks.test.chuck.clojure-test :refer [checking]]
+ [expound.paths :as paths]
+ [expound.test-utils :as test-utils]
+ [com.gfredericks.test.chuck :as chuck]))
+
+(def num-tests 100)
+
+(use-fixtures :once
+ test-utils/check-spec-assertions
+ test-utils/instrument-all)
+
+(deftest compare-paths-test
+ (checking
+ "path to a key comes before a path to a value"
+ 10
+ [k gen/simple-type-printable]
+ (is (= -1 (paths/compare-paths [(paths/->KeyPathSegment k)] [k])))
+ (is (= 1 (paths/compare-paths [k] [(paths/->KeyPathSegment k)])))))
+
+(defn nth-value [form i]
+ (let [seq (remove map-entry? (tree-seq coll? seq form))]
+ (nth seq (mod i (count seq)))))
+
+(deftest paths-to-value-test
+ (checking
+ "value-in is inverse of paths-to-value"
+ (chuck/times num-tests)
+ [form test-utils/any-printable-wo-nan
+ i gen/nat
+ :let [x (nth-value form i)
+ paths (paths/paths-to-value form x [] [])]]
+ (is (seq paths))
+ (doseq [path paths]
+ (is (= x
+ (paths/value-in form
+ path))))))
diff --git a/test-resources/lib_tests/expound/print_length_test.cljc b/test-resources/lib_tests/expound/print_length_test.cljc
new file mode 100644
index 00000000..7c89fb58
--- /dev/null
+++ b/test-resources/lib_tests/expound/print_length_test.cljc
@@ -0,0 +1,23 @@
+(ns expound.print-length-test
+ (:require [clojure.test :as ct :refer [is deftest testing]]
+ [clojure.spec.alpha :as s]
+ [expound.alpha]
+ [clojure.string :as string]))
+
+(def the-value (range 10))
+;; Fails on the last element of the range
+(def the-spec (s/coll-of #(< % 9)))
+(def the-explanation (s/explain-data the-spec the-value))
+
+(deftest print-length-test
+ (testing "Expound works even in face of a low `*print-length*` and `*print-level*`, without throwing exceptions.
+See https://github.com/bhb/expound/issues/217"
+ (doseq [length [1 5 100 *print-length*]
+ level [1 5 100 *print-level*]
+ ;; Note that the `is` resides outside of the `binding`. Else test output itself can be affected.
+ :let [v (binding [*print-length* length
+ *print-level* level]
+ (with-out-str
+ (expound.alpha/printer the-explanation)))]]
+ ;; Don't make a particularly specific test assertion, since a limited print-length isn't necessarily realistic/usual:
+ (is (not (string/blank? v))))))
diff --git a/test-resources/lib_tests/expound/printer_test.cljc b/test-resources/lib_tests/expound/printer_test.cljc
new file mode 100644
index 00000000..2ea1ccb0
--- /dev/null
+++ b/test-resources/lib_tests/expound/printer_test.cljc
@@ -0,0 +1,428 @@
+(ns expound.printer-test
+ (:require [clojure.spec.alpha :as s]
+ [clojure.test :as ct :refer [is deftest use-fixtures testing]]
+ [expound.printer :as printer]
+ [clojure.string :as string]
+ [com.gfredericks.test.chuck.clojure-test :refer [checking]]
+ [expound.test-utils :as test-utils :refer [contains-nan?]]
+ [expound.spec-gen :as sg]
+ [expound.problems :as problems]))
+
+(def num-tests 5)
+
+(use-fixtures :once
+ test-utils/check-spec-assertions
+ test-utils/instrument-all)
+
+(defn example-fn [])
+(defn get-args [& args] args)
+
+(deftest pprint-fn
+ (is (= "string?"
+ (printer/pprint-fn (::s/spec (s/explain-data string? 1)))))
+ (is (= "expound.printer-test/example-fn"
+ (printer/pprint-fn example-fn)))
+ (is (= ""
+ (printer/pprint-fn #(inc (inc %)))))
+ (is (= ""
+ (printer/pprint-fn (constantly true))))
+ (is (= ""
+ (printer/pprint-fn (comp vec str))))
+ (is (= "expound.test-utils/instrument-all"
+ (printer/pprint-fn test-utils/instrument-all)))
+ (is (= "expound.test-utils/contains-nan?"
+ (printer/pprint-fn contains-nan?))))
+
+(s/def :print-spec-keys/field1 string?)
+(s/def :print-spec-keys/field2 (s/coll-of :print-spec-keys/field1))
+(s/def :print-spec-keys/field3 int?)
+(s/def :print-spec-keys/field4 string?)
+(s/def :print-spec-keys/field5 string?)
+(s/def :print-spec-keys/key-spec (s/keys
+ :req [:print-spec-keys/field1]
+ :req-un [:print-spec-keys/field2]))
+(s/def :print-spec-keys/key-spec2 (s/keys
+ :req-un [(and
+ :print-spec-keys/field1
+ (or
+ :print-spec-keys/field2
+ :print-spec-keys/field3))]))
+(s/def :print-spec-keys/key-spec3 (s/keys
+ :req-un [:print-spec-keys/field1
+ :print-spec-keys/field4
+ :print-spec-keys/field5]))
+(s/def :print-spec-keys/set-spec (s/coll-of :print-spec-keys/field1
+ :kind set?))
+(s/def :print-spec-keys/vector-spec (s/coll-of :print-spec-keys/field1
+ :kind vector?))
+(s/def :print-spec-keys/key-spec4 (s/keys
+ :req-un [:print-spec-keys/set-spec
+ :print-spec-keys/vector-spec
+ :print-spec-keys/key-spec3]))
+
+(defn copy-key [m k1 k2]
+ (assoc m k2 (get m k1)))
+
+(deftest print-spec-keys*
+ (is (=
+ [{"key" :field2, "spec" "(coll-of :print-spec-keys/field1)"}
+ {"key" :print-spec-keys/field1, "spec" "string?"}]
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ :print-spec-keys/key-spec
+ {}))))))
+ (is (nil?
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ (s/keys
+ :req [:print-spec-keys/field1]
+ :req-un [:print-spec-keys/field2])
+ {}))))))
+
+ (is (=
+ [{"key" :print-spec-keys/field1, "spec" "string?"}]
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ (s/keys
+ :req [:print-spec-keys/field1]
+ :req-un [:print-spec-keys/field2])
+ {:field2 [""]}))))))
+
+ (is (=
+ [{"key" :print-spec-keys/field1, "spec" "string?"}
+ {"key" :print-spec-keys/field2,
+ "spec" "(coll-of :print-spec-keys/field1)"}]
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ (s/keys
+ :req [:print-spec-keys/field1
+ :print-spec-keys/field2])
+ {}))))))
+ (is (=
+ [{"key" :field1, "spec" "string?"}
+ {"key" :field2, "spec" "(coll-of :print-spec-keys/field1)"}
+ {"key" :field3, "spec" "int?"}]
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ :print-spec-keys/key-spec2
+ {}))))))
+ (is (=
+ [{"key" :key-spec3,
+ "spec" #?(:clj
+ "(keys\n :req-un\n [:print-spec-keys/field1\n :print-spec-keys/field4\n :print-spec-keys/field5])"
+ :cljs
+ "(keys\n :req-un\n [:print-spec-keys/field1\n :print-spec-keys/field4 \n :print-spec-keys/field5])")}
+ {"key" :set-spec, "spec" #?(:clj
+ "(coll-of\n :print-spec-keys/field1\n :kind\n set?)"
+ :cljs
+ "(coll-of :print-spec-keys/field1 :kind set?)")}
+ {"key" :vector-spec, "spec" #?(:clj "(coll-of\n :print-spec-keys/field1\n :kind\n vector?)"
+ :cljs "(coll-of\n :print-spec-keys/field1 \n :kind \n vector?)")}]
+ (printer/print-spec-keys*
+ (map #(copy-key % :via :expound/via)
+ (::s/problems
+ (s/explain-data
+ :print-spec-keys/key-spec4
+ {})))))))
+
+(deftest print-table
+ (is (=
+ "
+| :key | :spec |
+|======+=======|
+| abc | a |
+| | b |
+|------+-------|
+| def | d |
+| | e |
+"
+ (printer/print-table [{:key "abc" :spec "a\nb"}
+ {:key "def" :spec "d\ne"}])))
+ ;; can select ordering of keys
+ (is (=
+ "
+| :b | :c |
+|====+====|
+| 2 | 3 |
+|----+----|
+| {} | () |
+"
+ (printer/print-table
+ [:b :c]
+ [{:a 1 :b 2 :c 3}
+ {:a [] :b {} :c '()}])))
+
+ ;; ordering is deterministic, not based on hashmap
+ ;; semantics
+ (is (=
+ "
+| :k | :a | :b | :c | :d | :e | :f | :g | :h | :i | :j |
+|====+====+====+====+====+====+====+====+====+====+====|
+| k | a | b | c | d | e | f | g | h | i | j |
+|----+----+----+----+----+----+----+----+----+----+----|
+| k | a | b | c | d | e | f | g | h | i | j |
+"
+ (printer/print-table
+ [:k :a :b :c :d :e :f :g :h :i :j]
+ [{:a "a" :b "b" :c "c" :d "d" :e "e" :f "f" :g "g" :h "h" :i "i" :j "j" :k "k" :l "l"}
+ {:l "l" :k "k" :j "j" :i "i" :h "h" :g "g" :f "f" :e "e" :d "d" :c "c" :b "b" :a "a"}]))))
+
+(deftest print-table-gen
+ (checking
+ "any table with have constant width"
+ num-tests
+ [col-count (s/gen pos-int?)
+ keys (s/gen (s/coll-of keyword? :min-count 1))
+ row-count (s/gen pos-int?)
+ vals (s/gen (s/coll-of
+ (s/coll-of string? :count col-count)
+ :count row-count))
+ :let [rows (mapv
+ #(zipmap keys (get vals %))
+ (range 0 row-count))
+ table (printer/print-table rows)
+ srows (rest (string/split table #"\n"))]]
+
+ (is (apply = (map count srows))))
+
+ (checking
+ "any table will contain a sub-table of all rows but the last"
+ num-tests
+ [col-count (s/gen pos-int?)
+ keys (s/gen (s/coll-of keyword? :min-count 1))
+ row-count (s/gen (s/int-in 2 10))
+ vals (s/gen (s/coll-of
+ (s/coll-of string? :count col-count)
+ :count row-count))
+ :let [rows (mapv
+ #(zipmap keys (get vals %))
+ (range 0 row-count))
+ sub-rows (butlast rows)
+ table (printer/print-table rows)
+ sub-table (printer/print-table sub-rows)
+ sub-table-last-row (last (string/split sub-table #"\n"))
+ table-last-row (last (string/split table #"\n"))]]
+ ;; If the line we delete shrinks the width of the table
+ ;; (because it was the widest value)
+ ;; then the property will not apply
+ (when (= (count sub-table-last-row) (count table-last-row))
+ (is (string/includes? table sub-table))))
+
+ #?(:clj
+ (checking
+ "for any known registered spec, table has max width"
+ num-tests
+ [spec sg/spec-gen
+ :let [rows [{:key spec
+ :spec (printer/expand-spec spec)}]
+ table (printer/print-table rows)
+ srows (rest (string/split table #"\n"))]]
+ (is (< (count (last srows)) 200)))
+ :cljs
+ ;; Noop, just to make clj-kondo happy
+ (sg/topo-sort [])))
+
+(deftest highlighted-value
+ (testing "atomic value"
+ (is (= "\"Fred\"\n^^^^^^"
+ (printer/highlighted-value
+ {}
+ {:expound/form "Fred"
+ :expound/in []}))))
+ (testing "value in vector"
+ (is (= "[... :b ...]\n ^^"
+ (printer/highlighted-value
+ {}
+ {:expound/form [:a :b :c]
+ :expound/in [1]}))))
+ (testing "long, composite values are pretty-printed"
+ (is (= (str "{:letters {:a \"aaaaaaaa\",
+ :b \"bbbbbbbb\",
+ :c \"cccccccd\",
+ :d \"dddddddd\",
+ :e \"eeeeeeee\"}}"
+ #?(:clj "\n ^^^^^^^^^^^^^^^"
+ :cljs "\n ^^^^^^^^^^^^^^^^"))
+ ;; ^- the above works in clojure - maybe not CLJS?
+ (printer/highlighted-value
+ {}
+ {:expound/form
+ {:letters
+ {:a "aaaaaaaa"
+ :b "bbbbbbbb"
+ :c "cccccccd"
+ :d "dddddddd"
+ :e "eeeeeeee"}}
+ :expound/in [:letters]}))))
+ (testing "args to function"
+ (is (= "(1 ... ...)\n ^"
+ (printer/highlighted-value
+ {}
+ {:expound/form (get-args 1 2 3)
+ :expound/in [0]}))))
+ (testing "show all values"
+ (is (= "(1 2 3)\n ^"
+ (printer/highlighted-value
+ {:show-valid-values? true}
+ {:expound/form (get-args 1 2 3)
+ :expound/in [0]}))))
+
+ (testing "special replacement chars are not used"
+ (is (= "\"$ $$ $1 $& $` $'\"\n^^^^^^^^^^^^^^^^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data keyword? "$ $$ $1 $& $` $'"))))))))
+
+ (testing "nested map-of specs"
+ (is (= "{:a {:b 1}}\n ^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/nested-map-of {:a {:b 1}})))))))
+ (is (= "{:a {\"a\" ...}}\n ^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/nested-map-of {:a {"a" :b}})))))))
+ (is (= "{1 ...}\n ^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/nested-map-of {1 {:a :b}}))))))))
+
+ (testing "nested keys specs"
+ (is (= "{:address {:city 1}}\n ^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/house {:address {:city 1}})))))))
+ (is (= "{:address {\"city\" \"Denver\"}}\n ^^^^^^^^^^^^^^^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/house {:address {"city" "Denver"}})))))))
+ (is (= "{\"address\" {:city \"Denver\"}}\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data :highlighted-value/house {"address" {:city "Denver"}})))))))))
+
+(deftest highlighted-value-on-alt
+ (is (= "[... 0]\n ^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (clojure.spec.alpha/alt :a int?
+ :b (clojure.spec.alpha/spec (clojure.spec.alpha/cat :c int?)))
+ [1 0]))))))))
+
+(deftest highlighted-value-on-coll-of
+ ;; sets
+ (is (= "#{1 3 2 :a}\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ #{1 :a 2 3})))))))
+ (is (= "#{:a}\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ #{:a})))))))
+
+ ;; lists
+ (is (= "(... :a ... ...)\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ '(1 :a 2 3))))))))
+ (is (= "(:a)\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ '(:a))))))))
+
+ ;; vectors
+ (is (= "[... :a ... ...]\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ [1 :a 2 3])))))))
+
+ (is (= "[:a]\n ^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ [:a])))))))
+
+ ;; maps
+ (is (= "[1 :a]\n^^^^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ {1 :a 2 3})))))))
+
+ (is (= "[:a 1]\n^^^^^^"
+ (printer/highlighted-value
+ {}
+ (first
+ (:expound/problems
+ (problems/annotate
+ (s/explain-data
+ (s/coll-of integer?)
+ {:a 1}))))))))
diff --git a/test-resources/lib_tests/expound/problems_test.cljc b/test-resources/lib_tests/expound/problems_test.cljc
new file mode 100644
index 00000000..a40642af
--- /dev/null
+++ b/test-resources/lib_tests/expound/problems_test.cljc
@@ -0,0 +1,30 @@
+(ns expound.problems-test
+ (:require [clojure.test :as ct :refer [is deftest use-fixtures]]
+ [clojure.spec.alpha :as s]
+ [expound.problems :as problems]
+ [expound.test-utils :as test-utils]))
+
+(use-fixtures :once
+ test-utils/check-spec-assertions
+ test-utils/instrument-all)
+
+(s/def :highlighted-value/nested-map-of (s/map-of keyword? (s/map-of keyword? keyword?)))
+
+(s/def :highlighted-value/city string?)
+(s/def :highlighted-value/address (s/keys :req-un [:highlighted-value/city]))
+(s/def :highlighted-value/house (s/keys :req-un [:highlighted-value/address]))
+
+(s/def :annotate-test/div-fn (s/fspec
+ :args (s/cat :x int? :y pos-int?)))
+(defn my-div [x y]
+ (assert (pos? (/ x y))))
+
+(deftest annotate-test
+ (is (= {:expound/in [0]
+ :val '(0 1)
+ :reason "Assert failed: (pos? (/ x y))"}
+ (-> (s/explain-data (s/coll-of :annotate-test/div-fn) [my-div])
+ problems/annotate
+ :expound/problems
+ first
+ (select-keys [:expound/in :val :reason])))))
\ No newline at end of file
diff --git a/test-resources/lib_tests/expound/spec_gen.cljc b/test-resources/lib_tests/expound/spec_gen.cljc
new file mode 100644
index 00000000..68c684ac
--- /dev/null
+++ b/test-resources/lib_tests/expound/spec_gen.cljc
@@ -0,0 +1,97 @@
+(ns expound.spec-gen
+ (:require [clojure.spec.alpha :as s]
+ [com.stuartsierra.dependency :as deps]
+ [clojure.test.check.generators :as gen]
+ [expound.alpha :as expound]))
+
+;; I want to do something like
+;; (s/def :specs.coll-of/into #{[] '() #{}})
+;; but Clojure (not Clojurescript) won't allow
+;; this. As a workaround, I'll just use vectors instead
+;; of vectors and lists.
+;; FIXME - force a specific type of into/kind one for each test
+;; (one for vectors, one for lists, etc)
+
+(s/def :specs.coll-of/into #{[] #{}})
+(s/def :specs.coll-of/kind #{vector? list? set?})
+(s/def :specs.coll-of/count pos-int?)
+(s/def :specs.coll-of/max-count pos-int?)
+(s/def :specs.coll-of/min-count pos-int?)
+(s/def :specs.coll-of/distinct boolean?)
+
+(s/def :specs/every-args
+ (s/keys :req-un
+ [:specs.coll-of/into
+ :specs.coll-of/kind
+ :specs.coll-of/count
+ :specs.coll-of/max-count
+ :specs.coll-of/min-count
+ :specs.coll-of/distinct]))
+
+(defn apply-coll-of [spec {:keys [into max-count min-count distinct]}]
+ (s/coll-of spec :into into :min-count min-count :max-count max-count :distinct distinct))
+
+(defn apply-map-of [spec1 spec2 {:keys [into max-count min-count distinct _gen-max]}]
+ (s/map-of spec1 spec2 :into into :min-count min-count :max-count max-count :distinct distinct))
+
+;; Since CLJS prints out entire source of a function when
+;; it pretty-prints a failure, the output becomes much nicer if
+;; we wrap each function in a simple spec
+(expound/def :specs/string string? "should be a string")
+(expound/def :specs/vector vector? "should be a vector")
+(s/def :specs/int int?)
+(s/def :specs/boolean boolean?)
+(expound/def :specs/keyword keyword? "should be a keyword")
+(s/def :specs/map map?)
+(s/def :specs/symbol symbol?)
+(s/def :specs/pos-int pos-int?)
+(s/def :specs/neg-int neg-int?)
+(s/def :specs/zero #(and (number? %) (zero? %)))
+(s/def :specs/keys (s/keys
+ :req-un [:specs/string]
+ :req [:specs/map]
+ :opt-un [:specs/vector]
+ :opt [:specs/int]))
+
+(def simple-spec-gen (gen/one-of
+ [(gen/elements [:specs/string
+ :specs/vector
+ :specs/int
+ :specs/boolean
+ :specs/keyword
+ :specs/map
+ :specs/symbol
+ :specs/pos-int
+ :specs/neg-int
+ :specs/zero
+ :specs/keys])
+ (gen/set gen/simple-type-printable)]))
+
+(defn spec-dependencies [spec]
+ (->> spec
+ s/form
+ (tree-seq coll? seq)
+ (filter #(and (s/get-spec %) (not= spec %)))
+ distinct))
+
+(defn topo-sort [specs]
+ (deps/topo-sort
+ (reduce
+ (fn [gr spec]
+ (reduce
+ (fn [g d]
+ ;; If this creates a circular reference, then
+ ;; just skip it.
+ (if (deps/depends? g d spec)
+ g
+ (deps/depend g spec d)))
+ gr
+ (spec-dependencies spec)))
+ (deps/graph)
+ specs)))
+
+#?(:clj
+ (def spec-gen (gen/elements (->> (s/registry)
+ (map key)
+ topo-sort
+ (filter keyword?)))))
diff --git a/test-resources/lib_tests/expound/specs_test.cljc b/test-resources/lib_tests/expound/specs_test.cljc
new file mode 100644
index 00000000..bd545ce7
--- /dev/null
+++ b/test-resources/lib_tests/expound/specs_test.cljc
@@ -0,0 +1,26 @@
+(ns expound.specs-test
+ (:require [expound.specs]
+ [clojure.spec.alpha :as s]
+ [clojure.test :as ct :refer [is deftest use-fixtures]]
+ [expound.test-utils :as test-utils]
+ [expound.alpha :as expound]))
+
+(use-fixtures :once
+ test-utils/check-spec-assertions
+ test-utils/instrument-all)
+
+(deftest provided-specs
+ (binding [s/*explain-out* (expound/custom-printer {:print-specs? false})]
+ (is (= "-- Spec failed --------------------
+
+ 1
+
+should be a keyword with no namespace
+
+-------------------------
+Detected 1 error
+"
+ (s/explain-str :expound.specs/simple-kw 1)))
+ (doseq [kw expound.specs/public-specs]
+ (is (some? (s/get-spec kw)) (str "Failed to find spec for keyword " kw))
+ (is (some? (expound/error-message kw)) (str "Failed to find error message for keyword " kw)))))
diff --git a/test-resources/lib_tests/expound/spell_spec_test.cljc b/test-resources/lib_tests/expound/spell_spec_test.cljc
new file mode 100644
index 00000000..6ffb1e4f
--- /dev/null
+++ b/test-resources/lib_tests/expound/spell_spec_test.cljc
@@ -0,0 +1,115 @@
+;; copied from
+;; https://github.com/bhauman/spell-spec/blob/master/test/spell_spec/expound_test.cljc
+;; so I don't break the extension API
+(ns expound.spell-spec-test
+ (:require [#?(:clj clojure.test :cljs cljs.test)
+ :refer [deftest is testing]]
+ [#?(:clj clojure.spec.alpha
+ :cljs cljs.spec.alpha)
+ :as s]
+ [clojure.string :as string]
+ [spell-spec.alpha :as spell :refer [warn-keys strict-keys warn-strict-keys]]
+ [expound.alpha :as exp]
+ [spell-spec.expound :as sp.ex]))
+
+;; copied from
+;; https://github.com/bhauman/spell-spec/blob/48ea2ca544f02b04a73dc42a91aa4876dcc5fc95/src/spell_spec/expound.cljc#L23-L34
+;; because test-refresh doesn't refesh libraries if I set explicit paths and
+;; if I don't restrict the paths, it tries to reload deps in the CLJS build
+
+(defmethod exp/problem-group-str :spell-spec.alpha/misspelled-key [_type spec-name val path problems opts]
+ (sp.ex/exp-formated "Misspelled map key" _type spec-name val path problems opts))
+
+(defmethod exp/expected-str :spell-spec.alpha/misspelled-key [_type _spec-name _val _path problems _opts]
+ (let [{:keys [:spell-spec.alpha/likely-misspelling-of]} (first problems)]
+ (str "should probably be" (sp.ex/format-correction-list likely-misspelling-of))))
+
+(defmethod exp/problem-group-str :spell-spec.alpha/unknown-key [_type spec-name val path problems opts]
+ (sp.ex/exp-formated "Unknown map key" _type spec-name val path problems opts))
+
+(defmethod exp/expected-str :spell-spec.alpha/unknown-key [_type _spec-name _val _path problems _opts]
+ (str "should be" (sp.ex/format-correction-list (-> problems first :pred))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn fetch-warning-output [thunk]
+ #?(:clj (binding [*err* (java.io.StringWriter.)]
+ (thunk)
+ (str *err*))
+ :cljs (with-out-str (thunk))))
+
+(deftest check-misspell-test
+ (let [spec (spell/keys :opt-un [::hello ::there])
+ data {:there 1 :helloo 1 :barabara 1}
+ result
+ (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be"))
+ (is (string/includes? result " :hello\n"))))
+
+(deftest check-misspell-with-namespace-test
+ (let [spec (spell/keys :opt [::hello ::there])
+ data {::there 1 ::helloo 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be"))
+ (is (string/includes? result ":expound.spell-spec-test/hello\n"))))
+
+(s/def ::hello integer?)
+(s/def ::there integer?)
+
+(deftest other-errors-test
+ (let [spec (spell/keys :opt-un [::hello ::there])
+ data {:there "1" :helloo 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be"))
+ (is (string/includes? result " :hello\n"))
+
+ (is (not (string/includes? result "Spec failed")))
+ (is (not (string/includes? result "should satisfy")))
+ (is (not (string/includes? result "integer?")))))
+
+(deftest warning-is-valid-test
+ (let [spec (warn-keys :opt-un [::hello ::there])
+ data {:there 1 :helloo 1 :barabara 1}]
+ (testing "expound prints warning to *err*"
+ (is (= (fetch-warning-output #(exp/expound-str spec data))
+ "SPEC WARNING: possible misspelled map key :helloo should probably be :hello in {:there 1, :helloo 1, :barabara 1}\n")))))
+
+(deftest strict-keys-test
+ (let [spec (strict-keys :opt-un [::hello ::there])
+ data {:there 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Unknown map key"))
+ (is (string/includes? result "should be one of"))
+ (is (string/includes? result " :hello, :there\n"))))
+
+(deftest warn-on-unknown-keys-test
+ (let [spec (warn-strict-keys :opt-un [::hello ::there])
+ data {:there 1 :barabara 1}]
+ (testing "expound prints warning to *err*"
+ (is (= (fetch-warning-output #(exp/expound-str spec data))
+ "SPEC WARNING: unknown map key :barabara in {:there 1, :barabara 1}\n")))))
+
+(deftest multiple-spelling-matches
+ (let [spec (spell/keys :opt-un [::hello1 ::hello2 ::hello3 ::hello4 ::there])
+ data {:there 1 :helloo 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be one of"))
+ (doseq [k [:hello1 :hello2 :hello3 :hello4]]
+ (is (string/includes? result (pr-str k)))))
+ (let [spec (spell/keys :opt-un [::hello1 ::hello2 ::hello3 ::there])
+ data {:there 1 :helloo 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be one of"))
+ (is (not (string/includes? result (pr-str :hello4))))
+ (doseq [k [:hello1 :hello2 :hello3]]
+ (is (string/includes? result (pr-str k)))))
+ (let [spec (spell/keys :opt-un [::hello ::there])
+ data {:there 1 :helloo 1 :barabara 1}
+ result (exp/expound-str spec data)]
+ (is (string/includes? result "Misspelled map key"))
+ (is (string/includes? result "should probably be: :hello\n"))))
diff --git a/test-resources/lib_tests/expound/test_runner.cljs b/test-resources/lib_tests/expound/test_runner.cljs
new file mode 100644
index 00000000..39f3e00e
--- /dev/null
+++ b/test-resources/lib_tests/expound/test_runner.cljs
@@ -0,0 +1,28 @@
+(ns expound.test-runner
+ (:require [jx.reporter.karma :refer-macros [#_run-tests #_run-all-tests]]
+ [expound.alpha-test]
+ [expound.paths-test]
+ [expound.printer-test]
+ [expound.print-length-test]
+ [expound.problems-test]
+ [expound.test-utils]
+ [expound.specs-test]
+ [expound.spell-spec-test]))
+
+(enable-console-print!)
+
+;; runs all tests in all namespaces
+;; This is what runs by default
+(defn ^:export run-all [karma]
+ (jx.reporter.karma/run-all-tests karma))
+
+;; runs all tests in all namespaces - only namespaces with names matching
+;; the regular expression will be tested
+;; You can use this by changing client.args in karma.conf.js
+#_(defn ^:export run-all-regex [karma]
+ (run-all-tests karma #".*-test$"))
+
+;; runs all tests in the given namespaces
+;; You can use this by changing client.args in karma.conf.js
+#_(defn ^:export run [karma]
+ (run-tests karma 'expound.alpha-test))
diff --git a/test-resources/lib_tests/expound/test_utils.cljc b/test-resources/lib_tests/expound/test_utils.cljc
new file mode 100644
index 00000000..e1975ae5
--- /dev/null
+++ b/test-resources/lib_tests/expound/test_utils.cljc
@@ -0,0 +1,41 @@
+(ns expound.test-utils
+ (:require [clojure.spec.alpha :as s]
+ #?(:cljs
+ [clojure.spec.test.alpha :as st]
+ ;; FIXME
+ ;; orchestra is supposed to work with cljs but
+ ;; it isn't working for me right now
+ #_[orchestra-cljs.spec.test :as st]
+ :clj [orchestra.spec.test :as st])
+ [expound.alpha :as expound]
+ [clojure.test :as ct]
+ ;; BB-TEST-PATCH: Don't have this dep and can't load it
+ #_[com.gfredericks.test.chuck.clojure-test :as chuck]
+ [expound.util :as util]
+ [clojure.test.check.generators :as gen]))
+
+;; test.chuck defines a reporter for the shrunk results, but only for the
+;; default reporter (:cljs.test/default). Since karma uses its own reporter,
+;; we need to provide an implementation of the report multimethod for
+;; the karma reporter and shrunk results
+
+; (defmethod ct/report [:jx.reporter.karma/karma ::chuck/shrunk] [m]
+; (let [f (get (methods ct/report) [::ct/default ::chuck/shrunk])]
+; (f m)))
+
+(defn check-spec-assertions [test-fn]
+ (s/check-asserts true)
+ (test-fn)
+ (s/check-asserts false))
+
+(defn instrument-all [test-fn]
+ (binding [s/*explain-out* (expound/custom-printer {:theme :figwheel-theme})]
+ (st/instrument)
+ (test-fn)
+ (st/unstrument)))
+
+(defn contains-nan? [x]
+ (boolean (some util/nan? (tree-seq coll? identity x))))
+
+(def any-printable-wo-nan (gen/such-that (complement contains-nan?)
+ gen/any-printable))
diff --git a/test-resources/lib_tests/failjure/runner.cljs b/test-resources/lib_tests/failjure/runner.cljs
new file mode 100644
index 00000000..5b334cbc
--- /dev/null
+++ b/test-resources/lib_tests/failjure/runner.cljs
@@ -0,0 +1,5 @@
+(ns failjure.runner
+ (:require [doo.runner :refer-macros [doo-tests]]
+ [failjure.test-core]))
+
+(doo-tests 'failjure.test-core)
diff --git a/test-resources/lib_tests/gaka/core_test.clj b/test-resources/lib_tests/gaka/core_test.clj
index 622c4756..7305fdbc 100644
--- a/test-resources/lib_tests/gaka/core_test.clj
+++ b/test-resources/lib_tests/gaka/core_test.clj
@@ -1,7 +1,6 @@
(ns gaka.core-test
- (:require [clojure.test :refer [deftest is are]]
- [gaka.core :refer [css compile* inline-css render-rule]]
- ))
+ (:use gaka.core
+ clojure.test))
(defmacro =? [& body]
`(are [x# y#] (= x# y#)
@@ -194,3 +193,4 @@
(is
(re-find #"^(color: red; border: 1;|border: 1; color: red;)$"
(inline-css {:color :red :border 1}))))
+
diff --git a/test-resources/lib_tests/honey/sql/helpers_test.cljc b/test-resources/lib_tests/honey/sql/helpers_test.cljc
index 33ea68c5..76fa2f88 100644
--- a/test-resources/lib_tests/honey/sql/helpers_test.cljc
+++ b/test-resources/lib_tests/honey/sql/helpers_test.cljc
@@ -2,8 +2,7 @@
(ns honey.sql.helpers-test
(:refer-clojure :exclude [filter for group-by partition-by set update])
- (:require #?(:clj [clojure.test :refer [deftest is testing]]
- :cljs [cljs.test :refer-macros [deftest is testing]])
+ (:require [clojure.test :refer [deftest is testing]]
[honey.sql :as sql]
[honey.sql.helpers :as h
:refer [add-column add-index alter-table columns create-table create-table-as create-view
diff --git a/test-resources/lib_tests/honey/sql/postgres_test.cljc b/test-resources/lib_tests/honey/sql/postgres_test.cljc
index ad173596..9649a665 100644
--- a/test-resources/lib_tests/honey/sql/postgres_test.cljc
+++ b/test-resources/lib_tests/honey/sql/postgres_test.cljc
@@ -9,8 +9,7 @@
(ns honey.sql.postgres-test
(:refer-clojure :exclude [update partition-by set])
- (:require #?(:clj [clojure.test :refer [deftest is testing]]
- :cljs [cljs.test :refer-macros [deftest is testing]])
+ (:require [clojure.test :refer [deftest is testing]]
;; pull in all the PostgreSQL helpers that the nilenso
;; library provided (as well as the regular HoneySQL ones):
[honey.sql.helpers :as sqlh :refer
@@ -320,7 +319,7 @@
(deftest values-except-select
(testing "select which values are not not present in a table"
- (is (= ["(VALUES (?), (?), (?)) EXCEPT (SELECT id FROM images)" 4 5 6]
+ (is (= ["VALUES (?), (?), (?) EXCEPT SELECT id FROM images" 4 5 6]
(sql/format
{:except
[{:values [[4] [5] [6]]}
@@ -328,7 +327,7 @@
(deftest select-except-select
(testing "select which rows are not present in another table"
- (is (= ["(SELECT ip) EXCEPT (SELECT ip FROM ip_location)"]
+ (is (= ["SELECT ip EXCEPT SELECT ip FROM ip_location"]
(sql/format
{:except
[{:select [:ip]}
@@ -336,7 +335,7 @@
(deftest values-except-all-select
(testing "select which values are not not present in a table"
- (is (= ["(VALUES (?), (?), (?)) EXCEPT ALL (SELECT id FROM images)" 4 5 6]
+ (is (= ["VALUES (?), (?), (?) EXCEPT ALL SELECT id FROM images" 4 5 6]
(sql/format
{:except-all
[{:values [[4] [5] [6]]}
@@ -344,7 +343,7 @@
(deftest select-except-all-select
(testing "select which rows are not present in another table"
- (is (= ["(SELECT ip) EXCEPT ALL (SELECT ip FROM ip_location)"]
+ (is (= ["SELECT ip EXCEPT ALL SELECT ip FROM ip_location"]
(sql/format
{:except-all
[{:select [:ip]}
diff --git a/test-resources/lib_tests/honey/sql_test.cljc b/test-resources/lib_tests/honey/sql_test.cljc
index d1043ed7..3c93cac4 100644
--- a/test-resources/lib_tests/honey/sql_test.cljc
+++ b/test-resources/lib_tests/honey/sql_test.cljc
@@ -3,8 +3,7 @@
(ns honey.sql-test
(:refer-clojure :exclude [format])
(:require [clojure.string :as str]
- #?(:clj [clojure.test :refer [deftest is testing]]
- :cljs [cljs.test :refer-macros [deftest is testing]])
+ [clojure.test :refer [deftest is testing]]
[honey.sql :as sut :refer [format]]
[honey.sql.helpers :as h])
#?(:clj (:import (clojure.lang ExceptionInfo))))
@@ -15,17 +14,39 @@
{:dialect :mysql}))))
(deftest expr-tests
+ ;; special-cased = nil:
(is (= ["id IS NULL"]
(sut/format-expr [:= :id nil])))
(is (= ["id IS NULL"]
(sut/format-expr [:is :id nil])))
+ (is (= ["id = TRUE"]
+ (sut/format-expr [:= :id true])))
+ (is (= ["id IS TRUE"]
+ (sut/format-expr [:is :id true])))
+ (is (= ["id <> TRUE"]
+ (sut/format-expr [:<> :id true])))
+ (is (= ["id IS NOT TRUE"]
+ (sut/format-expr [:is-not :id true])))
+ (is (= ["id = FALSE"]
+ (sut/format-expr [:= :id false])))
+ (is (= ["id IS FALSE"]
+ (sut/format-expr [:is :id false])))
+ (is (= ["id <> FALSE"]
+ (sut/format-expr [:<> :id false])))
+ (is (= ["id IS NOT FALSE"]
+ (sut/format-expr [:is-not :id false])))
+ ;; special-cased <> nil:
(is (= ["id IS NOT NULL"]
(sut/format-expr [:<> :id nil])))
+ ;; legacy alias:
(is (= ["id IS NOT NULL"]
(sut/format-expr [:!= :id nil])))
+ ;; legacy alias:
+ (is (= ["id IS NOT NULL"]
+ (sut/format-expr [:not= :id nil])))
(is (= ["id IS NOT NULL"]
(sut/format-expr [:is-not :id nil])))
- ;; degenerate cases:
+ ;; degenerate (special) cases:
(is (= ["NULL IS NULL"]
(sut/format-expr [:= nil nil])))
(is (= ["NULL IS NOT NULL"]
@@ -185,30 +206,30 @@
;; ORDER BY foo ASC
(is (= (format {:union [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
- ["(SELECT foo FROM bar1) UNION (SELECT foo FROM bar2)"]))
+ ["SELECT foo FROM bar1 UNION SELECT foo FROM bar2"]))
(testing "union complex values"
(is (= (format {:union [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]})
- ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) (SELECT foo FROM bar1) UNION (SELECT foo FROM bar2)"
+ ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2"
1 2 3 4 5 6]))))
(deftest union-all-test
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
- ["(SELECT foo FROM bar1) UNION ALL (SELECT foo FROM bar2)"])))
+ ["SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2"])))
(deftest intersect-test
(is (= (format {:intersect [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
- ["(SELECT foo FROM bar1) INTERSECT (SELECT foo FROM bar2)"])))
+ ["SELECT foo FROM bar1 INTERSECT SELECT foo FROM bar2"])))
(deftest except-test
(is (= (format {:except [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
- ["(SELECT foo FROM bar1) EXCEPT (SELECT foo FROM bar2)"])))
+ ["SELECT foo FROM bar1 EXCEPT SELECT foo FROM bar2"])))
(deftest inner-parts-test
(testing "The correct way to apply ORDER BY to various parts of a UNION"
@@ -222,7 +243,7 @@
:order-by [[:amount :desc]]
:limit 5}]}]
:order-by [[:amount :asc]]})
- ["(SELECT amount, id, created_on FROM transactions) UNION (SELECT amount, id, created_on FROM (SELECT amount, id, created_on FROM other_transactions ORDER BY amount DESC LIMIT ?)) ORDER BY amount ASC" 5]))))
+ ["SELECT amount, id, created_on FROM transactions UNION SELECT amount, id, created_on FROM (SELECT amount, id, created_on FROM other_transactions ORDER BY amount DESC LIMIT ?) ORDER BY amount ASC" 5]))))
(deftest compare-expressions-test
(testing "Sequences should be fns when in value/comparison spots"
@@ -254,14 +275,14 @@
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]})
- ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) (SELECT foo FROM bar1) UNION (SELECT foo FROM bar2)" 1 2 3 4 5 6])))
+ ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2" 1 2 3 4 5 6])))
(deftest union-all-with-cte
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]})
- ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) (SELECT foo FROM bar1) UNION ALL (SELECT foo FROM bar2)" 1 2 3 4 5 6])))
+ ["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2" 1 2 3 4 5 6])))
(deftest parameterizer-none
(testing "array parameter"
@@ -277,7 +298,7 @@
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]}
{:inline true})
- ["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) (SELECT foo FROM bar1) UNION (SELECT foo FROM bar2)"]))))
+ ["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2"]))))
(deftest inline-was-parameterizer-none
(testing "array parameter"
@@ -294,7 +315,7 @@
:with [[[:bar {:columns [:spam :eggs]}]
{:values (mapv #(mapv vector (repeat :inline) %)
[[1 2] [3 4] [5 6]])}]]})
- ["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) (SELECT foo FROM bar1) UNION (SELECT foo FROM bar2)"]))))
+ ["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2"]))))
(deftest similar-regex-tests
(testing "basic similar to"
@@ -379,11 +400,21 @@
(is (=
["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42]
(->
- {:update :foo
- :join [:bar [:= :bar.id :foo.bar_id]]
- :set {:a 1}
- :where [:= :bar.b 42]}
- (format {:dialect :mysql})))))
+ {:update :foo
+ :join [:bar [:= :bar.id :foo.bar_id]]
+ :set {:a 1}
+ :where [:= :bar.b 42]}
+ (format {:dialect :mysql}))))
+ ;; issue 344
+ (is (=
+ ["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `f`.`a` = ? WHERE `bar`.`b` = ?" 1 42]
+ (->
+ {:update :foo
+ :join [:bar [:= :bar.id :foo.bar_id]]
+ ;; do not drop ns in set clause for MySQL:
+ :set {:f/a 1}
+ :where [:= :bar.b 42]}
+ (format {:dialect :mysql})))))
(deftest format-arity-test
(testing "format can be called with no options"
@@ -401,7 +432,8 @@
(-> {:delete-from :foo
:where [:= :foo.id 42]}
(format :dialect :mysql :pretty true)))))
- (when (str/starts-with? #?(:bb "1.11"
+ ;; BB-TEST-PATCH: bb doesn't have clojure-version
+ (when (str/starts-with? #?(:bb "1.11"
:clj (clojure-version)
:cljs *clojurescript-version*) "1.11")
(testing "format can be called with mixed arguments"
@@ -439,7 +471,7 @@
(format {:dialect :mysql})))))
(deftest inlined-values-are-stringified-correctly
- (is (= ["SELECT 'foo', 'It''s a quote!', BAR, NULL"]
+ (is (= ["SELECT 'foo', 'It''s a quote!', bar, NULL"]
(format {:select [[[:inline "foo"]]
[[:inline "It's a quote!"]]
[[:inline :bar]]
@@ -784,3 +816,31 @@ ORDER BY id = ? DESC
:from :bar
:join [[{:select :a :from :b :where [:= :id 123]} :x] :y]
:where [:= :id 456]})))))
+
+(deftest fetch-offset-issue-338
+ (testing "default offset (with and without limit)"
+ (is (= ["SELECT foo FROM bar LIMIT ? OFFSET ?" 10 20]
+ (format {:select :foo :from :bar
+ :limit 10 :offset 20})))
+ (is (= ["SELECT foo FROM bar OFFSET ?" 20]
+ (format {:select :foo :from :bar
+ :offset 20}))))
+ (testing "default offset / fetch"
+ (is (= ["SELECT foo FROM bar OFFSET ? ROWS FETCH NEXT ? ROWS ONLY" 20 10]
+ (format {:select :foo :from :bar
+ :fetch 10 :offset 20})))
+ (is (= ["SELECT foo FROM bar OFFSET ? ROW FETCH NEXT ? ROW ONLY" 1 1]
+ (format {:select :foo :from :bar
+ :fetch 1 :offset 1})))
+ (is (= ["SELECT foo FROM bar FETCH FIRST ? ROWS ONLY" 2]
+ (format {:select :foo :from :bar
+ :fetch 2}))))
+ (testing "SQL Server offset"
+ (is (= ["SELECT [foo] FROM [bar] OFFSET ? ROWS FETCH NEXT ? ROWS ONLY" 20 10]
+ (format {:select :foo :from :bar
+ :fetch 10 :offset 20}
+ {:dialect :sqlserver})))
+ (is (= ["SELECT [foo] FROM [bar] OFFSET ? ROWS" 20]
+ (format {:select :foo :from :bar
+ :offset 20}
+ {:dialect :sqlserver})))))
diff --git a/test-resources/lib_tests/honeysql/core_test.cljc b/test-resources/lib_tests/honeysql/core_test.cljc
index 4247f1bc..d1fc2f8b 100644
--- a/test-resources/lib_tests/honeysql/core_test.cljc
+++ b/test-resources/lib_tests/honeysql/core_test.cljc
@@ -3,11 +3,12 @@
(:require [#?@(:clj [clojure.test :refer]
:cljs [cljs.test :refer-macros]) [deftest testing is]]
[honeysql.core :as sql]
+ [honeysql.format :as sql-f]
[honeysql.helpers :refer [select modifiers from join left-join
right-join full-join cross-join
where group having
order-by limit offset values columns
- insert-into with merge-where]]
+ insert-into with merge-where merge-having]]
honeysql.format-test))
;; TODO: more tests
@@ -90,7 +91,7 @@
(->
(insert-into :foo)
(columns :bar)
- (values [[(honeysql.format/value {:baz "my-val"})]])
+ (values [[(sql-f/value {:baz "my-val"})]])
sql/format)))
(is (= ["INSERT INTO foo (a, b, c) VALUES (?, ?, ?), (?, ?, ?)"
"a" "b" "c" "a" "b" "c"]
@@ -217,43 +218,113 @@
sql/format))))
(deftest merge-where-no-params-test
- (testing "merge-where called with just the map as parameter - see #228"
- (let [sqlmap (-> (select :*)
- (from :table)
- (where [:= :foo :bar]))]
- (is (= ["SELECT * FROM table WHERE foo = bar"]
- (sql/format (apply merge-where sqlmap [])))))))
+ (doseq [[k [f merge-f]] {"WHERE" [where merge-where]
+ "HAVING" [having merge-having]}]
+ (testing "merge-where called with just the map as parameter - see #228"
+ (let [sqlmap (-> (select :*)
+ (from :table)
+ (f [:= :foo :bar]))]
+ (is (= [(str "SELECT * FROM table " k " foo = bar")]
+ (sql/format (apply merge-f sqlmap []))))))))
(deftest merge-where-test
- (is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
- (-> (select :*)
- (from :table)
- (where [:= :foo :bar] [:= :quuz :xyzzy])
- sql/format)))
- (is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
- (-> (select :*)
- (from :table)
- (where [:= :foo :bar])
- (merge-where [:= :quuz :xyzzy])
- sql/format))))
+ (doseq [[k sql-keyword f merge-f] [[:where "WHERE" where merge-where]
+ [:having "HAVING" having merge-having]]]
+ (is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
+ (-> (select :*)
+ (from :table)
+ (f [:= :foo :bar] [:= :quuz :xyzzy])
+ sql/format)))
+ (is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
+ (-> (select :*)
+ (from :table)
+ (f [:= :foo :bar])
+ (merge-f [:= :quuz :xyzzy])
+ sql/format)))
+ (testing "Should work when first arg isn't a map"
+ (is (= {k [:and [:x] [:y]]}
+ (merge-f [:x] [:y]))))
+ (testing "Shouldn't use conjunction if there is only one clause in the result"
+ (is (= {k [:x]}
+ (merge-f {} [:x]))))
+ (testing "Should be able to specify the conjunction type"
+ (is (= {k [:or [:x] [:y]]}
+ (merge-f {}
+ :or
+ [:x] [:y]))))
+ (testing "Should ignore nil clauses"
+ (is (= {k [:or [:x] [:y]]}
+ (merge-f {}
+ :or
+ [:x] nil [:y]))))))
+
+(deftest merge-where-build-clause-test
+ (doseq [k [:where :having]]
+ (testing (str "Should be able to build a " k " clause with sql/build")
+ (is (= {k [:and [:a] [:x] [:y]]}
+ (sql/build
+ k [:a]
+ (keyword (str "merge-" (name k))) [:and [:x] [:y]]))))))
+
+(deftest merge-where-combine-clauses-test
+ (doseq [[k f] {:where merge-where
+ :having merge-having}]
+ (testing (str "Combine new " k " clauses into the existing clause when appropriate. (#282)")
+ (testing "No existing clause"
+ (is (= {k [:and [:x] [:y]]}
+ (f {}
+ [:x] [:y]))))
+ (testing "Existing clause is not a conjunction."
+ (is (= {k [:and [:a] [:x] [:y]]}
+ (f {k [:a]}
+ [:x] [:y]))))
+ (testing "Existing clause IS a conjunction."
+ (testing "New clause(s) are not conjunctions"
+ (is (= {k [:and [:a] [:b] [:x] [:y]]}
+ (f {k [:and [:a] [:b]]}
+ [:x] [:y]))))
+ (testing "New clauses(s) ARE conjunction(s)"
+ (is (= {k [:and [:a] [:b] [:x] [:y]]}
+ (f {k [:and [:a] [:b]]}
+ [:and [:x] [:y]])))
+ (is (= {k [:and [:a] [:b] [:x] [:y]]}
+ (f {k [:and [:a] [:b]]}
+ [:and [:x]]
+ [:y])))
+ (is (= {k [:and [:a] [:b] [:x] [:y]]}
+ (f {k [:and [:a] [:b]]}
+ [:and [:x]]
+ [:and [:y]])))))
+ (testing "if existing clause isn't the same conjunction, don't merge into it"
+ (testing "existing conjunction is `:or`"
+ (is (= {k [:and [:or [:a] [:b]] [:x] [:y]]}
+ (f {k [:or [:a] [:b]]}
+ [:x] [:y]))))
+ (testing "pass conjunction type as a param (override default of :and)"
+ (is (= {k [:or [:and [:a] [:b]] [:x] [:y]]}
+ (f {k [:and [:a] [:b]]}
+ :or
+ [:x] [:y]))))))))
(deftest where-nil-params-test
- (testing "where called with nil parameters - see #246"
- (is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
- (-> (select :*)
- (from :table)
- (where nil [:= :foo :bar] nil [:= :quuz :xyzzy] nil)
- sql/format)))
- (is (= ["SELECT * FROM table"]
- (-> (select :*)
- (from :table)
- (where)
- sql/format)))
- (is (= ["SELECT * FROM table"]
- (-> (select :*)
- (from :table)
- (where nil nil nil nil)
- sql/format)))))
+ (doseq [[_ sql-keyword f] [[:where "WHERE" where]
+ [:having "HAVING" having]]]
+ (testing (str sql-keyword " called with nil parameters - see #246")
+ (is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
+ (-> (select :*)
+ (from :table)
+ (f nil [:= :foo :bar] nil [:= :quuz :xyzzy] nil)
+ sql/format)))
+ (is (= ["SELECT * FROM table"]
+ (-> (select :*)
+ (from :table)
+ (f)
+ sql/format)))
+ (is (= ["SELECT * FROM table"]
+ (-> (select :*)
+ (from :table)
+ (f nil nil nil nil)
+ sql/format))))))
(deftest cross-join-test
(is (= ["SELECT * FROM foo CROSS JOIN bar"]
diff --git a/test-resources/lib_tests/honeysql/format_test.cljc b/test-resources/lib_tests/honeysql/format_test.cljc
index d7fa8c7b..7ddea793 100644
--- a/test-resources/lib_tests/honeysql/format_test.cljc
+++ b/test-resources/lib_tests/honeysql/format_test.cljc
@@ -320,3 +320,17 @@
(format {:select [:*]
:from [[:foo :f]]
:cross-join [[:bar :b]]}))))
+
+(deftest issue-299-test
+ (let [name "test field"
+ ;; this was being rendered inline into the SQL
+ ;; creating an injection vulnerability (v1 only)
+ ;; the context for seq->sql here seems to be the
+ ;; 'regular' one so it tries to treat this as an
+ ;; alias: 'value alias' -- the fix was to make it
+ ;; a function context so it becomes (TRUE, ?):
+ enabled [true, "); SELECT case when (SELECT current_setting('is_superuser'))='off' then pg_sleep(0.2) end; -- "]]
+ (is (= ["INSERT INTO table (name, enabled) VALUES (?, (TRUE, ?))" name (second enabled)]
+ (format {:insert-into :table
+ :values [{:name name
+ :enabled enabled}]})))))
\ No newline at end of file
diff --git a/test-resources/lib_tests/io/aviso/binary_test.clj b/test-resources/lib_tests/io/aviso/binary_test.clj
new file mode 100644
index 00000000..bdaf0bc8
--- /dev/null
+++ b/test-resources/lib_tests/io/aviso/binary_test.clj
@@ -0,0 +1,52 @@
+(ns io.aviso.binary-test
+ "Tests for the io.aviso.binary namespace."
+ (:use io.aviso.binary
+ clojure.test)
+ (:import (java.nio ByteBuffer)))
+
+(defn ^:private format-string-as-byte-array [str]
+ (format-binary (.getBytes str)))
+
+(deftest format-byte-array-test
+
+ (are [input expected]
+ (= expected (format-string-as-byte-array input))
+
+ "Hello" "0000: 48 65 6C 6C 6F\n"
+
+ "This is a longer text that spans to a second line."
+ "0000: 54 68 69 73 20 69 73 20 61 20 6C 6F 6E 67 65 72 20 74 65 78 74 20 74 68 61 74 20 73 70 61 6E 73\n0020: 20 74 6F 20 61 20 73 65 63 6F 6E 64 20 6C 69 6E 65 2E\n"))
+
+(deftest format-string-as-byte-data
+ (are [input expected]
+ (= expected (format-binary input))
+ "" ""
+
+ "Hello" "0000: 48 65 6C 6C 6F\n"
+
+ "This is a longer text that spans to a second line."
+
+ "0000: 54 68 69 73 20 69 73 20 61 20 6C 6F 6E 67 65 72 20 74 65 78 74 20 74 68 61 74 20 73 70 61 6E 73\n0020: 20 74 6F 20 61 20 73 65 63 6F 6E 64 20 6C 69 6E 65 2E\n"))
+
+(deftest nil-is-an-empty-data
+ (is (= (format-binary nil) "")))
+
+(deftest byte-buffer
+ (let [bb (ByteBuffer/wrap (.getBytes "Duty Now For The Future" "UTF-8"))]
+ (is (= "0000: 44 75 74 79 20 4E 6F 77 20 46 6F 72 20 54 68 65 20 46 75 74 75 72 65\n"
+ (format-binary bb)))
+
+ (is (= "0000: 44 75 74 79\n"
+ (-> bb
+ (.position 5)
+ (.limit 9)
+ format-binary)))
+
+ (is (= "0000: 46 6F 72\n"
+ (-> bb
+ (.position 9)
+ (.limit 12)
+ .slice
+ format-binary)))
+
+ ))
diff --git a/test-resources/lib_tests/io/aviso/exception_test.clj b/test-resources/lib_tests/io/aviso/exception_test.clj
new file mode 100644
index 00000000..f8b1cbe0
--- /dev/null
+++ b/test-resources/lib_tests/io/aviso/exception_test.clj
@@ -0,0 +1,583 @@
+(ns io.aviso.exception-test
+ (:use clojure.test)
+ (:require [clojure.string :as str]
+ [io.aviso.exception :as e :refer [*fonts* parse-exception format-exception]]
+ [clojure.pprint :refer [pprint]]
+ [com.stuartsierra.component :as component]
+ [com.walmartlabs.test-reporting :refer [reporting]]
+ io.aviso.component))
+
+(deftest write-exceptions
+ (testing "exception properties printing"
+ (testing "Does not fail with ex-info's map keys not implementing clojure.lang.Named"
+ (is (re-find #"string-key.*string-val"
+ (format-exception (ex-info "Error" {"string-key" "string-val"})))))))
+
+(defn parse [& text-lines]
+ (let [text (str/join \newline text-lines)]
+ (binding [*fonts* nil]
+ (parse-exception text nil))))
+
+(deftest parse-exceptions
+ (is (= [{:class-name "java.lang.IllegalArgumentException"
+ :message "No value supplied for key: {:host \"example.com\"}"
+ :stack-trace
+ [{:simple-class "PersistentHashMap"
+ :package "clojure.lang"
+ :omitted true
+ :is-clojure? false
+ :method "create"
+ :name ""
+ :formatted-name "..."
+ :file ""
+ :line nil
+ :class "clojure.lang.PersistentHashMap"
+ :names []}
+ {:simple-class "client$tcp_client"
+ :package "riemann"
+ :is-clojure? true
+ :method "doInvoke"
+ :name "riemann.client/tcp-client"
+ :formatted-name "riemann.client/tcp-client"
+ :file "client.clj"
+ :line 90
+ :class "riemann.client$tcp_client"
+ :names '("riemann.client" "tcp-client")}
+ {:simple-class "RestFn"
+ :package "clojure.lang"
+ :omitted true
+ :is-clojure? false
+ :method "invoke"
+ :name ""
+ :formatted-name "..."
+ :file ""
+ :line nil
+ :class "clojure.lang.RestFn"
+ :names []}
+ {:simple-class "error_monitor$make_connection"
+ :package "com.example"
+ :is-clojure? true
+ :method "invoke"
+ :name "com.example.error-monitor/make-connection"
+ :formatted-name "com.example.error-monitor/make-connection"
+ :file "error_monitor.clj"
+ :line 22
+ :class "com.example.error_monitor$make_connection"
+ :names '("com.example.error-monitor" "make-connection")}
+ {:simple-class "error_monitor$make_client"
+ :package "com.example"
+ :is-clojure? true
+ :method "invoke"
+ :name "com.example.error-monitor/make-client"
+ :formatted-name "com.example.error-monitor/make-client"
+ :file "error_monitor.clj"
+ :line 26
+ :class "com.example.error_monitor$make_client"
+ :names '("com.example.error-monitor" "make-client")}
+ {:simple-class "core$map$fn__4553"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/map/fn"
+ :formatted-name "clojure.core/map/fn"
+ :file "core.clj"
+ :line 2624
+ :class "clojure.core$map$fn__4553"
+ :names '("clojure.core" "map" "fn")}
+ {:simple-class "LazySeq"
+ :package "clojure.lang"
+ :omitted true
+ :is-clojure? false
+ :method "sval"
+ :name ""
+ :formatted-name "..."
+ :file ""
+ :line nil
+ :class "clojure.lang.LazySeq"
+ :names []}
+ {:simple-class "core$seq__4128"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/seq"
+ :formatted-name "clojure.core/seq"
+ :file "core.clj"
+ :line 137
+ :class "clojure.core$seq__4128"
+ :names '("clojure.core" "seq")}
+ {:simple-class "core$sort"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/sort"
+ :formatted-name "clojure.core/sort"
+ :file "core.clj"
+ :line 2981
+ :class "clojure.core$sort"
+ :names '("clojure.core" "sort")}
+ {:simple-class "core$sort_by"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/sort-by"
+ :formatted-name "clojure.core/sort-by"
+ :file "core.clj"
+ :line 2998
+ :class "clojure.core$sort_by"
+ :names '("clojure.core" "sort-by")}
+ {:simple-class "core$sort_by"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/sort-by"
+ :formatted-name "clojure.core/sort-by"
+ :file "core.clj"
+ :line 2996
+ :class "clojure.core$sort_by"
+ :names '("clojure.core" "sort-by")}
+ {:simple-class "error_monitor$make_clients"
+ :package "com.example"
+ :is-clojure? true
+ :method "invoke"
+ :name "com.example.error-monitor/make-clients"
+ :formatted-name "com.example.error-monitor/make-clients"
+ :file "error_monitor.clj"
+ :line 31
+ :class "com.example.error_monitor$make_clients"
+ :names '("com.example.error-monitor" "make-clients")}
+ {:simple-class "error_monitor$report_and_reset"
+ :package "com.example"
+ :is-clojure? true
+ :method "invoke"
+ :name "com.example.error-monitor/report-and-reset"
+ :formatted-name "com.example.error-monitor/report-and-reset"
+ :file "error_monitor.clj"
+ :line 185
+ :class "com.example.error_monitor$report_and_reset"
+ :names '("com.example.error-monitor" "report-and-reset")}
+ {:simple-class "main$_main$fn__705"
+ :package "com.example.error_monitor"
+ :is-clojure? true
+ :method "invoke"
+ :name "com.example.error-monitor.main/-main/fn"
+ :formatted-name "com.example.error-monitor.main/-main/fn"
+ :file "main.clj"
+ :line 19
+ :class "com.example.error_monitor.main$_main$fn__705"
+ :names '("com.example.error-monitor.main" "-main" "fn")}
+ {:simple-class "main$_main"
+ :package "com.example.error_monitor"
+ :is-clojure? true
+ :method "doInvoke"
+ :name "com.example.error-monitor.main/-main"
+ :formatted-name "com.example.error-monitor.main/-main"
+ :file "main.clj"
+ :line 16
+ :class "com.example.error_monitor.main$_main"
+ :names '("com.example.error-monitor.main" "-main")}
+ {:simple-class "RestFn"
+ :package "clojure.lang"
+ :omitted true
+ :is-clojure? false
+ :method "applyTo"
+ :name ""
+ :formatted-name "..."
+ :file ""
+ :line nil
+ :class "clojure.lang.RestFn"
+ :names []}
+ {:class "com.example.error_monitor.main"
+ :file ""
+ :formatted-name "com.example.error_monitor.main.main"
+ :is-clojure? false
+ :line nil
+ :method "main"
+ :name ""
+ :names []
+ :package "com.example.error_monitor"
+ :simple-class "main"}]}]
+ (parse "java.lang.IllegalArgumentException: No value supplied for key: {:host \"example.com\"}"
+ "\tat clojure.lang.PersistentHashMap.create(PersistentHashMap.java:77)"
+ "\tat riemann.client$tcp_client.doInvoke(client.clj:90)"
+ "\tat clojure.lang.RestFn.invoke(RestFn.java:408)"
+ "\tat com.example.error_monitor$make_connection.invoke(error_monitor.clj:22)"
+ "\tat com.example.error_monitor$make_client.invoke(error_monitor.clj:26)"
+ "\tat clojure.core$map$fn__4553.invoke(core.clj:2624)"
+ "\tat clojure.lang.LazySeq.sval(LazySeq.java:40)"
+ "\tat clojure.lang.LazySeq.seq(LazySeq.java:49)"
+ "\tat clojure.lang.RT.seq(RT.java:507)"
+ "\tat clojure.core$seq__4128.invoke(core.clj:137)"
+ "\tat clojure.core$sort.invoke(core.clj:2981)"
+ "\tat clojure.core$sort_by.invoke(core.clj:2998)"
+ "\tat clojure.core$sort_by.invoke(core.clj:2996)"
+ "\tat com.example.error_monitor$make_clients.invoke(error_monitor.clj:31)"
+ "\tat com.example.error_monitor$report_and_reset.invoke(error_monitor.clj:185)"
+ "\tat com.example.error_monitor.main$_main$fn__705.invoke(main.clj:19)"
+ "\tat com.example.error_monitor.main$_main.doInvoke(main.clj:16)"
+ "\tat clojure.lang.RestFn.applyTo(RestFn.java:137)"
+ "\tat com.example.error_monitor.main.main(Unknown Source)"))
+
+ (is (= [{:class-name "java.lang.RuntimeException", :message "Request handling exception"}
+ {:class-name "java.lang.RuntimeException", :message "Failure updating row"}
+ {:class-name "java.sql.SQLException"
+ :message "Database failure\nSELECT FOO, BAR, BAZ\nFROM GNIP\nfailed with ABC123"
+ :stack-trace [{:simple-class "user$jdbc_update"
+ :package nil
+ :is-clojure? true
+ :method "invoke"
+ :name "user/jdbc-update"
+ :formatted-name "user/jdbc-update"
+ :file "user.clj"
+ :line 7
+ :class "user$jdbc_update"
+ :names '("user" "jdbc-update")}
+ {:simple-class "user$make_jdbc_update_worker$reify__497"
+ :package nil
+ :is-clojure? true
+ :method "do_work"
+ :name "user/make-jdbc-update-worker/reify/do-work"
+ :formatted-name "user/make-jdbc-update-worker/reify/do-work"
+ :file "user.clj"
+ :line 18
+ :class "user$make_jdbc_update_worker$reify__497"
+ :names '("user" "make-jdbc-update-worker" "reify" "do-work")}
+ {:simple-class "user$update_row"
+ :package nil
+ :is-clojure? true
+ :method "invoke"
+ :name "user/update-row"
+ :formatted-name "user/update-row"
+ :file "user.clj"
+ :line 23
+ :class "user$update_row"
+ :names '("user" "update-row")}
+ {:simple-class "user$make_exception"
+ :package nil
+ :is-clojure? true
+ :method "invoke"
+ :name "user/make-exception"
+ :formatted-name "user/make-exception"
+ :file "user.clj"
+ :line 31
+ :class "user$make_exception"
+ :names '("user" "make-exception")}
+ {:simple-class "user$eval2018"
+ :package nil
+ :is-clojure? true
+ :method "invoke"
+ :name "user/eval2018"
+ :formatted-name "user/eval2018"
+ :file "REPL Input"
+ :line nil
+ :class "user$eval2018"
+ :names '("user" "eval2018")}
+ {:simple-class "Compiler"
+ :package "clojure.lang"
+ :omitted true
+ :is-clojure? false
+ :method "eval"
+ :name ""
+ :formatted-name "..."
+ :file ""
+ :line nil
+ :class "clojure.lang.Compiler"
+ :names []}
+ {:simple-class "core$eval"
+ :package "clojure"
+ :is-clojure? true
+ :method "invoke"
+ :name "clojure.core/eval"
+ :formatted-name "clojure.core/eval"
+ :file "core.clj"
+ :line 2852
+ :class "clojure.core$eval"
+ :names '("clojure.core" "eval")}]}]
+ (parse "java.lang.RuntimeException: Request handling exception"
+ "\tat user$make_exception.invoke(user.clj:31)"
+ "\tat user$eval2018.invoke(form-init1482095333541107022.clj:1)"
+ "\tat clojure.lang.Compiler.eval(Compiler.java:6619)"
+ "\tat clojure.lang.Compiler.eval(Compiler.java:6582)"
+ "\tat clojure.core$eval.invoke(core.clj:2852)"
+ "\tat clojure.main$repl$read_eval_print__6602$fn__6605.invoke(main.clj:259)"
+ "\tat clojure.main$repl$read_eval_print__6602.invoke(main.clj:259)"
+ "\tat clojure.main$repl$fn__6611$fn__6612.invoke(main.clj:277)"
+ "\tat clojure.main$repl$fn__6611.invoke(main.clj:277)"
+ "\tat clojure.main$repl.doInvoke(main.clj:275)"
+ "\tat clojure.lang.RestFn.invoke(RestFn.java:1523)"
+ "\tat clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__1419.invoke(interruptible_eval.clj:72)"
+ "\tat clojure.lang.AFn.applyToHelper(AFn.java:159)"
+ "\tat clojure.lang.AFn.applyTo(AFn.java:151)"
+ "\tat clojure.core$apply.invoke(core.clj:617)"
+ "\tat clojure.core$with_bindings_STAR_.doInvoke(core.clj:1788)"
+ "\tat clojure.lang.RestFn.invoke(RestFn.java:425)"
+ "\tat clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj:56)"
+ "\tat clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__1461$fn__1464.invoke(interruptible_eval.clj:191)"
+ "\tat clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__1456.invoke(interruptible_eval.clj:159)"
+ "\tat clojure.lang.AFn.run(AFn.java:24)"
+ "\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)"
+ "\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)"
+ "\tat java.lang.Thread.run(Thread.java:745)"
+ "Caused by: java.lang.RuntimeException: Failure updating row"
+ "\tat user$update_row.invoke(user.clj:23)"
+ "\t... 24 more"
+ "Caused by: java.sql.SQLException: Database failure"
+ "SELECT FOO, BAR, BAZ"
+ "FROM GNIP"
+ "failed with ABC123"
+ "\tat user$jdbc_update.invoke(user.clj:7)"
+ "\tat user$make_jdbc_update_worker$reify__497.do_work(user.clj:18)"
+ "\t... 25 more"))
+
+ (is (= [{:class-name "com.datastax.driver.core.TransportException", :message "/17.76.3.14:9042 Cannot connect"}
+ {:class-name "java.net.ConnectException",
+ :message "Connection refused: /17.76.3.14:9042",
+ :stack-trace [{:simple-class "SocketChannelImpl"
+ :package "sun.nio.ch"
+ :is-clojure? false
+ :method "checkConnect"
+ :name ""
+ :formatted-name "sun.nio.ch.SocketChannelImpl.checkConnect"
+ :file ""
+ :line nil
+ :class "sun.nio.ch.SocketChannelImpl"
+ :names []}
+ {:simple-class "SocketChannelImpl"
+ :package "sun.nio.ch"
+ :is-clojure? false
+ :method "finishConnect"
+ :name ""
+ :formatted-name "sun.nio.ch.SocketChannelImpl.finishConnect"
+ :file "SocketChannelImpl.java"
+ :line 717
+ :class "sun.nio.ch.SocketChannelImpl"
+ :names []}
+ {:simple-class "NioClientBoss"
+ :package "com.datastax.shaded.netty.channel.socket.nio"
+ :is-clojure? false
+ :method "connect"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.connect"
+ :file "NioClientBoss.java"
+ :line 150
+ :class "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss"
+ :names []}
+ {:simple-class "NioClientBoss"
+ :package "com.datastax.shaded.netty.channel.socket.nio"
+ :is-clojure? false
+ :method "processSelectedKeys"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.processSelectedKeys"
+ :file "NioClientBoss.java"
+ :line 105
+ :class "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss"
+ :names []}
+ {:simple-class "NioClientBoss"
+ :package "com.datastax.shaded.netty.channel.socket.nio"
+ :is-clojure? false
+ :method "process"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.process"
+ :file "NioClientBoss.java"
+ :line 79
+ :class "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss"
+ :names []}
+ {:simple-class "AbstractNioSelector"
+ :package "com.datastax.shaded.netty.channel.socket.nio"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.channel.socket.nio.AbstractNioSelector.run"
+ :file "AbstractNioSelector.java"
+ :line 318
+ :class "com.datastax.shaded.netty.channel.socket.nio.AbstractNioSelector"
+ :names []}
+ {:simple-class "NioClientBoss"
+ :package "com.datastax.shaded.netty.channel.socket.nio"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.run"
+ :file "NioClientBoss.java"
+ :line 42
+ :class "com.datastax.shaded.netty.channel.socket.nio.NioClientBoss"
+ :names []}
+ {:simple-class "ThreadRenamingRunnable"
+ :package "com.datastax.shaded.netty.util"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.util.ThreadRenamingRunnable.run"
+ :file "ThreadRenamingRunnable.java"
+ :line 108
+ :class "com.datastax.shaded.netty.util.ThreadRenamingRunnable"
+ :names []}
+ {:simple-class "DeadLockProofWorker$1"
+ :package "com.datastax.shaded.netty.util.internal"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "com.datastax.shaded.netty.util.internal.DeadLockProofWorker$1.run"
+ :file "DeadLockProofWorker.java"
+ :line 42
+ :class "com.datastax.shaded.netty.util.internal.DeadLockProofWorker$1"
+ :names []}
+ {:simple-class "Connection"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method ""
+ :name ""
+ :formatted-name "com.datastax.driver.core.Connection."
+ :file "Connection.java"
+ :line 104
+ :class "com.datastax.driver.core.Connection"
+ :names []}
+ {:simple-class "PooledConnection"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method ""
+ :name ""
+ :formatted-name "com.datastax.driver.core.PooledConnection."
+ :file "PooledConnection.java"
+ :line 32
+ :class "com.datastax.driver.core.PooledConnection"
+ :names []}
+ {:simple-class "Connection$Factory"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method "open"
+ :name ""
+ :formatted-name "com.datastax.driver.core.Connection$Factory.open"
+ :file "Connection.java"
+ :line 557
+ :class "com.datastax.driver.core.Connection$Factory"
+ :names []}
+ {:simple-class "DynamicConnectionPool"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method ""
+ :name ""
+ :formatted-name "com.datastax.driver.core.DynamicConnectionPool."
+ :file "DynamicConnectionPool.java"
+ :line 74
+ :class "com.datastax.driver.core.DynamicConnectionPool"
+ :names []}
+ {:simple-class "HostConnectionPool"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method "newInstance"
+ :name ""
+ :formatted-name "com.datastax.driver.core.HostConnectionPool.newInstance"
+ :file "HostConnectionPool.java"
+ :line 33
+ :class "com.datastax.driver.core.HostConnectionPool"
+ :names []}
+ {:simple-class "SessionManager$2"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method "call"
+ :name ""
+ :formatted-name "com.datastax.driver.core.SessionManager$2.call"
+ :file "SessionManager.java"
+ :line 231
+ :class "com.datastax.driver.core.SessionManager$2"
+ :names []}
+ {:simple-class "SessionManager$2"
+ :package "com.datastax.driver.core"
+ :is-clojure? false
+ :method "call"
+ :name ""
+ :formatted-name "com.datastax.driver.core.SessionManager$2.call"
+ :file "SessionManager.java"
+ :line 224
+ :class "com.datastax.driver.core.SessionManager$2"
+ :names []}
+ {:simple-class "FutureTask"
+ :package "java.util.concurrent"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "java.util.concurrent.FutureTask.run"
+ :file "FutureTask.java"
+ :line 266
+ :class "java.util.concurrent.FutureTask"
+ :names []}
+ {:simple-class "ThreadPoolExecutor"
+ :package "java.util.concurrent"
+ :is-clojure? false
+ :method "runWorker"
+ :name ""
+ :formatted-name "java.util.concurrent.ThreadPoolExecutor.runWorker"
+ :file "ThreadPoolExecutor.java"
+ :line 1142
+ :class "java.util.concurrent.ThreadPoolExecutor"
+ :names []}
+ {:simple-class "ThreadPoolExecutor$Worker"
+ :package "java.util.concurrent"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "java.util.concurrent.ThreadPoolExecutor$Worker.run"
+ :file "ThreadPoolExecutor.java"
+ :line 617
+ :class "java.util.concurrent.ThreadPoolExecutor$Worker"
+ :names []}
+ {:simple-class "Thread"
+ :package "java.lang"
+ :is-clojure? false
+ :method "run"
+ :name ""
+ :formatted-name "java.lang.Thread.run"
+ :file "Thread.java"
+ :line 745
+ :class "java.lang.Thread"
+ :names []}]}]
+
+ (parse "com.datastax.driver.core.TransportException: /17.76.3.14:9042 Cannot connect"
+ "\tat com.datastax.driver.core.Connection.(Connection.java:104) ~store-service.jar:na"
+ "\tat com.datastax.driver.core.PooledConnection.(PooledConnection.java:32) ~store-service.jar:na"
+ "\tat com.datastax.driver.core.Connection$Factory.open(Connection.java:557) ~store-service.jar:na"
+ "\tat com.datastax.driver.core.DynamicConnectionPool.(DynamicConnectionPool.java:74) ~store-service.jar:na"
+ "\tat com.datastax.driver.core.HostConnectionPool.newInstance(HostConnectionPool.java:33) ~store-service.jar:na"
+ "\tat com.datastax.driver.core.SessionManager$2.call(SessionManager.java:231) store-service.jar:na"
+ "\tat com.datastax.driver.core.SessionManager$2.call(SessionManager.java:224) store-service.jar:na"
+ "\tat java.util.concurrent.FutureTask.run(FutureTask.java:266) na:1.8.0_66"
+ "\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) na:1.8.0_66"
+ "\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) na:1.8.0_66"
+ "\tat java.lang.Thread.run(Thread.java:745) na:1.8.0_66"
+ "Caused by: java.net.ConnectException: Connection refused: /17.76.3.14:9042"
+ "\tat sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~na:1.8.0_66"
+ "\tat sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717) ~na:1.8.0_66"
+ "\tat com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.connect(NioClientBoss.java:150) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.processSelectedKeys(NioClientBoss.java:105) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.process(NioClientBoss.java:79) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:318) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.channel.socket.nio.NioClientBoss.run(NioClientBoss.java:42) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108) ~store-service.jar:na"
+ "\tat com.datastax.shaded.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42) ~store-service.jar:na"
+ "\t... 3 common frames omitted"))))))
+
+(defrecord MyComponent []
+
+ component/Lifecycle
+ (start [this] this)
+ (stop [this] this))
+
+
+(deftest component-print-behavior
+ (binding [e/*fonts* nil]
+ (let [my-component (map->MyComponent {})
+ system (component/system-map
+ :my-component my-component)
+ sys-exception (format-exception (ex-info "System Exception" {:system system}))
+ comp-exception (format-exception (ex-info "Component Exception" {:component my-component}))]
+
+ (reporting {sys-exception (str/split-lines sys-exception)}
+ (is (re-find #"system: #" sys-exception)))
+
+ (reporting {comp-exception (str/split-lines comp-exception)}
+ (is (re-find #"component: #" comp-exception))))))
+
+(deftest write-exceptions-with-nil-data
+ (testing "Does not fail with a nil ex-info map key"
+ (is (re-find #"nil.*nil"
+ (format-exception (ex-info "Error" {nil nil}))))))
diff --git a/test-resources/lib_tests/lambdaisland/regal/malli_test.cljc b/test-resources/lib_tests/lambdaisland/regal/malli_test.cljc
new file mode 100644
index 00000000..5b3072e3
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal/malli_test.cljc
@@ -0,0 +1,19 @@
+(ns lambdaisland.regal.malli-test
+ (:require [clojure.test :refer [deftest is ]]
+ [malli.core :as m]
+ [malli.error :as me]
+ [lambdaisland.regal.malli :as regal-malli]))
+
+(def malli-opts {:registry {:regal regal-malli/regal-schema}})
+
+(def form [:+ "y"])
+
+(def schema (m/schema [:regal form] malli-opts))
+
+(deftest regal-malli-test
+ (is (= [:regal [:+ "y"]] (m/form schema)))
+ (is (= :regal (m/type schema)))
+ (is (= true (m/validate schema "yyy")))
+ (is (= ["Pattern does not match"] (me/humanize (m/explain schema "xxx")))))
+
+
diff --git a/test-resources/lib_tests/lambdaisland/regal/parse_test.cljc b/test-resources/lib_tests/lambdaisland/regal/parse_test.cljc
new file mode 100644
index 00000000..2a169516
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal/parse_test.cljc
@@ -0,0 +1,29 @@
+(ns lambdaisland.regal.parse-test
+ (:require [clojure.test :refer [deftest testing is are]]
+ [lambdaisland.regal :as regal]
+ [lambdaisland.regal.parse :as parse]))
+
+(deftest parse-whitespace-test
+ (is (= [:class " " :tab :newline :vertical-tab :form-feed :return]
+ (regal/with-flavor :java
+ (parse/parse-pattern "\\s"))))
+
+ (is (= :whitespace
+ (regal/with-flavor :ecma
+ (parse/parse-pattern "\\s"))))
+
+ (is (= [:not " " :tab :newline :vertical-tab :form-feed :return]
+ (regal/with-flavor :java
+ (parse/parse-pattern "\\S"))))
+
+ (is (= :non-whitespace
+ (regal/with-flavor :ecma
+ (parse/parse-pattern "\\S")))))
+
+(deftest ^{:kaocha/pending
+ "Needs a special case in the regex generation code"}
+ whitespace-round-trip
+ (is (= "\\s"
+ (regal/with-flavor :java
+ (regal/pattern
+ (parse/parse-pattern "\\s"))))))
diff --git a/test-resources/lib_tests/lambdaisland/regal/re2_test.clj b/test-resources/lib_tests/lambdaisland/regal/re2_test.clj
new file mode 100644
index 00000000..6f26c84c
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal/re2_test.clj
@@ -0,0 +1,45 @@
+(ns lambdaisland.regal.re2-test
+ (:require [clojure.spec.alpha :as s]
+ [clojure.test :refer [is]]
+ [clojure.test.check.clojure-test :refer [defspec]]
+ [clojure.test.check.generators :as gen]
+ [com.gfredericks.test.chuck.properties :as prop']
+ [lambdaisland.regal :as regal]
+ [lambdaisland.regal.generator :as regal-gen]
+ [lambdaisland.regal.test-util :refer [re2-find re2-compile]]
+ [lambdaisland.regal.spec-alpha :as regal-spec]))
+
+(defn gen-carefully [fgen else-gen]
+ (try
+ (let [gen (fgen)]
+ (gen/->Generator
+ (fn [rnd size]
+ (try
+ (gen/call-gen gen rnd size)
+ (catch Exception _
+ (gen/call-gen else-gen rnd size))))))
+ (catch Exception _
+ else-gen)))
+
+(defn can-generate? [regal]
+ (try
+ (gen/sample (regal-gen/gen regal))
+ true
+ (catch Exception _
+ false)))
+
+(defspec re2-matches-like-java 10
+ (with-redefs [regal-spec/token-gen #(s/gen (disj regal-spec/known-tokens :line-break :start :end))]
+ (prop'/for-all [regal (s/gen ::regal/form)
+ :when (can-generate? regal)
+ s (gen-carefully #(regal-gen/gen regal)
+ gen/string)
+ :let [java-result
+ (try (re-find (regal/regex regal) s)
+ (catch Exception _
+ :fail))]
+ :when (not= :fail java-result)]
+ (is (= java-result
+ (re2-find (regal/with-flavor :re2
+ (re2-compile (regal/pattern regal)))
+ s))))))
diff --git a/test-resources/lib_tests/lambdaisland/regal/spec_gen_test.clj b/test-resources/lib_tests/lambdaisland/regal/spec_gen_test.clj
new file mode 100644
index 00000000..20b14f16
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal/spec_gen_test.clj
@@ -0,0 +1,39 @@
+(ns lambdaisland.regal.spec-gen-test
+ (:require [clojure.spec.alpha :as s]
+ [clojure.spec.gen.alpha :as spec-gen]
+ [clojure.test :refer [deftest is are testing run-tests]]
+ [clojure.test.check.clojure-test :refer [defspec]]
+ [clojure.test.check.generators :as gen]
+ [clojure.test.check.properties :as prop]
+ [lambdaisland.regal :as regal]
+ [lambdaisland.regal.parse :as parse]
+ [lambdaisland.regal.spec-alpha]))
+
+(def form-gen (s/gen ::regal/form))
+(def canonical-form-gen (gen/fmap regal/normalize (s/gen ::regal/form)))
+
+(defspec generated-forms-can-be-converted 100
+ (prop/for-all [regal form-gen]
+ (try
+ (regal/regex regal)
+ (catch Exception _
+ false))))
+
+(defn- round-trip? [form]
+ (try
+ (= form (parse/parse (regal/regex form)))
+ (catch Exception _ false)))
+
+(defspec round-trip-property 100
+ (prop/for-all* [canonical-form-gen] round-trip?))
+
+(deftest round-trip-test
+ (is (round-trip? [:cat " " [:class "&& "]]))
+ (is (round-trip? [:class " " [" " "["]]))
+ (is (round-trip? [:ctrl "A"]))
+ (is (round-trip? [:class " - "]))
+ (is (round-trip? [:alt " " [:capture " " :escape]]))
+ (is (round-trip? :whitespace))
+ (is (round-trip? [:? [:? "x"]]))
+ (is (round-trip? [:cat " " [:class " " :non-whitespace]]))
+ (is (round-trip? [:cat "-" [:repeat [:repeat "x" 0] 0]])))
diff --git a/test-resources/lib_tests/lambdaisland/regal/test_util.cljc b/test-resources/lib_tests/lambdaisland/regal/test_util.cljc
new file mode 100644
index 00000000..04c1d659
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal/test_util.cljc
@@ -0,0 +1,146 @@
+(ns lambdaisland.regal.test-util
+ (:require [lambdaisland.regal :as regal])
+ #?(:cljs (:require-macros [lambdaisland.regal.test-util :refer [inline-resource]])
+ :clj (:require [clojure.java.io :as io]
+ [clojure.test.check.generators :as gen]
+ [lambdaisland.regal.generator :as regal-gen]
+ ;; BB-TEST-PATCH: Don't have this dependency
+ #_[com.gfredericks.test.chuck.regexes.charsets :as charsets])))
+
+#?(:clj
+ (defmacro inline-resource [resource-path]
+ (read-string (slurp (io/resource resource-path)))))
+
+(defn read-test-cases []
+ #? (:clj (read-string (slurp (io/resource "lambdaisland/regal/test_cases.edn")))
+ :cljs (inline-resource "lambdaisland/regal/test_cases.edn")))
+
+(defn flavor-parents [flavor]
+ (->> flavor
+ (iterate (comp first (partial parents regal/flavor-hierarchy)))
+ (take-while identity)))
+
+(defn format-cases [cases]
+ (for [[form pattern & tests :as case] cases
+ :let [[props tests] (if (map? (first tests))
+ [(first tests) (rest tests)]
+ [{} tests])]]
+ (with-meta (merge
+ {:pattern pattern
+ :form form
+ :tests tests}
+ props)
+ (meta case))))
+
+(defn test-cases
+ ([]
+ (let [cases (read-test-cases)]
+ (loop [[id & cases] cases
+ result []]
+ (if id
+ (recur (drop-while vector? cases)
+ (conj result
+ {:id id
+ :cases (format-cases (take-while vector? cases))}))
+ result)))))
+
+;; BB-TEST-PATCH: bb doesn't have Pattern class
+#_(:clj
+ (do
+ (defn re2-compile ^com.google.re2j.Pattern [s]
+ (com.google.re2j.Pattern/compile s))
+ (defn re2-groups
+ [^com.google.re2j.Matcher m]
+ (let [gc (. m (groupCount))]
+ (if (zero? gc)
+ (. m (group))
+ (loop [ret [] c 0]
+ (if (<= c gc)
+ (recur (conj ret (. m (group c))) (inc c))
+ ret)))))
+ (defn re2-find
+ ([^com.google.re2j.Matcher m]
+ (when (. m (find))
+ (re2-groups m)))
+ ([^com.google.re2j.Pattern re s]
+ (let [m (.matcher re s)]
+ (re2-find m))))))
+;; BB-TEST-PATCH: Uses ns that can't load
+#_(:clj
+ (do
+ ;; Implementation for generating classes using test.chuck's charsets.
+ ;; This should eventually be moved to lambdaisland.regal.generator
+ ;; when we have our own charset implementation
+ (def token->charset-map
+ (let [whitespace-charset (apply charsets/union
+ (map (comp charsets/singleton str char) regal/whitespace-char-codes))]
+ {:any charsets/all-unicode-but-line-terminators
+ :digit (charsets/predefined-regex-classes \d)
+ :non-digit (charsets/predefined-regex-classes \D)
+ :word (charsets/predefined-regex-classes \w)
+ :non-word (charsets/predefined-regex-classes \W)
+ :whitespace whitespace-charset
+ :non-whitespace (charsets/difference
+ (charsets/intersection charsets/all-unicode
+ (charsets/range "\u0000" "\uFFFF"))
+ whitespace-charset)
+ :newline (charsets/singleton "\n")
+ :return (charsets/singleton "\r")
+ :tab (charsets/singleton "\t")
+ :form-feed (charsets/singleton "\f")
+ :alert (charsets/singleton "\u0007")
+ :escape (charsets/singleton "\u001B")
+ :vertical-whitespace (charsets/predefined-regex-classes \v)
+ :vertical-tab (charsets/singleton "\u000B")
+ :null (charsets/singleton "\u0000")}))
+
+ (defn token->charset [token]
+ (or (get token->charset-map token)
+ (throw (ex-info "Unknown token type" {:token token}))))
+
+ (defn class->charset [cls]
+ (reduce charsets/union*
+ charsets/empty
+ (for [c cls]
+ (try
+ (cond
+ (vector? c)
+ (let [[start end] (map str c)]
+ (assert (>= 0 (compare start end)))
+ (charsets/range start end))
+
+ (simple-keyword? c)
+ (token->charset c)
+
+ (string? c)
+ (reduce charsets/union*
+ (map (comp charsets/singleton str) c))
+
+ (char? c)
+ (charsets/singleton (str c)))
+ (catch Exception e
+ (throw (ex-info "Failed to translate class element into charset"
+ {:cls cls
+ :element c}
+ e)))))))
+
+ (defn class->gen [[op & elts :as expr]]
+ (let [cls (class->charset elts)
+ cls (case op
+ :not (charsets/difference charsets/all-unicode cls)
+ :class cls
+
+ (throw (ex-info "Unknown character class op" {:op op})))]
+ (if (nat-int? (charsets/size cls))
+ (gen/fmap #(charsets/nth cls %) (gen/choose 0 (dec (charsets/size cls))))
+ (throw (ex-info "Can't generate empty class" {:expr expr})))))
+
+ (defmethod regal-gen/-generator :not
+ [r _opts]
+ (class->gen r))
+
+ (defmethod regal-gen/-generator :class
+ [r _opts]
+ (class->gen r))))
+#_
+(test-cases)
diff --git a/test-resources/lib_tests/lambdaisland/regal_test.cljc b/test-resources/lib_tests/lambdaisland/regal_test.cljc
new file mode 100644
index 00000000..cdc14b74
--- /dev/null
+++ b/test-resources/lib_tests/lambdaisland/regal_test.cljc
@@ -0,0 +1,174 @@
+(ns lambdaisland.regal-test
+ (:require [lambdaisland.regal :as regal]
+ [lambdaisland.regal.spec-alpha]
+ [lambdaisland.regal.generator :as regal-gen]
+ [lambdaisland.regal.test-util :as test-util]
+ ;; BB-TEST-PATCH: bb can't load ns
+ #_[lambdaisland.regal.parse :as parse]
+ [clojure.spec.test.alpha :as stest]
+ [clojure.test :refer [deftest testing is are]]
+ [clojure.spec.alpha :as s]))
+
+(stest/instrument `regal/regex)
+
+(deftest regex-test
+ (is (= "abc"
+ (regal/pattern [:cat "a" "b" "c"])))
+
+ (is (= "a|b|c"
+ (regal/pattern [:alt "a" "b" "c"])))
+
+ (is (= "a*"
+ (regal/pattern [:* "a"])))
+
+ (is (= "(?:ab)*"
+ (regal/pattern [:* "ab"])))
+
+ (is (= "(?:ab)*"
+ (regal/pattern [:* "a" "b"])))
+
+ (is (= "(?:a|b)*"
+ (regal/pattern [:* [:alt "a" "b"]])))
+
+ (is (= "a*?"
+ (regal/pattern [:*? "a"])))
+
+ (is (= "(?:ab)*?"
+ (regal/pattern [:*? "ab"])))
+
+ (is (= "(?:ab)*?"
+ (regal/pattern [:*? "a" "b"])))
+
+ (is (= "(?:a|b)*?"
+ (regal/pattern [:*? [:alt "a" "b"]])))
+
+ (is (= "a+"
+ (regal/pattern [:+ "a"])))
+
+ (is (= "a+?"
+ (regal/pattern [:+? "a"])))
+
+ (is (= "a?"
+ (regal/pattern [:? "a"])))
+
+ (is (= "a??"
+ (regal/pattern [:?? "a"])))
+
+ (is (= "[a-z0-9_\\-]"
+ (regal/pattern [:class [\a \z] [\0 \9] \_ \-])))
+
+ (is (= "[^a-z0-9_\\-]"
+ (regal/pattern [:not [\a \z] [\0 \9] \_ \-])))
+
+ (is (= "a{3,5}"
+ (regal/pattern [:repeat \a 3 5])))
+
+ (regal/with-flavor :ecma
+ (is (= "^a$"
+ (regal/pattern [:cat :start \a :end]))))
+
+ (regal/with-flavor :java
+ (is (= "^a$"
+ (regal/pattern [:cat :start \a :end]))))
+
+ (is (= "a(?:b|c)"
+ (regal/pattern [:cat "a" [:alt "b" "c"]])))
+
+ (is (= "(abc)"
+ (regal/pattern [:capture "abc"])))
+
+ (is (= "a(b|c)"
+ (regal/pattern [:cat "a" [:capture [:alt "b" "c"]]]))))
+
+(deftest escape-test
+ (are [in out] (= out (regal/escape in))
+ "$" "\\$"
+ "(" "\\("
+ ")" "\\)"
+ "*" "\\*"
+ "+" "\\+"
+ "." "\\."
+ "?" "\\?"
+ "[" "\\["
+ "]" "\\]"
+ "\\" "\\\\"
+ "^" "\\^"
+ "{" "\\{"
+ "|" "\\|"
+ "}" "\\}"))
+
+
+(def flavors [:java8 :java9 :ecma :re2])
+
+(def parseable-flavor? #{:java8 :java9 :ecma})
+
+(deftest data-based-tests
+ (doseq [{:keys [id cases]} (test-util/test-cases)
+ {:keys [form pattern equivalent tests] :as test-case} cases
+ :let [skip? (set (when (map? pattern)
+ (for [flavor flavors
+ :when (= (get pattern flavor :skip) :skip)]
+ flavor)))
+ throws? (set (when (map? pattern)
+ (for [[flavor p] pattern
+ :when (and (vector? p) (= (first p) :throws))]
+ flavor)))]]
+
+ (testing (str (pr-str form) " -> " (pr-str pattern))
+ (is (s/valid? ::regal/form form))
+
+ (doseq [flavor flavors
+ :when (not (skip? flavor))
+ :let [pattern (if (map? pattern)
+ (some pattern (test-util/flavor-parents flavor))
+ pattern)]]
+ (if (throws? flavor)
+ (testing (str "Generating pattern throws (" (name id) ") " (pr-str form) " (" flavor ")")
+ (if-some [msg (second pattern)]
+ (is (thrown-with-msg? #?(:clj Exception
+ :cljs js/Error) (re-pattern msg)
+ (regal/with-flavor flavor
+ (regal/pattern form))))
+ (is (thrown? #?(:clj Exception
+ :cljs js/Error)
+ (regal/with-flavor flavor
+ (regal/pattern form))))))
+ (testing (str "Generated pattern is correct (" (name id) ") " (pr-str form) " (" flavor ")")
+ (regal/with-flavor flavor
+ (is (= pattern (regal/pattern form))))))
+
+ ;; BB-TEST-PATCH: Uses ns that can't load
+ #_(when (and (parseable-flavor? flavor)
+ (not-any? (comp :no-parse meta) [test-case cases]))
+ (testing (str "Pattern parses correctly (" (name id) ") " (pr-str pattern) " (" flavor ")")
+ (regal/with-flavor flavor
+ (is (= form (parse/parse-pattern pattern)))))))
+
+ (doseq [[input match] tests]
+ (testing (str "Test case " (pr-str form) " matches " (pr-str input))
+
+ (testing "Generated pattern matches"
+ (is (= match (re-find (regal/regex form) input))))
+ ;; BB-TEST-PATCH: Uses ns that can't load
+ #_(:clj
+ (when-not (or (skip? :re2) (throws? :re2))
+ (testing "Generated pattern matches (re2)"
+ (is (= match (test-util/re2-find (regal/with-flavor :re2
+ (test-util/re2-compile
+ (regal/pattern form)))
+ input))))))
+
+ (doseq [pattern (if (map? equivalent)
+ (some equivalent (test-util/flavor-parents (regal/runtime-flavor)))
+ equivalent)]
+ (testing (str "Alternative equivalent pattern " (pr-str pattern) " matches")
+ (is (= match (re-find (regal/compile pattern) input)))))))
+
+ (testing (str "creating a generator does not throw exception " (pr-str form))
+ (is (regal-gen/gen form)))
+
+ ;; We should do this with proper properties so we get shrinking, just a
+ ;; basic check for now
+ (testing (str "generated strings match the given pattern " (pr-str form))
+ (doseq [s (regal-gen/sample form)]
+ (is (re-find (regal/regex form) s)))))))
diff --git a/test-resources/lib_tests/markdown/md_test.cljc b/test-resources/lib_tests/markdown/md_test.cljc
new file mode 100644
index 00000000..b2279d00
--- /dev/null
+++ b/test-resources/lib_tests/markdown/md_test.cljc
@@ -0,0 +1,450 @@
+(ns markdown.md-test
+ (:require #?(:cljs [goog.string])
+ [clojure.test :refer [deftest is]]
+ [markdown.core :as markdown]
+ [markdown.tables :as tables]))
+
+(def entry-function
+ #?(:clj markdown/md-to-html-string
+ :cljs markdown/md->html))
+
+(deftest heading1
+ (is (= "Ticket #123
" (entry-function "# Ticket #123")))
+ (is (= "Foo
" (entry-function " # Foo")))
+ (is (= "foo
" (entry-function "#foo")))
+ (is (= "foo
" (entry-function "foo\n===")))
+ (is (= "foo
" (entry-function "#foo#")))
+ (is (= "foo
" (entry-function "#foo#\n")))
+ (is (= "some header with_an_underscore
"
+ (entry-function "# some header `with_an_underscore`")))
+ (is (= "heading1
"
+ (entry-function "* one\n\nheading1\n========\n"))))
+
+(deftest heading2
+ (is (= "foo
" (entry-function "##foo")))
+ (is (= "foo
" (entry-function "foo\n---")))
+ (is (= "foo
" (entry-function "##foo##")))
+ (is (= "foo
" (entry-function "##foo##\n"))))
+
+(deftest heading-with-complex-anchor
+ (is (=
+ "foo bar BAz
some text
"
+ (entry-function "###foo bar BAz\nsome text" :heading-anchors true)))
+ (is (=
+ "foo bar BAz
some text
"
+ (entry-function "###foo bar BAz##\nsome text" :heading-anchors true))))
+
+(deftest br
+ (is (= "foo
" (entry-function "foo "))))
+
+(deftest hr
+ (is (= "
" (entry-function "***")))
+ (is (= "
" (entry-function " * * * ")))
+ (is (= "
" (entry-function " *****")))
+ (is (= "
" (entry-function "- - - "))))
+
+(deftest em
+ (is (= "foo
" (entry-function "*foo*"))))
+
+(deftest italics
+ (is (= "foo
" (entry-function "_foo_"))))
+
+(deftest strong
+ (is (= "foo
" (entry-function "**foo**"))))
+
+(deftest bold-italics
+ (is (= "foo
" (entry-function "***foo***"))))
+
+(deftest bold
+ (is (= "foo
" (entry-function "__foo__"))))
+
+(deftest strong-inside-em
+ (is (= "foobarbaz
" (entry-function "*foo**bar**baz*"))))
+
+(deftest bold-inside-a-list
+ (is (= "- chickens.
See more: Cluck Cluck
"
+ (entry-function "1. chickens. \n\n **See more: [Cluck Cluck](http://cluck.cluck.com)** \n\n"))))
+
+(deftest em-inside-strong
+ (is (= "foobarbaz
" (entry-function "**foo*bar*baz**"))))
+
+(deftest paragraph
+ (is (= "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
"
+ (entry-function "\nLorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore"))))
+
+(deftest paragraph-multiline
+ (is (= "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
"
+ (entry-function "\nLorem ipsum dolor\nsit amet, consectetur adipisicing elit,\nsed do eiusmod tempor incididunt ut labore"))))
+
+(deftest paragraph-before-codeblock
+ (is (= "foo
bar\n
baz
"
+ (entry-function "foo\n```\nbar\n```\nbaz")))
+ (is (= "foo \nbar
" (entry-function "```\nfoo \nbar```")))
+ (is (= "
" (entry-function "```\n```")))
+ (is (= "
" (entry-function "```go\n```")))
+ (is (= "<html>\n</html>\n
" (entry-function "```\n\n\n``` "))))
+
+(deftest paragraph-after-codeblock
+ (is (= "foo\n
bar baz
"
+ (entry-function "```\nfoo\n```\nbar\nbaz"))))
+
+(deftest mulitple-paragraphs
+ (is (= "foo bar baz
foo bar baz
"
+ (entry-function "\nfoo bar baz\n\n\nfoo bar baz"))))
+
+(deftest ul
+ (is (= ""
+ (entry-function "* foo\n* bar\n* baz")))
+ (is (= ""
+ (entry-function "- foo\n- bar\n- baz")))
+ (is (= ""
+ (entry-function "+ foo\n+ bar\n+ baz"))))
+
+(deftest list-in-a-codeblock
+ (is
+ (= "list:\n- 1\n- 2\n
"
+ (entry-function "```yaml\nlist:\n- 1\n- 2\n```"))))
+
+(deftest ul-followed-by-paragraph
+ (is (= "paragraph next line
"
+ (entry-function "* foo\n* bar\n* baz\n\nparagraph\nnext line"))))
+
+(deftest ul-with-codeblock
+ (is (= "- foo
- bar
(defn foo []\n bar)\n
- baz
- more text
"
+ (entry-function
+ "\n* foo\n* bar\n ```\n (defn foo []\n bar)\n ```\n* baz\n* more text\n")))
+ (is (= "- foo
- bar
(defn foo []\n bar)\n
text - baz
- more text
"
+ (entry-function
+ "\n* foo\n* bar\n ```\n (defn foo []\n bar)\n ```\n text\n* baz\n* more text\n"))))
+
+(deftest ul-followed-by-multiline-paragraph
+ (is (= "paragraph
"
+ (entry-function "* foo\n* bar\n* baz\n\nparagraph"))))
+
+(deftest ul-nested
+ (is (= "- first item
- first sub-item
- third sub-item
- second item
- first sub-item
- second sub-item
- third item
"
+ (entry-function "* first item\n * first sub-item\n * second sub-item\n * third sub-item\n* second item\n * first sub-item\n * second sub-item\n* third item")))
+ (is (= "- first item
- first sub-item
- third sub-item
- second item
- first sub-item
- second sub-item
- third item
"
+ (entry-function "* first item\n - first sub-item\n - second sub-item\n - third sub-item\n* second item\n + first sub-item\n + second sub-item\n* third item")))
+ (is (= "" (entry-function " * abc\n\n+ def"))))
+
+(deftest ol
+ (is (= "- Foo
- Bar
- Baz
"
+ (entry-function "1. Foo\n2. Bar\n3. Baz"))))
+
+(deftest ul-in-ol
+ (is (= "- Bar
- Subbar
- Baz
"
+ (entry-function "1. Bar\n 2. Subbar\n * foo\n * bar\n * baz\n3. Baz"))))
+
+(deftest ol-in-ul
+ (is (= ""
+ (entry-function "* Foo\n 1. Bar\n 1. Subbar\n* Baz")))
+ (is (= ""
+ (entry-function "* Foo\n 1. Bar"))))
+
+(deftest multilist
+ (is (=
+ ""
+ (entry-function
+ "* foo
+* bar
+
+ * baz
+ 1. foo
+ 2. bar
+
+ * fuzz
+
+ * blah
+ * blue
+* brass"))))
+
+(deftest code
+ (is (= "foo bar baz x = y + z; foo
"
+ (entry-function "foo bar baz `x = y + z;` foo")))
+ (is (= "bar foo --- -- bar foo
"
+ (entry-function "bar `foo --- -- bar` foo")))
+ (is (= "<?xml version='1.0' encoding='UTF-8'?><channel></channel>
"
+ (entry-function "``")))
+ (is (= "foo bar baz (fn [x & xs] (str "x:" x)) foo
"
+ (entry-function "foo bar baz `(fn [x & xs] (str \"x:\" x))` foo")))
+ (is (= "```\nfoo\n```
"
+ (entry-function " ```\n foo\n ```"))))
+
+(deftest multiline-code
+ (is (= "x = 5\ny = 6\nz = x + y
"
+ (entry-function " x = 5\n y = 6\n z = x + y")))
+ (is (= "x = 5\ny = 6\nz = x + y\n(fn [x & xs] (str "x"))
"
+ (entry-function " x = 5\n y = 6\n z = x + y\n (fn [x & xs] (str \"x\"))"))))
+
+(deftest codeblock
+ (is (= "(defn- write^ [writer text]\n (doseq [c text]\n (.write writer (int c))))\n
"
+ (entry-function "```\n(defn- write^ [writer text]\n (doseq [c text]\n (.write writer (int c))))\n```")))
+ (is (= "(fn [x & xs]\n (str "x"))\n
"
+ (entry-function "```\n(fn [x & xs]\n (str \"x\"))\n```")))
+ (is (= "(fn [x & xs]\n (str "x"))\n
"
+ (entry-function "```\n(fn [x & xs]\n (str \"x\"))\n```")))
+ (is (= "(fn [x & xs]\n (str "x"))\n
"
+ (entry-function "```clojure\n(fn [x & xs]\n (str \"x\"))\n```")))
+ (is (= "------------\n============\n ------------\n ============\n
"
+ (entry-function
+ "
+```nohighlight
+------------
+============
+ ------------
+ ============
+```
+"))))
+
+(deftest indented-codeblock
+ (is (= "foo
"
+ (entry-function " foo")))
+ (is (= "foo
bar
"
+ (entry-function " foo\n\nbar")))
+ (is (= "foo
bar"
+ (entry-function " foo\nbar")))
+ (is (= "baz foo
bar
"
+ (entry-function "baz\n foo\n\nbar")))
+ (is (= ""
+ (entry-function ""))))
+
+(deftest strikethrough
+ (is (= "foo
"
+ (entry-function "~~foo~~"))))
+
+(deftest superscript
+ (is (= "foobar baz
"
+ (entry-function "foo^bar baz"))))
+
+(deftest link
+ (is (= "underscoresarefine
"
+ (entry-function "underscores_are_fine")))
+ (is (= "github
"
+ (entry-function "[github](http://github.com)")))
+ (is (= "github
"
+ (entry-function "[github](http://github.com/~)")))
+ (is (= "github
"
+ (entry-function "[github](http://github.com/^)")))
+ (is (= "github
"
+ (entry-function "[github](http://github.com/*)")))
+ (is (= ""
+ (entry-function "* [github](http://github.com/*)")))
+ (is (= "a link
"
+ (entry-function "* hi\n\n[a link](https://see-here)")))
+ (is (= ">!
"
+ (entry-function "[>!](https://clojure.github.io/core.async/#clojure.core.async/>!)")))
+ (is (= "
"
+ (entry-function "[github"
+ (entry-function "[*github*](http://github.com)")))
+ (is (= "github
"
+ (entry-function "[_github_](http://github.com)")))
+ (is (= "github
"
+ (entry-function "[__github__](http://github.com)")))
+ (is (= "github
"
+ (entry-function "[**github**](http://github.com)")))
+ (is (= "github
"
+ (entry-function "[~~github~~](http://github.com)"))))
+
+(deftest img
+ (is (= "
"
+ (entry-function "")))
+ (is (= "
"
+ (entry-function ""))))
+
+(deftest img-link
+ (is (= "
"
+ (entry-function "[](http://travis-ci.org/yogthos/markdown-clj)")))
+ (is (= "
"
+ (entry-function ""))))
+
+(deftest bad-link
+ (is (= "[github](http://github.comfooo
"
+ (entry-function "[github](http://github.comfooo")))
+ (is (= "[github] no way (http://github.com)
"
+ (entry-function "[github] no way (http://github.com)"))))
+
+(deftest bad-link-title
+ (is (= "[github(http://github.comfooo)
"
+ (entry-function "[github(http://github.comfooo)"))))
+
+(deftest blockquote
+ (is (= "Foo bar baz
"
+ (entry-function ">Foo bar baz"))))
+
+(deftest blockquote-footer
+ (is (= " Foo bar baz
"
+ (entry-function "> Foo bar baz\n>- Leo Tolstoy"))))
+
+(deftest blockquote-empty-footer
+ (is (= " Foo bar baz
"
+ (entry-function "> Foo bar baz\n>-"))))
+
+(deftest blockquote-multiline-without-leading-angle-bracket
+ (is (= " Foo bar baz
"
+ (entry-function "> Foo bar\nbaz"))))
+
+(deftest blockquote-multiple-paragraphs
+ (is (= " Foo bar
baz
"
+ (entry-function "> Foo bar\n>\n> baz"))))
+
+(deftest blockquote-bullets
+ (is (= " list:
end.
"
+ (entry-function "> list:\n>* foo\n>* bar\n\nend.")))
+ (is (= "
"
+ (entry-function ">* foo\n>* bar\n>* baz"))))
+
+(deftest blockquote-headings
+ (is (= "Foo
bar baz
"
+ (entry-function "> ## Foo\n>bar baz")))
+ (is (= " Foo
bar
baz
"
+ (entry-function "> Foo\n>## bar\n> baz"))))
+
+(deftest escaped-characters
+ (is
+ (= "^*‘_{}[]footestbar{x}[y]
"
+ (entry-function "\\^\\*\\`\\_\\{\\}\\[\\]*foo*`test`_bar_{x}[y]"))))
+
+(deftest paragraph-after-list
+ (is (= "- a
- b
test bold and italic
"
+ (entry-function "1. a\n2. b\n\ntest **bold** and *italic*"))))
+
+(deftest paragraph-close-before-list
+ (is (= "in paragraph
"
+ (entry-function "in paragraph\n- list"))))
+
+(deftest autourl
+ (is (= "http://example.com/
"
+ (entry-function "")))
+
+ (is (= "Some content with a http://www.google.com/abc__123__efg link it in
"
+ (entry-function "Some content with a link it in")))
+
+ (is (= "http://foo https://bar/baz foo bar
"
+ (entry-function " foo bar")))
+
+ #?(:bb nil :org.babashka/nbb nil
+ :default
+ (is (= "abc@google.com
"
+ (#?(:clj org.apache.commons.lang.StringEscapeUtils/unescapeHtml
+ :cljs goog.string/unescapeEntities)
+ (entry-function "")))))
+
+ #?(:bb nil :org.babashka/nbb nil
+ :default
+ (is (= "abc_def_ghi@google.com
"
+ (#?(:clj org.apache.commons.lang.StringEscapeUtils/unescapeHtml
+ :cljs goog.string/unescapeEntities)
+ (entry-function ""))))))
+
+(deftest not-a-list
+ (is (= "The fish was 192.8 lbs and was amazing to see.
"
+ (entry-function "The fish was\n192.8 lbs and was amazing to see."))))
+
+(deftest dont-encode-chars-in-hrefs
+ (is (= "example_link with tilde ~ and carat ^ and splat *
"
+ (entry-function "[example_link with tilde ~ and carat ^ and splat *](http://www.google.com/example_link_foo~_^*)"))))
+
+(deftest complex-link-with-terminal-encoding-inside-header
+ (is (= ""
+ (entry-function "##With a link [the contents of the_link](http://a.com/under_score_in_the_link/)"))))
+
+(deftest two-links-tests-link-processing
+ (is (= "When you have a pair of links link1 and you want both Wow
"
+ (entry-function "## When you have a pair of links [link1](http://123.com/1) and you want both [Wow](That%27s%20crazy)"))))
+
+(deftest link-then-image-processing
+ (is (= "You can have a link followed by an image 
"
+ (entry-function "You can have a [link](github.com) followed by an image "))))
+
+(deftest image-then-link-processing
+ (is (= "You can have an image
followed by a link
"
+ (entry-function "You can have an image  followed by a [link](github.com)"))))
+
+(deftest link-with-optional-title
+ (is (= "Cryogens site
"
+ (entry-function "[Cryogens site](https://github.com/cryogen-project/cryogen \"Cryogen Github\")"))))
+
+(deftest parse-table-row
+ (is (= (tables/parse-table-row "| table cell contents |") [{:text "table cell contents"}]))
+ (is (= (tables/parse-table-row "| contents 1 | contents 2 | contents 3 | contents 4 |")
+ [{:text "contents 1"} {:text "contents 2"} {:text "contents 3"} {:text "contents 4"}])))
+
+(deftest table-row->str
+ (is (= (tables/table-row->str
+ [{:text "contents 1"} {:text "contents 2"} {:text "contents 3"} {:text "contents 4"}]
+ true)
+ "contents 1 | contents 2 | contents 3 | contents 4 | "))
+ (is (= (tables/table-row->str
+ [{:text "contents 1"} {:text "contents 2"} {:text "contents 3"} {:text "contents 4"}]
+ false)
+ "contents 1 | contents 2 | contents 3 | contents 4 | "))
+ (is (= (tables/table-row->str
+ [{:text "contents 1" :alignment :left}
+ {:text "contents 2" :alignment :center}
+ {:text "contents 3" :alignment :right}
+ {:text "contents 4"}]
+ false)
+ "contents 1 | contents 2 | contents 3 | contents 4 | ")))
+
+(deftest table->str
+ (is (= (tables/table->str
+ {:alignment-seq
+ [{:alignment :left} {:alignment :center} {:alignment :right} {:alignment nil}]
+ :data [[{:text "Header 1"}
+ {:text "Header 2"}
+ {:text "Header 3"}
+ {:text "Header 4"}]
+ [{:text "contents 1"}
+ {:text "contents 2"}
+ {:text "contents 3"}
+ {:text "contents 4"}]]})
+ "| Header 1 | Header 2 | Header 3 | Header 4 |
|---|
| contents 1 | contents 2 | contents 3 | contents 4 |
")))
+
+(deftest divider-seq->alignment
+ (is (= (tables/divider-seq->alignment
+ [{:text "-----"} {:text ":-----"} {:text "-----:"} {:text ":-----:"}])
+ [nil {:alignment :left} {:alignment :right} {:alignment :center}])))
+
+(deftest n-dash
+ (is (= "boo – bar
" (entry-function "boo -- bar"))))
+
+(deftest m-dash
+ (is (= "boo — bar
" (entry-function "boo --- bar"))))
+
+(deftest inhibit-simple
+ (is (= "_abc_
" (entry-function "$_abc_$" :inhibit-separator "$"))))
+
+(deftest inhibit-simple-seq
+ (is (= "_abc_
" (entry-function "$_abc_$" :inhibit-separator [\$]))))
+
+(deftest inhibit-inline-code
+ (is (= "`abc`
" (entry-function "$`abc`$" :inhibit-separator [\$]))))
+
+(deftest inhibit-inside-code
+ (is (= "a*b* & dc
" (entry-function "`a$*b* & d$c`" :inhibit-separator "$"))))
+
+(deftest inhibit-across-backticks
+ (is (= "one` `two
" (entry-function "`one$` `$two`" :inhibit-separator "$"))))
+
+(deftest inhibit-escape
+ (is (= "$
" (entry-function "$$" :inhibit-separator [\$]))))
+
+(deftest inhibit-escape-twice
+ (is (= "$$
" (entry-function "$$$$" :inhibit-separator "$"))))
+
+(deftest img-reprocess
+ (is (= "
and Edit
"
+ (entry-function " and [Edit](#)"))))
+
+(deftest dont-inhibit-text-within-escapes
+ (is (= "$abc$
" (entry-function "$$*abc*$$" :inhibit-separator "$"))))
+
+(deftest inhibit-escape-inside-code
+ (is (= "$
" (entry-function "`$$`" :inhibit-separator "$"))))
+
+(deftest whitespace-paragraphs
+ (is (= "foo
bar
" (entry-function "foo\n \nbar"))))
diff --git a/test-resources/lib_tests/markdown/nbb_runner.cljs b/test-resources/lib_tests/markdown/nbb_runner.cljs
new file mode 100644
index 00000000..ec9bffa0
--- /dev/null
+++ b/test-resources/lib_tests/markdown/nbb_runner.cljs
@@ -0,0 +1,10 @@
+(ns nbb-runner
+ (:require [clojure.string :as str]
+ [clojure.test :refer [run-tests]]
+ [nbb.classpath :as cp]))
+
+(cp/add-classpath (str/join ":" ["src/cljs" "src/cljc" "test"]))
+
+(require '[markdown.md-test])
+
+(run-tests 'markdown.md-test)
diff --git a/test-resources/lib_tests/markdown/runner.cljs b/test-resources/lib_tests/markdown/runner.cljs
new file mode 100644
index 00000000..aa2cf9f4
--- /dev/null
+++ b/test-resources/lib_tests/markdown/runner.cljs
@@ -0,0 +1,5 @@
+(ns markdown.runner
+ (:require [doo.runner :refer-macros [doo-tests]]
+ [markdown.md-test]))
+
+(doo-tests 'markdown.md-test)
diff --git a/test-resources/lib_tests/medley/core_test.cljc b/test-resources/lib_tests/medley/core_test.cljc
new file mode 100644
index 00000000..98f6c505
--- /dev/null
+++ b/test-resources/lib_tests/medley/core_test.cljc
@@ -0,0 +1,409 @@
+(ns medley.core-test
+ #?(:clj (:import [clojure.lang ArityException]))
+ (:require #?(:clj [clojure.test :refer :all]
+ :cljs [cljs.test :refer-macros [deftest is testing]])
+ [medley.core :as m]))
+
+(deftest test-find-first
+ (testing "sequences"
+ (is (= (m/find-first even? [7 3 3 2 8]) 2))
+ (is (nil? (m/find-first even? [7 3 3 7 3]))))
+ (testing "transducers"
+ (is (= (transduce (m/find-first even?) + 0 [7 3 3 2 8]) 2))
+ (is (= (transduce (m/find-first even?) + 0 [7 3 3 7 3]) 0))))
+
+(deftest test-dissoc-in
+ (is (= (m/dissoc-in {:a {:b {:c 1 :d 2}}} [:a :b :c])
+ {:a {:b {:d 2}}}))
+ (is (= (m/dissoc-in {:a {:b {:c 1}}} [:a :b :c])
+ {}))
+ (is (= (m/dissoc-in {:a {:b {:c 1} :d 2}} [:a :b :c])
+ {:a {:d 2}}))
+ (is (= (m/dissoc-in {:a {:b {:c 1} :d 2} :b {:c {:d 2 :e 3}}} [:a :b :c] [:b :c :d])
+ {:a {:d 2} :b {:c {:e 3}}}))
+ (is (= (m/dissoc-in {:a 1} [])
+ {:a 1})))
+
+(deftest test-assoc-some
+ (is (= (m/assoc-some {:a 1} :b 2) {:a 1 :b 2}))
+ (is (= (m/assoc-some {:a 1} :b nil) {:a 1}))
+ (is (= (m/assoc-some {:a 1} :b 2 :c nil :d 3) {:a 1 :b 2 :d 3})))
+
+(deftest test-update-existing
+ (is (= (m/update-existing {:a 1} :a inc) {:a 2}))
+ (is (= (m/update-existing {:a 1 :b 2} :a inc) {:a 2 :b 2}))
+ (is (= (m/update-existing {:b 2} :a inc) {:b 2}))
+ (is (= (m/update-existing {:a nil} :a str) {:a ""}))
+ (is (= (m/update-existing {} :a str) {})))
+
+(deftest test-update-existing-in
+ (is (= (m/update-existing-in {:a 1} [:a] inc) {:a 2}))
+ (is (= (m/update-existing-in {:a 1 :b 2} [:a] inc) {:a 2 :b 2}))
+ (is (= (m/update-existing-in {:b 2} [:a] inc) {:b 2}))
+ (is (= (m/update-existing-in {:a nil} [:a] str) {:a ""}))
+ (is (= (m/update-existing-in {} [:a] str) {}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] inc)
+ {:a [:b {:c 43} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 7)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 4)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] + 3 3 1)
+ {:a [:b {:c 49} :d]}))
+ (is (= (m/update-existing-in {:a [:b {:c 42} :d]} [:a 1 :c] vector 9 10 11 12 13 14)
+ {:a [:b {:c [42 9 10 11 12 13 14]} :d]})))
+
+(deftest test-map-entry
+ (is (= (key (m/map-entry :a 1)) :a))
+ (is (= (val (m/map-entry :a 1)) 1))
+ (is (= (first (m/map-entry :a 1)) :a))
+ (is (= (second (m/map-entry :a 1)) 1))
+ (is (= (type (m/map-entry :a 1))
+ (type (first {:a 1})))))
+
+(defrecord MyRecord [x])
+
+(deftest test-map-kv
+ (is (= (m/map-kv (fn [k v] [(name k) (inc v)]) {:a 1 :b 2})
+ {"a" 2 "b" 3}))
+ (is (= (m/map-kv (fn [k v] [(name k) (inc v)]) (sorted-map :a 1 :b 2))
+ {"a" 2 "b" 3}))
+ (is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) {:a 1 :b 2})
+ {"a" 2 "b" 3}))
+ (testing "map-kv with record"
+ (is (= (m/map-kv (fn [k v] (m/map-entry (name k) (inc v))) (->MyRecord 1)) {"x" 2}))))
+
+(deftest test-map-keys
+ (is (= (m/map-keys name {:a 1 :b 2})
+ {"a" 1 "b" 2}))
+ (is (= (m/map-keys name (sorted-map :a 1 :b 2))
+ (sorted-map "a" 1 "b" 2)))
+ (testing "map-keys with record"
+ (is (= (m/map-keys name (->MyRecord 1)) {"x" 1}))))
+
+(deftest test-map-vals
+ (is (= (m/map-vals inc {:a 1 :b 2})
+ {:a 2 :b 3}))
+ (is (= (m/map-vals inc (sorted-map :a 1 :b 2))
+ (sorted-map :a 2 :b 3)))
+ (testing "map-vals with record"
+ (is (= (m/map-vals inc (->MyRecord 1)) {:x 2})))
+ (testing "multiple collections"
+ (is (= (m/map-vals + {:a 1 :b 2 :c 3} {:a 4 :c 5 :d 6})
+ {:a 5, :c 8}))
+ (is (= (m/map-vals min
+ (sorted-map :z 10 :y 8 :x 4)
+ {:x 7, :y 14, :z 13}
+ {:x 11, :y 6, :z 9}
+ {:x 19, :y 3, :z 2}
+ {:x 4, :y 0, :z 16}
+ {:x 17, :y 14, :z 13})
+ (sorted-map :x 4 :y 0 :z 2)))
+ (is (= (m/map-vals #(%1 %2) {:a nil? :b some?} {:b nil})
+ {:b false}))))
+
+(deftest test-map-kv-keys
+ (is (= (m/map-kv-keys + {1 2, 2 4})
+ {3 2, 6 4}))
+ (is (= (m/map-kv-keys + (sorted-map 1 2, 2 4))
+ (sorted-map 3 2, 6 4)))
+ (is (= (m/map-kv-keys str (->MyRecord 1))
+ {":x1" 1})))
+
+(deftest test-map-kv-vals
+ (is (= (m/map-kv-vals + {1 2, 2 4})
+ {1 3, 2 6}))
+ (is (= (m/map-kv-vals + (sorted-map 1 2, 2 4))
+ (sorted-map 1 3, 2 6)))
+ (is (= (m/map-kv-vals str (->MyRecord 1))
+ {:x ":x1"})))
+
+(deftest test-filter-kv
+ (is (= (m/filter-kv (fn [k v] (and (keyword? k) (number? v))) {"a" 1 :b 2 :c "d"})
+ {:b 2}))
+ (is (= (m/filter-kv (fn [k v] (= v 2)) (sorted-map "a" 1 "b" 2))
+ (sorted-map "b" 2))))
+
+(deftest test-filter-keys
+ (is (= (m/filter-keys keyword? {"a" 1 :b 2})
+ {:b 2}))
+ (is (= (m/filter-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2))
+ (sorted-map "b" 2))))
+
+(deftest test-filter-vals
+ (is (= (m/filter-vals even? {:a 1 :b 2})
+ {:b 2}))
+ (is (= (m/filter-vals even? (sorted-map :a 1 :b 2))
+ (sorted-map :b 2))))
+
+(deftest test-remove-kv
+ (is (= (m/remove-kv (fn [k v] (and (keyword? k) (number? v))) {"a" 1 :b 2 :c "d"})
+ {"a" 1 :c "d"}))
+ (is (= (m/remove-kv (fn [k v] (= v 2)) (sorted-map "a" 1 "b" 2))
+ (sorted-map "a" 1))))
+
+(deftest test-remove-keys
+ (is (= (m/remove-keys keyword? {"a" 1 :b 2})
+ {"a" 1}))
+ (is (= (m/remove-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2))
+ {"a" 1})))
+
+(deftest test-remove-vals
+ (is (= (m/remove-vals even? {:a 1 :b 2})
+ {:a 1}))
+ (is (= (m/remove-keys #(re-find #"^b" %) (sorted-map "a" 1 "b" 2))
+ {"a" 1})))
+
+(deftest test-queue
+ (testing "empty"
+ #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue)))
+ :cljs (is (instance? cljs.core.PersistentQueue (m/queue))))
+ (is (empty? (m/queue))))
+ (testing "not empty"
+ #?(:clj (is (instance? clojure.lang.PersistentQueue (m/queue [1 2 3])))
+ :cljs (is (instance? cljs.core.PersistentQueue (m/queue [1 2 3]))))
+ (is (= (first (m/queue [1 2 3])) 1))))
+
+(deftest test-queue?
+ #?(:clj (is (m/queue? clojure.lang.PersistentQueue/EMPTY))
+ :cljs (is (m/queue? cljs.core.PersistentQueue.EMPTY)))
+ (is (not (m/queue? []))))
+
+(deftest test-boolean?
+ (is (m/boolean? true))
+ (is (m/boolean? false))
+ (is (not (m/boolean? nil)))
+ (is (not (m/boolean? "foo")))
+ (is (not (m/boolean? 1))))
+
+(deftest test-least
+ (is (= (m/least) nil))
+ (is (= (m/least "a") "a"))
+ (is (= (m/least "a" "b") "a"))
+ (is (= (m/least 3 2 5 -1 0 2) -1)))
+
+(deftest test-greatest
+ (is (= (m/greatest) nil))
+ (is (= (m/greatest "a") "a"))
+ (is (= (m/greatest "a" "b") "b"))
+ (is (= (m/greatest 3 2 5 -1 0 2) 5)))
+
+(deftest test-join
+ (is (= (m/join [[1 2] [] [3] [4 5 6]]) [1 2 3 4 5 6]))
+ (is (= (m/join (sorted-map :x 1, :y 2, :z 3)) [:x 1 :y 2 :z 3]))
+ (let [a (atom 0)
+ s (m/join (iterate #(do (swap! a inc) (range (inc (count %)))) ()))]
+ (is (= (first s) 0))
+ (is (= @a 1))
+ (is (= (second s) 0))
+ (is (= @a 2))))
+
+(deftest test-deep-merge
+ (is (= (m/deep-merge) nil))
+ (is (= (m/deep-merge {:a 1}) {:a 1}))
+ (is (= (m/deep-merge {:a 1} nil) {:a 1}))
+ (is (= (m/deep-merge {:a 1} {:a 2 :b 3}) {:a 2 :b 3}))
+ (is (= (m/deep-merge {:a {:b 1 :c 2}} {:a {:b 2 :d 3}}) {:a {:b 2 :c 2 :d 3}}))
+ (is (= (m/deep-merge {:a {:b 1}} {:a 1}) {:a 1}))
+ (is (= (m/deep-merge {:a 1} {:b 2} {:b 3 :c 4}) {:a 1 :b 3 :c 4}))
+ (is (= (m/deep-merge {:a {:b {:c {:d 1}}}} {:a {:b {:c {:e 2}}}}) {:a {:b {:c {:d 1 :e 2}}}}))
+ (is (= (m/deep-merge {:a {:b [1 2]}} {:a {:b [3 4]}}) {:a {:b [3 4]}}))
+ (is (= (m/deep-merge (->MyRecord 1) {:x 2}) (->MyRecord 2)))
+ (is (= (m/deep-merge {:a (->MyRecord 1)} {:a {:x 2 :y 3}}) {:a (map->MyRecord {:x 2 :y 3})})))
+
+(deftest test-mapply
+ (letfn [(foo [& {:keys [bar]}] bar)]
+ (is (= (m/mapply foo {}) nil))
+ (is (= (m/mapply foo {:baz 1}) nil))
+ (is (= (m/mapply foo {:bar 1}) 1)))
+ (letfn [(foo [bar & {:keys [baz]}] [bar baz])]
+ (is (= (m/mapply foo 0 {}) [0 nil]))
+ (is (= (m/mapply foo 0 {:baz 1}) [0 1]))
+ (is (= (m/mapply foo 0 {:spam 1}) [0 nil]))
+ (is (= (m/mapply foo 0 nil) [0 nil]))
+ #?@(:clj [;; BB-TEST-PATCH: bb throws Exception
+ #_(is (thrown? ArityException (m/mapply foo {})))
+ (is (thrown? IllegalArgumentException (m/mapply foo 0)))]
+ :cljs [(is (thrown? js/Error (m/mapply foo 0)))])))
+
+(deftest test-index-by
+ (is (= (m/index-by identity [1 2 3]) {1 1, 2 2, 3 3}))
+ (is (= (m/index-by inc [1 2 3]) {2 1, 3 2, 4 3}))
+ (is (= (m/index-by first ["foo" "bar" "baz"]) {\f "foo", \b "baz"}))
+ (is (= (m/index-by first []) {})))
+
+(deftest test-interleave-all
+ (is (= (m/interleave-all []) []))
+ (is (= (m/interleave-all [1 2 3]) [1 2 3]))
+ (is (= (m/interleave-all [1 2 3] [4 5 6]) [1 4 2 5 3 6]))
+ (is (= (m/interleave-all [1 2 3] [4 5 6] [7 8 9]) [1 4 7 2 5 8 3 6 9]))
+ (is (= (m/interleave-all [1 2] [3]) [1 3 2]))
+ (is (= (m/interleave-all [1 2 3] [4 5]) [1 4 2 5 3]))
+ (is (= (m/interleave-all [1] [2 3] [4 5 6]) [1 2 4 3 5 6])))
+
+(deftest test-distinct-by
+ (testing "sequences"
+ (is (= (m/distinct-by count ["a" "ab" "c" "cd" "def"])
+ ["a" "ab" "def"]))
+ (is (= (m/distinct-by count [])
+ []))
+ (is (= (m/distinct-by first ["foo" "faa" "boom" "bar"])
+ ["foo" "boom"])))
+
+ (testing "transucers"
+ (is (= (into [] (m/distinct-by count) ["a" "ab" "c" "cd" "def"])
+ ["a" "ab" "def"]))
+ (is (= (into [] (m/distinct-by count) [])
+ []))
+ (is (= (into [] (m/distinct-by first) ["foo" "faa" "boom" "bar"])
+ ["foo" "boom"]))))
+
+(deftest test-dedupe-by
+ (testing "sequences"
+ (is (= (m/dedupe-by count ["a" "b" "bc" "bcd" "cd"])
+ ["a" "bc" "bcd" "cd"]))
+ (is (= (m/dedupe-by count [])
+ []))
+ (is (= (m/dedupe-by first ["foo" "faa" "boom" "bar"])
+ ["foo" "boom"])))
+
+ (testing "transucers"
+ (is (= (into [] (m/dedupe-by count) ["a" "b" "bc" "bcd" "cd"])
+ ["a" "bc" "bcd" "cd"]))
+ (is (= (into [] (m/dedupe-by count) [])
+ []))
+ (is (= (into [] (m/dedupe-by first) ["foo" "faa" "boom" "bar"])
+ ["foo" "boom"]))))
+
+(deftest test-take-upto
+ (testing "sequences"
+ (is (= (m/take-upto zero? [1 2 3 0 4 5 6]) [1 2 3 0]))
+ (is (= (m/take-upto zero? [0 1 2 3 4 5 6]) [0]))
+ (is (= (m/take-upto zero? [1 2 3 4 5 6 7]) [1 2 3 4 5 6 7])))
+
+ (testing "tranducers"
+ (is (= (into [] (m/take-upto zero?) [1 2 3 0 4 5 6]) [1 2 3 0]))
+ (is (= (into [] (m/take-upto zero?) [0 1 2 3 4 5 6]) [0]))
+ (is (= (into [] (m/take-upto zero?) [1 2 3 4 5 6 7]) [1 2 3 4 5 6 7]))
+ (is (= (transduce (m/take-upto zero?)
+ (completing (fn [_ x] (reduced x)))
+ nil
+ [0 1 2])
+ 0))))
+
+(deftest test-drop-upto
+ (testing "sequences"
+ (is (= (m/drop-upto zero? [1 2 3 0 4 5 6]) [4 5 6]))
+ (is (= (m/drop-upto zero? [0 1 2 3 4 5 6]) [1 2 3 4 5 6]))
+ (is (= (m/drop-upto zero? [1 2 3 4 5 6 7]) [])))
+
+ (testing "transducers"
+ (is (= (into [] (m/drop-upto zero?) [1 2 3 0 4 5 6]) [4 5 6]))
+ (is (= (into [] (m/drop-upto zero?) [0 1 2 3 4 5 6]) [1 2 3 4 5 6]))
+ (is (= (into [] (m/drop-upto zero?) [1 2 3 4 5 6 7]) []))))
+
+(deftest test-indexed
+ (testing "sequences"
+ (is (= (m/indexed [:a :b :c :d])
+ [[0 :a] [1 :b] [2 :c] [3 :d]]))
+ (is (= (m/indexed [])
+ [])))
+
+ (testing "transducers"
+ (is (= (into [] (m/indexed) [:a :b :c :d])
+ [[0 :a] [1 :b] [2 :c] [3 :d]]))
+ (is (= (into [] (m/indexed) [])
+ []))))
+
+(deftest test-insert-nth
+ (testing "sequences"
+ (is (= (m/insert-nth 0 :a [1 2 3 4]) [:a 1 2 3 4]))
+ (is (= (m/insert-nth 1 :a [1 2 3 4]) [1 :a 2 3 4]))
+ (is (= (m/insert-nth 3 :a [1 2 3 4]) [1 2 3 :a 4]))
+ (is (= (m/insert-nth 4 :a [1 2 3 4]) [1 2 3 4 :a])))
+
+ (testing "transducers"
+ (is (= (into [] (m/insert-nth 0 :a) [1 2 3 4]) [:a 1 2 3 4]))
+ (is (= (into [] (m/insert-nth 1 :a) [1 2 3 4]) [1 :a 2 3 4]))
+ (is (= (into [] (m/insert-nth 3 :a) [1 2 3 4]) [1 2 3 :a 4]))
+ (is (= (into [] (m/insert-nth 4 :a) [1 2 3 4]) [1 2 3 4 :a]))))
+
+(deftest test-remove-nth
+ (testing "sequences"
+ (is (= (m/remove-nth 0 [1 2 3 4]) [2 3 4]))
+ (is (= (m/remove-nth 1 [1 2 3 4]) [1 3 4]))
+ (is (= (m/remove-nth 3 [1 2 3 4]) [1 2 3])))
+
+ (testing "transducers"
+ (is (= (into [] (m/remove-nth 0) [1 2 3 4]) [2 3 4]))
+ (is (= (into [] (m/remove-nth 1) [1 2 3 4]) [1 3 4]))
+ (is (= (into [] (m/remove-nth 3) [1 2 3 4]) [1 2 3]))))
+
+(deftest test-replace-nth
+ (testing "sequences"
+ (is (= (m/replace-nth 0 :a [1 2 3 4]) [:a 2 3 4]))
+ (is (= (m/replace-nth 1 :a [1 2 3 4]) [1 :a 3 4]))
+ (is (= (m/replace-nth 3 :a [1 2 3 4]) [1 2 3 :a])))
+
+ (testing "transducers"
+ (is (= (into [] (m/replace-nth 0 :a) [1 2 3 4]) [:a 2 3 4]))
+ (is (= (into [] (m/replace-nth 1 :a) [1 2 3 4]) [1 :a 3 4]))
+ (is (= (into [] (m/replace-nth 3 :a) [1 2 3 4]) [1 2 3 :a]))))
+
+(deftest test-abs
+ (is (= (m/abs -3) 3))
+ (is (= (m/abs 2) 2))
+ (is (= (m/abs -2.1) 2.1))
+ (is (= (m/abs 1.8) 1.8))
+ #?@(:clj [(is (= (m/abs -1/3) 1/3))
+ (is (= (m/abs 1/2) 1/2))
+ (is (= (m/abs 3N) 3N))
+ (is (= (m/abs -4N) 4N))]))
+
+(deftest test-deref-swap!
+ (let [a (atom 0)]
+ (is (= (m/deref-swap! a inc) 0))
+ (is (= @a 1))
+ (is (= (m/deref-swap! a inc) 1))
+ (is (= @a 2))))
+
+(deftest test-deref-reset!
+ (let [a (atom 0)]
+ (is (= (m/deref-reset! a 3) 0))
+ (is (= @a 3))
+ (is (= (m/deref-reset! a 1) 3))
+ (is (= @a 1))))
+
+(deftest test-ex-message
+ (is (= (m/ex-message (ex-info "foo" {})) "foo"))
+ (is (= (m/ex-message (new #?(:clj Exception :cljs js/Error) "bar")) "bar")))
+
+(deftest test-ex-cause
+ (let [cause (new #?(:clj Exception :cljs js/Error) "foo")]
+ (is (= (m/ex-cause (ex-info "foo" {} cause)) cause))
+ #?(:clj (is (= (m/ex-cause (Exception. "foo" cause)) cause)))))
+
+(deftest test-uuid?
+ (let [x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6"]
+ (is (m/uuid? x))
+ (is (not (m/uuid? 2)))
+ (is (not (m/uuid? (str x))))
+ (is (not (m/uuid? nil)))))
+
+(deftest test-uuid
+ (let [x (m/uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6")]
+ (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x))
+ (is (= x #uuid "d1a4adfa-d9cf-4aa5-9f05-a15365d1bfa6"))))
+
+(deftest test-random-uuid
+ (let [x (m/random-uuid)
+ y (m/random-uuid)]
+ (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) x))
+ (is (instance? #?(:clj java.util.UUID :cljs cljs.core.UUID) y))
+ (is (not= x y))))
+
+;; BB-TEST-PATCH: Not available yet for latest maven release
+#_(deftest test-regexp?
+ (is (m/regexp? #"x"))
+ (is (not (m/regexp? "x")))
+ (is (not (m/regexp? nil))))
diff --git a/test-resources/lib_tests/medley/test_runner.cljs b/test-resources/lib_tests/medley/test_runner.cljs
new file mode 100644
index 00000000..4cc95109
--- /dev/null
+++ b/test-resources/lib_tests/medley/test_runner.cljs
@@ -0,0 +1,5 @@
+(ns medley.test-runner
+ (:require [doo.runner :refer-macros [doo-tests]]
+ [medley.core-test]))
+
+(doo-tests 'medley.core-test)
diff --git a/test-resources/lib_tests/minimallist/core_test.cljc b/test-resources/lib_tests/minimallist/core_test.cljc
index d86176f1..1faaaa23 100644
--- a/test-resources/lib_tests/minimallist/core_test.cljc
+++ b/test-resources/lib_tests/minimallist/core_test.cljc
@@ -40,6 +40,16 @@
[1]
[2]
+ (-> (h/fn int?)
+ (h/with-condition (h/fn odd?)))
+ [1]
+ [2]
+
+ (-> (h/fn symbol?)
+ (h/with-condition (h/fn (complement #{'if 'val}))))
+ ['a]
+ ['if]
+
;; enum
(h/enum #{1 "2" :3})
[1 "2" :3]
@@ -79,8 +89,7 @@
{:a 1, :b 'bar, [1 2 3] "soleil !"}]
;; map, keys and values
- (h/map-of (h/fn keyword?)
- (h/fn int?))
+ (h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
[{} {:a 1, :b 2}]
[{:a 1, :b "2"} [[:a 1] [:b 2]] {true 1, false 2}]
@@ -89,6 +98,10 @@
['(1 2 3) [1 2 3] `(1 2 ~3)]
['(1 :a) #{1 2 3} {:a 1, :b 2, :c 3}]
+ (h/sequence-of (h/fn char?))
+ ["" "hi" "hello"]
+ [[1 2 3]]
+
;; sequence, with condition
(-> (h/sequence-of (h/fn int?))
(h/with-condition (h/fn (fn [coll] (= coll (reverse coll))))))
@@ -97,29 +110,38 @@
;; sequence as a list
(h/list-of (h/fn int?))
- ['(1 2 3)]
- ['(1 :a) [1 2 3] #{1 2 3}
- #_`(1 2 ~3)] ; this is not a list in cljs
+ ['(1 2 3) `(1 2 ~3)]
+ ['(1 :a) [1 2 3] #{1 2 3}]
;; sequence as a vector
(h/vector-of (h/fn int?))
[[1 2 3]]
[[1 :a] '(1 2 3) #{1 2 3} `(1 2 ~3)]
+ ;; sequence as a string
+ (h/string-of (h/enum (set "0123456789abcdef")))
+ ["03ab4c" "cafe"]
+ ["coffee" [1 :a] '(1 2 3) #{1 2 3} `(1 2 ~3)]
+
;; sequence with size specified using a model
(-> (h/sequence-of (h/fn any?))
(h/with-count (h/enum #{2 3})))
- ['(1 2) [1 "2"] `(1 ~"2") [1 "2" :3]]
- [#{1 "a"} [1 "2" :3 :4]]
+ ['(1 2) [1 "2"] `(1 ~"2") [1 "2" :3] "hi"]
+ [#{1 "a"} [1 "2" :3 :4] "hello"]
;; sequence with entries (fixed size is implied)
(h/tuple (h/fn int?) (h/fn string?))
['(1 "2") [1 "2"] `(1 ~"2")]
[#{1 "a"} [1 "2" :3]]
+ ;; sequence with entries in a string
+ (h/string-tuple (h/val \a) (h/enum #{\b \c}))
+ ["ab" "ac"]
+ [[\a \b] #{\a \b}]
+
;; alt
(h/alt [:int (h/fn int?)]
- [:strings (h/cat (h/fn string?))])
+ [:strings (h/vector-of (h/fn string?))])
[1 ["1"]]
[[1] "1" :1 [:1]]
@@ -144,6 +166,12 @@
[[1 "2" 3] [1 :2 3] [1 ["a" :b] 3]]
[[1 "a" :b 3]]
+ ;; cat & repeat - a color string
+ (-> (h/cat (h/val \#)
+ (h/repeat 6 6 (h/enum (set "0123456789abcdefABCDEF")))))
+ ["#000000" "#af4Ea5"]
+ ["000000" "#cafe" "#coffee"]
+
;; cat of cat, the inner cat is implicitly inlined
(-> (h/cat (h/fn int?)
(h/cat (h/fn int?)))
@@ -163,15 +191,20 @@
[[] [1] [1 2] '() '(1) '(2 3)]
[[1 2 3] '(1 2 3)]
+ ;; repeat - inside a list
+ (h/in-list (h/repeat 0 2 (h/fn int?)))
+ ['() '(1) '(2 3)]
+ [[] [1] [1 2] [1 2 3] '(1 2 3)]
+
;; repeat - inside a vector
(h/in-vector (h/repeat 0 2 (h/fn int?)))
[[] [1] [1 2]]
[[1 2 3] '() '(1) '(2 3) '(1 2 3)]
- ;; repeat - inside a list
- (h/in-list (h/repeat 0 2 (h/fn int?)))
- ['() '(1) '(2 3)]
- [[] [1] [1 2] [1 2 3] '(1 2 3)]
+ ;; repeat - inside a string
+ (h/in-string (h/repeat 4 6 (h/fn char?)))
+ ["hello"]
+ ["" "hi" [] [1] '(1 2 3)]
;; repeat - min > 0
(h/repeat 2 3 (h/fn int?))
@@ -196,6 +229,16 @@
[[[1 "a"]] [[1 "a"] [2 "b"]] ['(1 "a") [2 "b"]]]
[[] [1] [1 2] [1 "a"] [1 "a" 2 "b"] [1 "a" 2 "b" 3 "c"]]
+ ;; char-cat & char-set
+ (-> (h/cat (h/char-cat "good")
+ (h/val \space)
+ (h/alt (h/char-cat "morning")
+ (h/char-cat "afternoon")
+ (h/repeat 3 10 (h/char-set "#?!@_*+%"))))
+ (h/in-string))
+ ["good morning" "good afternoon" "good #@*+?!"]
+ ["good" "good " "good day"]
+
;; let / ref
(h/let ['pos-even? (h/and (h/fn pos-int?)
(h/fn even?))]
@@ -236,14 +279,24 @@
[1 1 1 "hi" "hi" "hi"]]
[[1 1 "hi"]
[1 "hi" "hi"]
- [1 1 :no "hi" "hi"]]]]
+ [1 1 :no "hi" "hi"]]
+ ; let / ref - with shadowed local model
+ (h/let ['foo (h/ref 'bar)
+ 'bar (h/fn int?)]
+ (h/let ['bar (h/fn string?)]
+ (h/ref 'foo)))
+ [1]
+ ["hi"]]]
(doseq [[model valid-coll invalid-coll] (partition 3 test-data)]
(doseq [data valid-coll]
(is (valid? model data)))
(doseq [data invalid-coll]
- (is (not (valid? model data)))))))
+ (is (not (valid? model data))))))
+
+ (is (thrown? #?(:clj Exception :cljs js/Object)
+ (valid? (h/let [] (h/ref 'foo)) 'bar))))
(deftest describe-test
@@ -252,6 +305,16 @@
[1 1
2 :invalid]
+ (-> (h/fn int?)
+ (h/with-condition (h/fn odd?)))
+ [1 1
+ 2 :invalid]
+
+ (-> (h/fn symbol?)
+ (h/with-condition (h/fn (complement #{'if 'val}))))
+ ['a 'a
+ 'if :invalid]
+
;; enum
(h/enum #{1 "2" false nil})
[1 1
@@ -278,7 +341,7 @@
;; set
(h/set-of (h/fn int?))
- [#{1} #{1}]
+ [#{1 2} [1 2]]
;; map
(h/map [:a {:optional true} (h/fn int?)]
@@ -294,20 +357,29 @@
; extra entry
{:a 1, :b 2, :c 3} {:a 1, :b 2}]
- ;; map-of - :keys
- (h/map-of (h/fn keyword?) (h/fn any?))
- [{:a 1, :b 2} {:a 1, :b 2}
+ ;; map-of - entry-model
+ (h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
+ [{:a 1, :b 2} [[:a 1] [:b 2]]
{"a" 1} :invalid]
- ;; map-of - :values
- (h/map-of (h/fn any?) (h/fn int?))
- [{:a 1, "b" 2} {:a 1, "b" 2}
- {:a "1"} :invalid]
+ ;; map-of - real world use case
+ (h/map-of (h/alt [:symbol (h/vector (h/fn simple-symbol?) (h/fn keyword?))]
+ [:keys (h/vector (h/val :keys) (h/vector-of (h/fn symbol?)))]
+ [:as (h/vector (h/val :as) (h/fn simple-symbol?))]))
+ '[{first-name :first-name
+ last-name :last-name
+ :keys [foo bar]
+ :as foobar}
+ [[:symbol [first-name :first-name]]
+ [:symbol [last-name :last-name]]
+ [:keys [:keys [foo bar]]]
+ [:as [:as foobar]]]]
;; sequence - :elements-model
(h/sequence-of (h/fn int?))
[[1 2 3] [1 2 3]
'(1 2 3) '(1 2 3)
+ `(1 2 3) '(1 2 3)
[1 "2" 3] :invalid]
;; sequence - :elements-model with condition
@@ -319,7 +391,14 @@
;; sequence - :coll-type vector
(h/vector-of (h/fn any?))
[[1 2 3] [1 2 3]
- '(1 2 3) :invalid]
+ '(1 2 3) :invalid
+ `(1 2 3) :invalid]
+
+ ;; sequence - :coll-type list
+ (h/list-of (h/fn any?))
+ [[1 2 3] :invalid
+ '(1 2 3) '(1 2 3)
+ `(1 2 3) '(1 2 3)]
;; sequence - :entries
(h/tuple (h/fn int?) (h/fn string?))
@@ -327,16 +406,27 @@
[1 2] :invalid
[1] :invalid]
+ (h/tuple (h/fn int?)
+ [:text (h/fn string?)])
+ [[1 "a"] {:text "a"}]
+
+ (h/tuple [:number (h/fn int?)]
+ [:text (h/fn string?)])
+ [[1 "a"] {:number 1, :text "a"}]
+
;; sequence - :count-model
(-> (h/sequence-of (h/fn any?))
(h/with-count (h/val 3)))
[[1 2] :invalid
[1 2 3] [1 2 3]
- [1 2 3 4] :invalid]
+ [1 2 3 4] :invalid
+ "12" :invalid
+ "123" (into [] "123")
+ "1234" :invalid]
;; alt - not inside a sequence
(h/alt [:number (h/fn int?)]
- [:sequence (h/cat (h/fn string?))])
+ [:sequence (h/vector-of (h/fn string?))])
[1 [:number 1]
["1"] [:sequence ["1"]]
[1] :invalid
@@ -457,4 +547,7 @@
(doseq [[model data-description-pairs] (partition 2 test-data)]
(doseq [[data description] (partition 2 data-description-pairs)]
(is (= [data (describe model data)]
- [data description]))))))
+ [data description])))))
+
+ (is (thrown? #?(:clj Exception :cljs js/Object)
+ (describe (h/let [] (h/ref 'foo)) 'bar))))
diff --git a/test-resources/lib_tests/minimallist/generator_test.cljc b/test-resources/lib_tests/minimallist/generator_test.cljc
new file mode 100644
index 00000000..af9f8706
--- /dev/null
+++ b/test-resources/lib_tests/minimallist/generator_test.cljc
@@ -0,0 +1,546 @@
+(ns minimallist.generator-test
+ (:require [clojure.test :refer [deftest testing is are]]
+ [clojure.test.check.generators :as tcg]
+ [clojure.string :as str]
+ [minimallist.core :refer [valid?]]
+ [minimallist.helper :as h]
+ [minimallist.util :as util]
+ [minimallist.generator :as mg :refer [gen fn-any? fn-int? fn-string? fn-char?
+ fn-symbol? fn-simple-symbol? fn-qualified-symbol?
+ fn-keyword? fn-simple-keyword? fn-qualified-keyword?]]))
+
+(defn- path-test-visitor []
+ ;; Testing using side effects.
+ ;; A little ugly, but good enough for tests.
+ (let [paths (atom [])]
+ (fn
+ ([] @paths)
+ ([model stack path]
+ (swap! paths conj path)
+ model))))
+
+(deftest postwalk-visit-order-test
+ (are [model expected-paths]
+ (let [visitor (path-test-visitor)]
+ (mg/postwalk model visitor) ; Create side effects
+ (= (visitor) expected-paths)) ; Collect and compare the side effects
+
+ (h/let ['leaf (h/fn int?)
+ 'tree (h/ref 'leaf)]
+ (h/ref 'tree))
+ [[:bindings 'leaf]
+ [:bindings 'tree]
+ [:body]
+ []]
+
+ (h/let ['root (h/let ['leaf (h/fn int?)
+ 'tree (h/ref 'leaf)]
+ (h/ref 'tree))]
+ (h/ref 'root))
+ [[:bindings 'root :bindings 'leaf]
+ [:bindings 'root :bindings 'tree]
+ [:bindings 'root :body]
+ [:bindings 'root]
+ [:body]
+ []]
+
+ (h/let ['leaf (h/fn int?)
+ 'root (h/let ['tree (h/ref 'leaf)]
+ (h/ref 'tree))]
+ (h/ref 'root))
+ [[:bindings 'leaf]
+ [:bindings 'root :bindings 'tree]
+ [:bindings 'root :body]
+ [:bindings 'root]
+ [:body]
+ []]
+
+ ; test of no visit more than once
+ (h/let ['leaf (h/fn int?)
+ 'tree (h/tuple (h/ref 'leaf) (h/ref 'leaf))]
+ (h/ref 'tree))
+ [[:bindings 'leaf]
+ [:bindings 'tree :entries 0 :model]
+ [:bindings 'tree :entries 1 :model]
+ [:bindings 'tree]
+ [:body]
+ []]
+
+ ; test of no visit more than once, infinite loop otherwise
+ (h/let ['leaf (h/fn int?)
+ 'tree (h/tuple (h/ref 'tree) (h/ref 'leaf))]
+ (h/ref 'tree))
+ [[:bindings 'tree :entries 0 :model]
+ [:bindings 'leaf]
+ [:bindings 'tree :entries 1 :model]
+ [:bindings 'tree]
+ [:body]
+ []]
+
+ #__))
+
+(deftest assoc-leaf-distance-visitor-test
+ (are [model expected-walked-model]
+ (= (-> model
+ (mg/postwalk mg/assoc-leaf-distance-visitor)
+ (util/walk-map-dissoc :fn))
+ expected-walked-model)
+
+ ; Recursive data-structure impossible to generate
+ ; This one is trying to bring the generator function in an infinite loop.
+ (h/let ['loop (h/ref 'loop)]
+ (h/ref 'loop))
+ {:type :let
+ :bindings {'loop {:type :ref
+ :key 'loop}}
+ :body {:type :ref
+ :key 'loop}}
+
+ ; Recursive data-structure impossible to generate
+ (h/let ['leaf (h/fn int?)
+ 'tree (h/tuple (h/ref 'tree) (h/ref 'leaf))]
+ (h/ref 'tree))
+ {:type :let
+ :bindings {'leaf {:type :fn
+ ::mg/leaf-distance 0}
+ 'tree {:type :sequence
+ :entries [{:model {:type :ref
+ :key 'tree}}
+ {:model {:type :ref
+ :key 'leaf
+ ::mg/leaf-distance 1}}]}}
+ :body {:type :ref
+ :key 'tree}}
+
+ ; Recursive data-structure impossible to generate
+ (h/let ['rec-map (h/map [:a (h/fn int?)]
+ [:b (h/ref 'rec-map)])]
+ (h/ref 'rec-map))
+ {:type :let
+ :bindings {'rec-map {:type :map
+ :entries [{:key :a
+ :model {:type :fn
+ ::mg/leaf-distance 0}}
+ {:key :b
+ :model {:type :ref
+ :key 'rec-map}}]}}
+ :body {:type :ref
+ :key 'rec-map}}
+
+ ; Recursive data-structure which can be generated
+ (h/let ['leaf (h/fn int?)
+ 'tree (h/alt (h/ref 'tree) (h/ref 'leaf))]
+ (h/ref 'tree))
+ {:type :let
+ :bindings {'leaf {:type :fn
+ ::mg/leaf-distance 0}
+ 'tree {:type :alt
+ :entries [{:model {:type :ref
+ :key 'tree}}
+ {:model {:type :ref
+ :key 'leaf
+ ::mg/leaf-distance 1}}]
+ ::mg/leaf-distance 2}}
+ :body {:type :ref
+ :key 'tree
+ ::mg/leaf-distance 3}
+ ::mg/leaf-distance 4}
+
+ (h/let ['rec-map (h/map [:a (h/fn int?)]
+ [:b {:optional true} (h/ref 'rec-map)])]
+ (h/ref 'rec-map))
+ {:type :let
+ :bindings {'rec-map {:type :map
+ :entries [{:key :a
+ :model {:type :fn
+ ::mg/leaf-distance 0}}
+ {:key :b
+ :optional true
+ :model {:type :ref
+ :key 'rec-map}}]
+ ::mg/leaf-distance 1}}
+ :body {:type :ref
+ :key 'rec-map
+ ::mg/leaf-distance 2}
+ ::mg/leaf-distance 3}
+
+ #__))
+
+
+(deftest assoc-min-cost-visitor-test
+ (are [model expected-walked-model]
+ (= (-> model
+ (mg/postwalk mg/assoc-min-cost-visitor)
+ (util/walk-map-dissoc :fn))
+ expected-walked-model)
+
+ (h/tuple (h/fn int?) (h/fn string?))
+ {:type :sequence
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+
+ (h/cat (h/fn int?) (h/fn string?))
+ {:type :cat
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+
+ (h/in-vector (h/cat (h/fn int?) (h/fn string?)))
+ {:type :cat
+ :coll-type :vector
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+
+ (h/not-inlined (h/cat (h/fn int?) (h/fn string?)))
+ {:type :cat
+ :inlined false
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+
+ (h/map [:a (h/fn int?)]
+ [:b {:optional true} (h/fn int?)])
+ {:type :map
+ :entries [{:key :a
+ :model {:type :fn
+ ::mg/min-cost 1}}
+ {:key :b
+ :optional true
+ :model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 2}
+
+ (h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
+ {:type :map-of
+ :entry-model {:type :sequence
+ :coll-type :vector
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+ ::mg/min-cost 1}
+
+ (-> (h/map-of (h/vector (h/fn keyword?) (h/fn int?)))
+ (h/with-count (h/enum #{3 4})))
+ {:type :map-of
+ :entry-model {:type :sequence
+ :coll-type :vector
+ :entries [{:model {:type :fn
+ ::mg/min-cost 1}}
+ {:model {:type :fn
+ ::mg/min-cost 1}}]
+ ::mg/min-cost 3}
+ :count-model {:type :enum
+ :values #{3 4}}
+ ::mg/min-cost 7}
+
+ (h/set-of (h/fn any?))
+ {:type :set-of
+ :elements-model {:type :fn
+ ::mg/min-cost 1}
+ ::mg/min-cost 1}
+
+ (-> (h/set-of (h/fn any?))
+ (h/with-count (h/val 3)))
+ {:type :set-of
+ :elements-model {:type :fn
+ ::mg/min-cost 1}
+ :count-model {:type :enum
+ :values #{3}}
+ ::mg/min-cost 4}
+
+ (h/let ['foo (-> (h/set-of (h/fn int?))
+ (h/with-count (h/val 3)))]
+ (h/ref 'foo))
+ {:type :let
+ :bindings {'foo {:type :set-of
+ :count-model {:type :enum
+ :values #{3}}
+ :elements-model {:type :fn
+ ::mg/min-cost 1}
+ ::mg/min-cost 4}}
+ :body {:type :ref
+ :key 'foo
+ ::mg/min-cost 4}
+ ::mg/min-cost 4}
+
+ #__))
+
+(deftest budget-split-gen-test
+ (is (every? (fn [[a b c]]
+ (and (<= 0 a 5)
+ (<= 5 b 10)
+ (<= 10 c 15)))
+ (-> (#'mg/budget-split-gen 20.0 [0 5 10])
+ tcg/sample)))
+ (is (every? #(= % [5 10 10])
+ (-> (#'mg/budget-split-gen 20.0 [5 10 10])
+ tcg/sample)))
+ (is (every? empty?
+ (-> (#'mg/budget-split-gen 10.0 [])
+ tcg/sample))))
+
+(comment
+ ;; For occasional hand testing
+
+ (tcg/sample (gen (-> (h/set-of fn-any?)
+ (h/with-count (h/enum #{1 2 3 10}))
+ (h/with-condition (h/fn (comp #{1 2 3} count))))))
+
+ (tcg/sample (gen (h/map-of (h/vector fn-int? fn-simple-symbol?))))
+
+ (tcg/sample (gen (-> (h/map [:a fn-int?])
+ (h/with-optional-entries [:b fn-string?]))))
+
+ (tcg/sample (gen (h/sequence-of fn-int?)))
+
+ (tcg/sample (gen (h/tuple fn-int? fn-string?)))
+
+ (tcg/sample (gen (h/cat fn-int? fn-string?)))
+
+ (tcg/sample (gen (h/repeat 2 3 fn-int?)))
+
+ (tcg/sample (gen (h/repeat 2 3 (h/cat fn-int? fn-string?))))
+
+ (tcg/sample (gen (h/let ['int? fn-int?
+ 'string? fn-string?
+ 'int-string? (h/cat (h/ref 'int?) (h/ref 'string?))]
+ (h/repeat 2 3 (h/ref 'int-string?)))))
+
+ (tcg/sample (gen (-> (h/set-of fn-int?)
+ (h/with-condition (h/fn (fn [coll]
+ (or (empty? coll)
+ (some even? coll))))))))
+
+ (tcg/sample (gen (-> (h/set-of fn-any?)
+ (h/with-count (h/enum #{1 2 3 10}))
+ (h/with-condition (h/fn (comp #{1 2 3} count))))))
+
+ (tcg/sample (gen (h/let ['node (h/set-of (h/ref 'node))]
+ (h/ref 'node))))
+
+ (tcg/sample (gen (h/let ['node (h/map-of (h/vector fn-int? (h/ref 'node)))]
+ (h/ref 'node)) 50))
+
+ (tcg/sample (gen (h/let ['node (h/map-of (h/vector fn-keyword? (h/ref 'node)))]
+ (h/ref 'node)) 100) 1)
+
+ (tcg/sample (gen (h/map [:a fn-int?])))
+
+ (tcg/sample (gen (-> (h/map [:a fn-int?])
+ (h/with-optional-entries [:b fn-string?]))))
+
+ (tcg/sample (gen (h/cat (h/vector-of fn-int?)
+ (h/vector-of fn-int?)) 20))
+
+ (tcg/sample (gen (h/repeat 5 10 fn-int?)))
+
+ (tcg/sample (gen fn-symbol?))
+ (tcg/sample (gen fn-simple-symbol?))
+ (tcg/sample (gen fn-qualified-symbol?))
+
+ (tcg/sample (gen fn-keyword?))
+ (tcg/sample (gen fn-simple-keyword?))
+ (tcg/sample (gen fn-qualified-keyword?))
+
+ (tcg/sample (gen (-> (h/cat (h/char-cat "good")
+ (h/val \space)
+ (h/alt (h/char-cat "morning")
+ (h/char-cat "afternoon")
+ (h/repeat 3 10 (h/char-set "#?!@_*+%"))))
+ (h/in-string)))
+ 100)
+
+
+ #__)
+
+(deftest gen-test
+ (let [model fn-string?]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/enum #{:1 2 "3"})]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (-> (h/set-of fn-int?)
+ (h/with-condition (h/fn (fn [coll]
+ (or (empty? coll)
+ (some even? coll))))))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (-> (h/set-of fn-any?)
+ (h/with-count (h/enum #{1 2 3 10}))
+ (h/with-condition (h/fn (comp #{1 2 3} count))))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/map-of (h/vector fn-int? fn-string?))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (-> (h/map [:a fn-int?])
+ (h/with-optional-entries [:b fn-string?])
+ (h/with-entries [:c fn-int?])
+ (h/with-optional-entries [:d fn-string?]))
+ sample (tcg/sample (gen model) 100)]
+ (is (and (every? (partial valid? model) sample)
+ (every? (fn [element] (contains? element :a)) sample)
+ (some (fn [element] (contains? element :b)) sample)
+ (some (fn [element] (not (contains? element :b))) sample)
+ (every? (fn [element] (contains? element :c)) sample)
+ (some (fn [element] (contains? element :d)) sample)
+ (some (fn [element] (not (contains? element :d))) sample))))
+
+ (let [model (h/sequence-of fn-int?)]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/tuple fn-int? fn-string?)
+ sample (tcg/sample (gen model) 100)]
+ (is (and (every? (partial valid? model) sample)
+ (some list? sample)
+ (some vector? sample))))
+
+ (let [model (h/list fn-int? fn-string?)
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? list? sample))))
+
+ (let [model (h/vector fn-int? fn-string?)
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? vector? sample))))
+
+ (let [model (h/string-tuple fn-char? fn-char?)
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? string? sample))))
+
+ (let [model (h/in-list (h/cat fn-int? fn-string?))
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? list? sample))))
+
+ (let [model (h/in-vector (h/cat fn-int? fn-string?))
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? vector? sample))))
+
+ (let [model (h/in-string (h/cat fn-char? fn-char?))
+ sample (tcg/sample (gen model))]
+ (is (and (every? (partial valid? model) sample)
+ (every? string? sample))))
+
+ (let [model (h/alt fn-int? fn-string?)]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/cat fn-int? fn-string?)]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/repeat 2 3 fn-int?)]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/repeat 2 3 (h/cat fn-int? fn-string?))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/not-inlined (h/cat fn-int?))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/not-inlined (h/repeat 1 2 fn-int?))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ (let [model (h/let ['int? fn-int?
+ 'string? fn-string?
+ 'int-string? (h/cat (h/ref 'int?) (h/ref 'string?))]
+ (h/repeat 2 3 (h/ref 'int-string?)))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;; Budget-based limit on model choice.
+ (let [model (h/let ['tree (h/alt [:leaf fn-int?]
+ [:branch (h/vector (h/ref 'tree)
+ (h/ref 'tree))])]
+ (h/ref 'tree))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;; Budget-based limit on variable set size.
+ (let [model (h/let ['node (h/set-of (h/ref 'node))]
+ (h/ref 'node))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;; Budget-based limit on variable sequence size.
+ (let [model (h/let ['node (h/vector-of (h/ref 'node))]
+ (h/ref 'node))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;; Budget-based limit on variable map size.
+ (let [model (h/let ['node (h/map-of (h/vector fn-int? (h/ref 'node)))]
+ (h/ref 'node))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;; Budget-based limit on optional entries in a map.
+ (let [model (h/let ['node (-> (h/map [:a fn-int?])
+ (h/with-optional-entries [:x (h/ref 'node)]
+ [:y (h/ref 'node)]
+ [:z (h/ref 'node)]))]
+ (h/ref 'node))]
+ (is (every? (partial valid? model)
+ (tcg/sample (gen model)))))
+
+ ;;; Budget-based limit on number of occurrences in a repeat.
+ ;(let [model (h/let ['node (h/repeat 0 1 (h/ref 'node))]
+ ; (h/ref 'node))]
+ ; (is (every? (partial valid? model)
+ ; (tcg/sample (gen model)))))
+
+ ;; Model impossible to generate.
+ (let [model (h/let ['node (h/map [:a (h/ref 'node)])]
+ (h/ref 'node))]
+ (is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
+
+ ;; Model impossible to generate.
+ (let [model (h/let ['node (h/tuple (h/ref 'node))]
+ (h/ref 'node))]
+ (is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
+
+ ;; Model impossible to generate.
+ (let [model (h/let ['node (h/cat (h/ref 'node))]
+ (h/ref 'node))]
+ (is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
+
+ ;; Model impossible to generate.
+ (let [model (h/let ['node (h/cat (h/ref 'node))]
+ (h/ref 'node))]
+ (is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model)))))
+
+ (let [model (h/let ['node (h/repeat 1 2 (h/ref 'node))]
+ (h/ref 'node))]
+ (is (thrown? #?(:clj Exception :cljs js/Object) (tcg/sample (gen model))))))
+
+;; TODO: [later] reuse the cat-ipsum model for parsing the output.
+
+;; TODO: in the :alt node, introduce a property :occurrence for the generator.
+;; TODO: generate models, use them to generate data, should not stack overflow.
diff --git a/test-resources/lib_tests/minimallist/util_test.cljc b/test-resources/lib_tests/minimallist/util_test.cljc
new file mode 100644
index 00000000..3a0fbd0f
--- /dev/null
+++ b/test-resources/lib_tests/minimallist/util_test.cljc
@@ -0,0 +1,53 @@
+(ns minimallist.util-test
+ (:require [clojure.test :refer [deftest testing is are]]
+ [minimallist.util :as util]
+ [minimallist.helper :as h]))
+
+(deftest reduce-update-test
+ (let [m {:a 1
+ :b 5}
+ f (fn [acc elm]
+ (let [elm10 (* elm 10)]
+ [(conj acc elm10) elm10]))]
+ (is (= (-> [[] m]
+ (util/reduce-update :a f)
+ (util/reduce-update :b f))
+ [[10 50] {:a 10, :b 50}]))))
+
+(deftest reduce-update-in-test
+ (let [m {:a {:x 1, :y 2}
+ :b [3 4 5]}
+ f (fn [acc elm]
+ (let [elm10 (* elm 10)]
+ [(conj acc elm10) elm10]))]
+ (is (= (-> [[] m]
+ (util/reduce-update-in [:a :x] f)
+ (util/reduce-update-in [:b 2] f))
+ [[10 50] {:a {:x 10, :y 2}, :b [3 4 50]}]))))
+
+(deftest reduce-mapv
+ (let [m {:a {:x 1, :y 2}
+ :b [3 4 5]}
+ f (fn [acc elm]
+ (let [elm10 (* elm 10)]
+ [(conj acc elm10) elm10]))]
+ (is (= (util/reduce-update [[] m] :b (partial util/reduce-mapv f))
+ [[30 40 50] {:a {:x 1, :y 2}, :b [30 40 50]}]))))
+
+(deftest iterate-while-different-test
+ (let [inc-up-to-10 (fn [x] (cond-> x (< x 10) inc))]
+ (is (= (util/iterate-while-different inc-up-to-10 0 0) 0))
+ (is (= (util/iterate-while-different inc-up-to-10 0 5) 5))
+ (is (= (util/iterate-while-different inc-up-to-10 0 10) 10))
+ (is (= (util/iterate-while-different inc-up-to-10 0 15) 10))
+
+ (is (= (util/iterate-while-different inc-up-to-10 7 2) 9))
+ (is (= (util/iterate-while-different inc-up-to-10 7 3) 10))
+ (is (= (util/iterate-while-different inc-up-to-10 7 4) 10))
+
+ (is (= (util/iterate-while-different inc-up-to-10 12 0) 12))
+ (is (= (util/iterate-while-different inc-up-to-10 12 3) 12))
+
+ (is (= (util/iterate-while-different inc-up-to-10 0 ##Inf) 10))
+ (is (= (util/iterate-while-different inc-up-to-10 10 ##Inf) 10))
+ (is (= (util/iterate-while-different inc-up-to-10 15 ##Inf) 15))))
diff --git a/test-resources/lib_tests/missing/test/assertions_test.cljc b/test-resources/lib_tests/missing/test/assertions_test.cljc
new file mode 100644
index 00000000..9eb4e9e0
--- /dev/null
+++ b/test-resources/lib_tests/missing/test/assertions_test.cljc
@@ -0,0 +1,12 @@
+(ns missing.test.assertions-test
+ (:require
+ [clojure.test :refer [deftest testing is] :as t]
+ [missing.test.old-methods]
+ [missing.test.assertions]))
+
+(deftest a-test
+ (testing "FIXME, I fail."
+ 1))
+
+(deftest another-test
+ (testing (is 1)))
diff --git a/test-resources/lib_tests/missing/test/old_methods.cljc b/test-resources/lib_tests/missing/test/old_methods.cljc
new file mode 100644
index 00000000..4ef98a0d
--- /dev/null
+++ b/test-resources/lib_tests/missing/test/old_methods.cljc
@@ -0,0 +1,13 @@
+(ns missing.test.old-methods
+ (:require [clojure.test :as t]
+ [missing.test.assertions :refer [register!]]))
+
+(defmethod t/report #?(:clj :begin-test-var
+ :cljs [::t/default :begin-test-var]) [_]
+ (println "Begin test var."))
+
+(defmethod t/report #?(:clj :end-test-var
+ :cljs [::t/default :end-test-var]) [_]
+ (println "End test var."))
+
+(register! {:throw? false})
diff --git a/test-resources/lib_tests/portal/bench.cljc b/test-resources/lib_tests/portal/bench.cljc
new file mode 100644
index 00000000..3263189c
--- /dev/null
+++ b/test-resources/lib_tests/portal/bench.cljc
@@ -0,0 +1,18 @@
+(ns portal.bench
+ #?(:cljs (:refer-clojure :exclude [simple-benchmark]))
+ #?(:cljs (:require-macros portal.bench)))
+
+(defn now []
+ #?(:clj (System/currentTimeMillis)
+ :cljs (.now js/Date)))
+
+(defmacro simple-benchmark
+ [bindings expr iterations & {:keys [print-fn] :or {print-fn 'println}}]
+ (let [expr-str (pr-str expr)]
+ `(let ~bindings
+ (dotimes [_# ~iterations] ~expr)
+ (let [start# (now)
+ ret# (dotimes [_# ~iterations] ~expr)
+ end# (now)
+ elapsed# (- end# start#)]
+ (~print-fn (str ~iterations " runs, " elapsed# " msecs, " ~expr-str))))))
diff --git a/test-resources/lib_tests/portal/e2e.clj b/test-resources/lib_tests/portal/e2e.clj
new file mode 100644
index 00000000..26597322
--- /dev/null
+++ b/test-resources/lib_tests/portal/e2e.clj
@@ -0,0 +1,32 @@
+(ns portal.e2e
+ (:require [portal.colors :as c]))
+
+(defn step [code]
+ (binding [*out* *err*]
+ (println "\n==> Enter to execute:" code "\n"))
+ (read-line)
+ (prn code))
+
+(def pane-titles '("Alice" "Mad Hatter" "The Cake is a Lie"))
+
+(defn options []
+ {:portal.colors/theme
+ (rand-nth (keys (dissoc c/themes ::c/vs-code-embedded)))
+ :portal.launcher/window-title
+ (rand-nth pane-titles)})
+
+(defn -main [& args]
+ (if (= (first args) "web")
+ (step '(require '[portal.web :as p]))
+ (step '(require '[portal.api :as p])))
+ (step `(do (add-tap #'p/submit)
+ (p/open ~(options))))
+ (step '(tap> :hello-world))
+ (step '(p/clear))
+ (step '(require '[examples.data :refer [data]]))
+ (step '(tap> data))
+ (step '(p/clear))
+ (step '(remove-tap #'p/submit))
+ (step '(tap> :hello-world))
+ (step '(p/eval-str "(js/alert 1)"))
+ (step '(p/close)))
diff --git a/test-resources/lib_tests/portal/jvm_test.clj b/test-resources/lib_tests/portal/jvm_test.clj
new file mode 100644
index 00000000..20553047
--- /dev/null
+++ b/test-resources/lib_tests/portal/jvm_test.clj
@@ -0,0 +1,23 @@
+(ns portal.jvm-test
+ (:require [clojure.test :refer [deftest is]]
+ [portal.api :as p]
+ [portal.runtime.browser :as browser]
+ [portal.runtime.index :as index]
+ [portal.runtime.jvm.client :as client]))
+
+(defn- headless-chrome-flags [url]
+ ["--headless" "--disable-gpu" url])
+
+(defn- open [f]
+ (with-redefs [browser/flags f] (p/open)))
+
+(deftest e2e-jvm
+ (reset! index/testing? true)
+ (when-let [portal (open headless-chrome-flags)]
+ (with-redefs [client/timeout 60000]
+ (reset! portal 0)
+ (is (= @portal 0))
+ (swap! portal inc)
+ (is (= @portal 1))))
+ (p/close))
+
diff --git a/test-resources/lib_tests/portal/runtime/cson_test.cljc b/test-resources/lib_tests/portal/runtime/cson_test.cljc
new file mode 100644
index 00000000..c7b5a4ce
--- /dev/null
+++ b/test-resources/lib_tests/portal/runtime/cson_test.cljc
@@ -0,0 +1,172 @@
+(ns portal.runtime.cson-test
+ (:require [clojure.test :refer [deftest are is]]
+ #?(:clj [clojure.edn :as edn]
+ :cljs [cljs.reader :as edn])
+ [cognitect.transit :as transit]
+ [portal.bench :as b]
+ [portal.runtime.cson :as cson])
+ #?(:clj (:import [java.io ByteArrayOutputStream ByteArrayInputStream]
+ [java.util Date]
+ [java.util UUID])))
+
+(defn- transit-read [^String string]
+ #?(:clj (-> string
+ .getBytes
+ ByteArrayInputStream.
+ (transit/reader :json)
+ transit/read)
+ :cljs (transit/read (transit/reader :json) string)))
+
+(defn- transit-write [value]
+ #?(:clj (let [out (ByteArrayOutputStream. 1024)]
+ (transit/write
+ (transit/writer out :json {:transform transit/write-meta})
+ value)
+ (.toString out))
+ :cljs (transit/write
+ (transit/writer :json {:transform transit/write-meta})
+ value)))
+
+(defn pass [v]
+ (cson/read (cson/write v)))
+
+(deftest simple-values
+ (are [value]
+ (= value (pass value))
+ nil
+ 0
+ 1.0
+ #?(:clj 42N
+ :cljs (when (exists? js/BigInt)
+ (js/BigInt "42")))
+ \newline
+ true
+ false
+ 'hello
+ 'hello/world
+ :hello
+ :hello/world
+ ""
+ "hello"
+ "hello/world"))
+
+(deftest escape-strings
+ (are [value]
+ (= value (pass value))
+ "\n"
+ "\""
+ " \"hello\" "))
+
+(deftest basic-collections
+ (are [value]
+ (= value (pass value))
+ []
+ [1 2 3]
+ {}
+ {:a :b}
+ #{}
+ #{1 2 3}
+ '()
+ (list 1 2 3)))
+
+(def composite-value
+ ['hello
+ 'hello/world
+ '(1 2 3)
+ ""
+ 3.14
+ true
+ false
+ #inst "2021-04-07T22:43:59.393-00:00"
+ #uuid "1d80bdbb-ab16-47b2-a8bd-068f94950248"
+ nil
+ 1
+ \h
+ "data"
+ {:hello/world :grep}
+ #{1 2 3}])
+
+(deftest composite-collections
+ (are [value]
+ (= value (pass value))
+ [[[]]]
+ #{#{#{}}}
+ {{} {}}
+ {[] []}
+ {#{} #{}}
+ {(list) (list)}
+ (list [] #{} {})
+ composite-value))
+
+(deftest special-collections
+ (are [value]
+ (= value (pass value))
+ (range 10)))
+
+(deftest seq-collections
+ (are [value]
+ (= (seq value) (pass (seq value)))
+ '(0)
+ [0]
+ #{0}
+ {0 0}))
+
+(def tagged
+ [#?(:clj (Date.)
+ :cljs (js/Date.))
+ #?(:clj (UUID/randomUUID)
+ :cljs (random-uuid))
+ (tagged-literal 'tag :value)])
+
+(deftest tagged-objects
+ (doseq [value tagged]
+ (is (= value (pass value)))))
+
+(deftest metadata
+ (doseq [value ['hello {} [] #{}]]
+ (let [m {:my :meta}
+ value (with-meta value m)]
+ (is (= m (meta (pass value)))))))
+
+(deftest symbol-key-with-meta
+ (let [m {:a :b}
+ value {(with-meta 'k m) 'v}]
+ (is (= value (pass value)))
+ (is (= m (meta (first (keys (pass value))))))))
+
+(deftest cson-over-edn
+ (is
+ (-> composite-value
+ (cson/write {:stringify pr-str})
+ (cson/read {:parse edn/read-string})
+ (= composite-value))))
+
+(def n 10000)
+(def v composite-value)
+
+(def edn
+ {:parse edn/read-string
+ :stringify pr-str})
+
+(comment
+ (deftest rich-benchmark
+ (b/simple-benchmark [] (transit-write v) n)
+ (b/simple-benchmark [] (cson/write v edn) n)
+ (b/simple-benchmark [] (cson/write v) n)
+
+ (prn)
+
+ (b/simple-benchmark
+ [v (transit-write v)] (transit-read v) n)
+ (b/simple-benchmark
+ [v (cson/write v edn)] (cson/read v edn) n)
+ (b/simple-benchmark
+ [v (cson/write v)] (cson/read v) n)))
+
+#?(:clj
+ (deftest java-longs
+ (is (= 1 (byte 1) (pass (byte 1))))
+ (is (= 1 (short 1) (pass (short 1))))
+ (is (= 1 (int 1) (pass (int 1))))
+ (is (= 1 (long 1) (pass (long 1))))
+ (is (= 4611681620380904123 (pass 4611681620380904123)))))
diff --git a/test-resources/lib_tests/portal/runtime/fs_test.cljc b/test-resources/lib_tests/portal/runtime/fs_test.cljc
new file mode 100644
index 00000000..96c1bef2
--- /dev/null
+++ b/test-resources/lib_tests/portal/runtime/fs_test.cljc
@@ -0,0 +1,21 @@
+(ns portal.runtime.fs-test
+ (:require [clojure.test :refer [deftest is]]
+ [portal.runtime.fs :as fs]))
+
+(deftest fs
+ (is (some? (fs/slurp "deps.edn")))
+ (let [deps (fs/join (fs/cwd) "deps.edn")]
+ (is (= (fs/exists deps) deps)))
+ (is (some? (fs/home)))
+ (is (some? (seq (fs/paths))))
+ (is (contains?
+ (into #{} (fs/list (fs/cwd)))
+ (fs/join (fs/cwd) "deps.edn")))
+ (let [dir (str "target/" (gensym))
+ file (str dir "/" (gensym))]
+ (fs/mkdir dir)
+ (fs/spit file "hello")
+ (is (= (fs/slurp file) "hello"))
+ (fs/rm dir)
+ (is (nil? (fs/exists file)))
+ (is (nil? (fs/exists dir)))))
diff --git a/test-resources/lib_tests/portal/test_planck.cljs b/test-resources/lib_tests/portal/test_planck.cljs
new file mode 100644
index 00000000..3e7769a9
--- /dev/null
+++ b/test-resources/lib_tests/portal/test_planck.cljs
@@ -0,0 +1,11 @@
+(ns portal.test-planck
+ (:require [cljs.test :refer [run-tests]]
+ [planck.core :refer [exit]]
+ [portal.runtime.cson-test]))
+
+(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
+ (when-not (cljs.test/successful? m)
+ (exit 1)))
+
+(defn -main []
+ (run-tests 'portal.runtime.cson-test))
diff --git a/test-resources/lib_tests/portal/test_runner.clj b/test-resources/lib_tests/portal/test_runner.clj
new file mode 100644
index 00000000..d6c1fb71
--- /dev/null
+++ b/test-resources/lib_tests/portal/test_runner.clj
@@ -0,0 +1,14 @@
+(ns portal.test-runner
+ (:require [clojure.test :refer [run-tests]]
+ [portal.jvm-test]
+ [portal.runtime.cson-test]
+ [portal.runtime.fs-test]))
+
+(defn -main []
+ (let [{:keys [fail error]}
+ (run-tests 'portal.jvm-test
+ 'portal.runtime.cson-test
+ 'portal.runtime.fs-test)]
+ (shutdown-agents)
+ (System/exit (+ fail error))))
+
diff --git a/test-resources/lib_tests/portal/test_runner.cljs b/test-resources/lib_tests/portal/test_runner.cljs
new file mode 100644
index 00000000..e4d4d30e
--- /dev/null
+++ b/test-resources/lib_tests/portal/test_runner.cljs
@@ -0,0 +1,14 @@
+(ns portal.test-runner
+ (:require [cljs.test :refer [run-tests]]
+ [portal.runtime.cson-test]
+ [portal.runtime.fs-test]))
+
+(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
+ (when-not (cljs.test/successful? m)
+ (.exit js/process 1)))
+
+(defn -main []
+ (run-tests 'portal.runtime.cson-test
+ 'portal.runtime.fs-test))
+
+(-main)
diff --git a/test-resources/lib_tests/progrock/core_test.clj b/test-resources/lib_tests/progrock/core_test.clj
new file mode 100644
index 00000000..4b12f6ac
--- /dev/null
+++ b/test-resources/lib_tests/progrock/core_test.clj
@@ -0,0 +1,43 @@
+(ns progrock.core-test
+ (:require [clojure.test :refer :all]
+ [clojure.string :as str]
+ [progrock.core :as pr]))
+
+(deftest test-progress-bar
+ (let [bar (pr/progress-bar 50)]
+ (is (= (:total bar) 50))
+ (is (= (:progress bar) 0))
+ (is (not (:done? bar)))))
+
+(deftest test-tick
+ (let [bar (pr/progress-bar 50)]
+ (is (= (-> bar pr/tick :progress) 1))
+ (is (= (-> bar (pr/tick 16) :progress) 16))
+ (is (= (-> bar (pr/tick 5) pr/tick :progress) 6))))
+
+(deftest test-done
+ (let [bar (pr/progress-bar 50)]
+ (is (-> bar pr/done :done?))))
+
+(deftest test-render
+ (let [bar (pr/progress-bar 50)]
+ (is (= (pr/render bar)
+ " 0/50 0% [ ] ETA: --:--"))
+ (is (= (pr/render (pr/tick bar 25))
+ "25/50 50% [========================= ] ETA: 00:00"))
+ (is (= (pr/render (pr/tick bar 25) {:format "(:bar)", :length 10})
+ "(===== )"))
+ (is (= (pr/render (pr/tick bar 25) {:format "[:bar]", :complete \#, :incomplete \-})
+ "[#########################-------------------------]"))
+ (is (= (pr/render (pr/progress-bar 0))
+ "0/0 0% [ ] ETA: --:--"))))
+
+(deftest test-print
+ (let [bar (pr/progress-bar 50)]
+ (is (= (with-out-str (pr/print bar))
+ "\r 0/50 0% [ ] ETA: --:--"))
+ (is (= (with-out-str (pr/print bar {:length 10}))
+ "\r 0/50 0% [ ] ETA: --:--"))
+ ;; BB-TEST-PATCH: Make windows compatible
+ (is (= (str/trim (with-out-str (pr/print (pr/done bar) {:length 10})))
+ "0/50 0% [ ] ETA: --:--"))))
diff --git a/test-resources/lib_tests/slingshot/slingshot_test.clj b/test-resources/lib_tests/slingshot/slingshot_test.clj
index 09938332..f787a862 100644
--- a/test-resources/lib_tests/slingshot/slingshot_test.clj
+++ b/test-resources/lib_tests/slingshot/slingshot_test.clj
@@ -74,18 +74,19 @@
(mega-try (throw+ exception-1))
(mega-try (throw exception-1))
(try (throw+ exception-1)
- (catch Exception e [:class-exception e]))
+ (catch Exception e [:class-exception e]))
(try (throw exception-1)
- (catch Exception e [:class-exception e])))))
+ (catch Exception e [:class-exception e])))))
(testing "IllegalArgumentException thrown by clojure/core"
(is (= :class-iae (first (mega-try (str/replace "foo" 1 1)))))))
(testing "catch by java class generically"
(is (= [:class-string "fail"] (mega-try (throw+ "fail")))))
+ ;; BB-TEST-PATCH: bb has different record internals
#_(testing "catch by clojure record type"
- (is (= [:class-exception-record exception-record-1]
- (mega-try (throw+ exception-record-1)))))
+ (is (= [:class-exception-record exception-record-1]
+ (mega-try (throw+ exception-record-1)))))
(testing "catch by key is present"
(is (= [:key-is-present #{:a-key}] (mega-try (throw+ #{:a-key})))))
diff --git a/test-resources/lib_tests/slingshot/support_test.clj b/test-resources/lib_tests/slingshot/support_test.clj
index 12d2c142..9618bd73 100644
--- a/test-resources/lib_tests/slingshot/support_test.clj
+++ b/test-resources/lib_tests/slingshot/support_test.clj
@@ -46,9 +46,11 @@
(defn stack-trace-fn []
(stack-trace))
+;; BB-TEST-PATCH: Returns jdk.internal.reflect.DelegatingMethodAccessorImpl
+;; instead of what's expected
#_(deftest test-stack-trace
(let [{:keys [methodName className]} (-> (stack-trace-fn) first bean)]
- (is (= methodName "invoke"))
+ (is (.startsWith ^String methodName "invoke"))
(is (re-find #"stack_trace_fn" className))))
(deftest test-resolve-local
diff --git a/test-resources/lib_tests/table/core_test.clj b/test-resources/lib_tests/table/core_test.clj
index c2e609bf..336f85e1 100644
--- a/test-resources/lib_tests/table/core_test.clj
+++ b/test-resources/lib_tests/table/core_test.clj
@@ -310,7 +310,7 @@
")
(table-str [[1 2] [:c :d] [:a :b]] :sort true))))
-;; BB-TEST-PATCH: Intermittent failing test
+;; TODO: Intermittent failing test
#_(deftest test-table-with-sort-option-as-field-name
(is (=
(unindent
diff --git a/test-resources/lib_tests/testdoc/core_test.clj b/test-resources/lib_tests/testdoc/core_test.clj
new file mode 100644
index 00000000..c8f68b32
--- /dev/null
+++ b/test-resources/lib_tests/testdoc/core_test.clj
@@ -0,0 +1,133 @@
+(ns testdoc.core-test
+ (:require
+ [clojure.java.io :as io]
+ [clojure.test :as t]
+ [testdoc.core :as sut]))
+
+(defn- repl-styled-success-test-func
+ "foo bar
+
+ => (+ 1 2 3)
+ 6
+ => (+ 1 2
+ => 3 4)
+ 10
+ => *1
+ 10
+ => (inc *1)
+ 11"
+ [])
+
+(defn- code-first-styled-success-test-func
+ "foo bar
+
+ (+ 1 2 3)
+ ;; => 6
+
+ (+ 1 2
+ 3 4)
+ ;; => 10
+ *1
+ ;; => 10
+ (inc *1)
+ ;; => 11"
+ [])
+
+(defn- repl-styled-partial-success-test-func
+ "foo bar
+
+ => (+ 1 2 3)
+ 6
+ => (+ 1 2 3 4)
+ 999"
+ [])
+
+(defn- code-first-styled-partial-success-test-func
+ "foo bar
+
+ (+ 1 2 3)
+ ;; => 6
+ (+ 1 2 3 4)
+ ;; => 999"
+ [])
+
+(t/deftest testdoc-test
+ (t/testing "repl style"
+ (t/is (= [{:type :pass :expected 6 :actual 6}
+ {:type :pass :expected 10 :actual 10}
+ {:type :pass :expected 10 :actual 10}
+ {:type :pass :expected 11 :actual 11}]
+ (->> (sut/testdoc nil #'repl-styled-success-test-func)
+ (map #(select-keys % [:type :expected :actual]))
+ (sort-by :expected))))
+
+ (t/is (= [{:type :pass :expected 6 :actual 6}
+ {:type :fail :expected 999 :actual 10}]
+ (->> (sut/testdoc nil #'repl-styled-partial-success-test-func)
+ (map #(select-keys % [:type :expected :actual]))
+ (sort-by :expected)))))
+
+ (t/testing "code-first style"
+ (t/is (= [{:type :pass :expected 6 :actual 6}
+ {:type :pass :expected 10 :actual 10}
+ {:type :pass :expected 10 :actual 10}
+ {:type :pass :expected 11 :actual 11}]
+ (->> (sut/testdoc nil #'code-first-styled-success-test-func)
+ (map #(select-keys % [:type :expected :actual]))
+ (sort-by :expected))))
+
+ (t/is (= [{:type :pass :expected 6 :actual 6}
+ {:type :fail :expected 999 :actual 10}]
+ (->> (sut/testdoc nil #'code-first-styled-partial-success-test-func)
+ (map #(select-keys % [:type :expected :actual]))
+ (sort-by :expected))))))
+
+(t/deftest testdoc-unsupported-test
+ (let [[result :as results] (sut/testdoc nil 123)]
+ (t/is (= 1 (count results)))
+ (t/is (= :fail (:type result)))
+ (t/is (re-seq #"^Unsupported document:" (:message result)))))
+
+(defn plus
+ "Add a and b
+
+ => (plus 1 2)
+ 3
+ => (plus 2
+ => 3)
+ 5"
+ [a b]
+ (+ a b))
+
+(t/deftest plus-test
+ (t/is (testdoc #'plus)))
+
+(t/deftest plus-string-test
+ (t/is (testdoc "=> (require '[testdoc.core-test :as ct])
+ nil
+ => (ct/plus 1 2)
+ 3
+ => (ct/plus 2
+ => 3)
+ 5")))
+
+(t/deftest nil-value-test
+ (t/is (= [{:type :fail :message "(= 1 nil)" :expected nil :actual 1}]
+ (sut/testdoc nil "=> 1
+ nil"))))
+
+(t/deftest unresolved-symbol-test
+ (let [[err :as res] (sut/testdoc nil "
+ => (unresolved-fn 10)
+ 11")]
+ (t/is (= 1 (count res)))
+ (t/is (= :fail (:type err)))
+ (t/is (every? #(some? (get err %)) [:type :message :expected :actual]))
+ (t/is (= "(= (unresolved-fn 10) 11), [line: 2]" (:message err)))))
+
+(t/deftest debug-test
+ (with-out-str
+ (t/is (testdoc #'sut/debug))))
+
+(t/deftest README-test
+ (t/is (testdoc (slurp (io/file "README.md")))))
diff --git a/test-resources/lib_tests/testdoc/style/code_first_test.clj b/test-resources/lib_tests/testdoc/style/code_first_test.clj
new file mode 100644
index 00000000..f3747c9e
--- /dev/null
+++ b/test-resources/lib_tests/testdoc/style/code_first_test.clj
@@ -0,0 +1,26 @@
+(ns testdoc.style.code-first-test
+ (:require
+ [clojure.string :as str]
+ [clojure.test :as t]
+ [testdoc.style.code-first :as sut]))
+
+(defn- lines
+ [ls]
+ (str/join "\n" ls))
+
+(t/deftest parse-doc-test
+ (t/are [expected in] (= expected (sut/parse-doc (lines in)))
+ '[[a b]], ["a" ";; => b"]
+ '[[(a b) c]], ["(a" "b)" ";; => c"]
+ '[[(a b) c]], ["head" "(a" "b)" ";; => c"]
+ '[[a b] [c d]], ["a" ";; => b" "c" ";; => d"]
+ '[], ["a"]
+ '[[a b]], ["a" ";; => b" "c"]
+ '[[a b]], ["a" ";; => b" ";; => c"]
+ '[[a (b c)]], ["a" ";; => [b" ";; => c]"]))
+
+(t/deftest parse-doc-with-meta-test
+ (let [ret (sut/parse-doc (lines ["" "a" ";; => 6" "c" ";; => :d"]))]
+ (t/is (= '[[a 6] [c :d]] ret))
+ (t/is (= 2 (-> ret first meta :testdoc.string/line)))
+ (t/is (= 4 (-> ret second meta :testdoc.string/line)))))
diff --git a/test-resources/lib_tests/testdoc/style/repl_test.clj b/test-resources/lib_tests/testdoc/style/repl_test.clj
new file mode 100644
index 00000000..8952e314
--- /dev/null
+++ b/test-resources/lib_tests/testdoc/style/repl_test.clj
@@ -0,0 +1,25 @@
+(ns testdoc.style.repl-test
+ (:require
+ [clojure.string :as str]
+ [clojure.test :as t]
+ [testdoc.style.repl :as sut]))
+
+(defn- lines
+ [ls]
+ (str/join "\n" ls))
+
+(t/deftest parse-doc-test
+ (t/are [expected in] (= expected (sut/parse-doc (lines in)))
+ '[[a b]], ["=> a" "b"]
+ '[[(a b) c]], ["=> (a" "=> b)" "c"]
+ '[[a b] [c d]], ["=> a" "b" "=> c" "d"]
+ '[], ["=> a"]
+ '[[a b]], ["=> a" "b" "=> c"]
+ '[[a b]], ["=> a" "b" "c"]
+ '[[a (b c)]], ["=> a" "[b" "c]"]))
+
+(t/deftest parse-doc-with-meta-test
+ (let [ret (sut/parse-doc (lines ["" "=> a" "6" "=> c" ":d"]))]
+ (t/is (= '[[a 6] [c :d]] ret))
+ (t/is (= 2 (-> ret first meta :testdoc.string/line)))
+ (t/is (= 4 (-> ret second meta :testdoc.string/line)))))
diff --git a/test-resources/lib_tests/version_clj/compare_test.cljc b/test-resources/lib_tests/version_clj/compare_test.cljc
index c7ac75df..3eb0222f 100644
--- a/test-resources/lib_tests/version_clj/compare_test.cljc
+++ b/test-resources/lib_tests/version_clj/compare_test.cljc
@@ -70,4 +70,13 @@
;; Some more zero-extension Tests
"1-SNAPSHOT" "1.0-SNAPSHOT" 0
"1-alpha" "1-alpha0" 0
+
+ ;; Prefixed versions
+ "v1" "v1" 0
+ "v1" "v2" -1
+ "v1" "v1.1" -1
+ "v1.1" "v1.2" -1
+ "v1.1" "v2" -1
+ "alpaca1.0" "bermuda1.0" -1
))
+
diff --git a/test-resources/lib_tests/version_clj/split_test.cljc b/test-resources/lib_tests/version_clj/split_test.cljc
index 51cac090..54ae6ab2 100644
--- a/test-resources/lib_tests/version_clj/split_test.cljc
+++ b/test-resources/lib_tests/version_clj/split_test.cljc
@@ -35,6 +35,10 @@
"0.5.0-alpha.1" [[0 5 0] ["alpha" 1]]
"0.5.0-alpha.1" [[0 5 0] ["alpha" 1]]
"0.0.3-alpha.8+oryOS.15" [[0 0 3] ["alpha" [8 "+oryos"] 15]]
+ "v1" [["v" 1]]
+ "v1.1" [["v" [1 1]]]
+ "ver1" [["ver" 1]]
+ "ver1.1" [["ver" [1 1]]]
))
(deftest t-split-without-qualifiers
diff --git a/test-resources/lib_tests/version_clj/via_use_test.clj b/test-resources/lib_tests/version_clj/via_use_test.clj
index f75fa6bd..0d4e7526 100644
--- a/test-resources/lib_tests/version_clj/via_use_test.clj
+++ b/test-resources/lib_tests/version_clj/via_use_test.clj
@@ -6,6 +6,7 @@
(use 'version-clj.core)
+;; BB-TEST-PATCH: This test doesn't exist upstream
(deftest sanity-test
(is (= [[1 0 0] ["snapshot"]] (version->seq "1.0.0-SNAPSHOT")))
(is (= 0 (version-compare "1.0" "1.0.0")))