-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathassertions.cljc
142 lines (122 loc) · 4.83 KB
/
assertions.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
(ns untangled-spec.assertions
#?(:cljs
(:require-macros
[untangled-spec.assertions :refer [define-assert-exprs!]]))
(:require
#?(:clj [clojure.test])
cljs.test ;; contains multimethod in clojure file
[clojure.spec :as s]
#?(:clj [untangled-spec.impl.macros :as im])
[untangled-spec.spec :as us]))
(s/def ::arrow (comp #{"=>" "=fn=>" "=throws=>"} str))
(s/def ::behavior string?)
(s/def ::triple (s/cat
:actual ::us/any
:arrow ::arrow
:expected ::us/any))
(s/def ::block (s/cat
:behavior (s/? ::behavior)
:triples (s/+ ::triple)))
(s/def ::assertions (s/+ ::block))
(defn fn-assert-expr [msg [f arg :as form]]
`(let [arg# ~arg
result# (~f arg#)]
{:type (if result# :pass :fail)
:message ~msg :assert-type '~'exec
:actual arg# :expected '~f}))
(defn eq-assert-expr [msg [exp act :as form]]
`(let [act# ~act
exp# ~exp
result# (im/try-report ~msg (= exp# act#))]
{:type (if result# :pass :fail)
:message ~msg :assert-type '~'eq
:actual act# :expected exp#}))
(defn parse-criteria [[tag x]]
(case tag :sym {:ex-type x} x))
(defn check-error* [msg e & [ex-type regex fn fn-pr]]
(let [e-msg (or #?(:clj (.getMessage e) :cljs (.-message e)) (str e))]
(->> (cond
(some-> (ex-data e) :type (= ::internal))
{:type :error :extra e-msg
:actual e :expected "it to throw"}
(and ex-type (not= ex-type (type e)))
{:type :fail :actual (type e) :expected ex-type
:extra "exception did not match type"}
(and regex (not (re-find regex e-msg)))
{:type :fail :actual e-msg :expected (str regex)
:extra "exception's message did not match regex"}
(and fn (not (fn e)))
{:type :fail :actual e :expected fn-pr
:extra "checker function failed"}
:else {:type :pass :actual "act" :expected "exp"})
(merge {:message msg
:assert-type 'throws?
:throwable e}))))
(defn check-error [msg e criteria & [fn-pr]]
(apply check-error* msg e
((juxt :ex-type :regex :fn :fn-pr)
(assoc criteria :fn-pr fn-pr))))
(s/def ::ex-type symbol?)
(s/def ::regex ::us/regex)
(s/def ::fn ::us/any)
(s/def ::criteria
(s/or
:sym symbol?
:list (s/cat :ex-type ::ex-type :regex (s/? ::regex) :fn (s/? ::fn))
:map (s/keys :opt-un [::ex-type ::regex ::fn])))
(defn throws-assert-expr [msg [cljs? should-throw criteria]]
(let [criteria (parse-criteria (us/conform! ::criteria criteria))]
`(try ~should-throw
(throw (ex-info "Expected an error to be thrown!"
{:type ::internal :criteria ~criteria}))
(catch ~(if (not cljs?) (symbol "Throwable") (symbol "js" "Object"))
e# (check-error ~msg e# ~criteria)))))
(defn assert-expr [msg [disp-key & form]]
(case disp-key
= (eq-assert-expr msg form)
exec (fn-assert-expr msg form)
throws? (throws-assert-expr msg form)
{:type :fail :message msg :actual disp-key
:expected #{"exec" "eq" "throws?"}}))
(defn triple->assertion [cljs? {:keys [actual arrow expected]}]
(let [prefix (if cljs? "cljs.test" "clojure.test")
is (symbol prefix "is")
msg (str actual " " arrow " " expected)]
(case arrow
=>
`(~is (~'= ~expected ~actual)
~msg)
=fn=>
(let [checker expected
arg actual]
`(~is (~'exec ~checker ~arg)
~msg))
=throws=>
(let [should-throw actual
criteria expected]
`(~is (~'throws? ~cljs? ~should-throw ~criteria)
~msg))
(throw (ex-info "invalid arrow" {:arrow arrow})))))
(defn fix-conform [conformed-assertions]
;;see issue: #31
(if (vector? (second conformed-assertions))
(vec (cons (first conformed-assertions) (second conformed-assertions)))
conformed-assertions))
(defn block->asserts [cljs? {:keys [behavior triples]}]
(let [asserts (map (partial triple->assertion cljs?) triples)]
`(im/with-reporting ~(when behavior {:type :behavior :string behavior})
~@asserts)))
#?(:clj
(defmacro define-assert-exprs! []
(let [test-ns (im/if-cljs &env "cljs.test" "clojure.test")
do-report (symbol test-ns "do-report")
t-assert-expr (im/if-cljs &env cljs.test/assert-expr clojure.test/assert-expr)
do-assert-expr
(fn [args]
(let [[msg form] (cond-> args (im/cljs-env? &env) rest)]
`(~do-report ~(assert-expr msg form))))]
(defmethod t-assert-expr '= eq-ae [& args] (do-assert-expr args))
(defmethod t-assert-expr 'exec fn-ae [& args] (do-assert-expr args))
(defmethod t-assert-expr 'throws? throws-ae [& args] (do-assert-expr args))
nil)))
(define-assert-exprs!)