-
Notifications
You must be signed in to change notification settings - Fork 4
/
test.clj
149 lines (127 loc) · 5.06 KB
/
test.clj
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
143
144
145
146
147
148
149
(ns cuic.test
(:require [clojure.test :as t]
[clojure.pprint :refer [pprint]]
[clojure.string :as string]
[clojure.walk :refer [postwalk]]
[clojure.tools.logging :refer [debug]]
[clojure.java.io :as io]
[clojure.edn :as edn]
[cuic.core :refer [wait *browser* *config*]]
[cuic.impl.retry :as retry])
(:import (java.io File)
(cuic WaitTimeoutException AbortTestError)))
(declare matches-snapshot?)
(defn- sorted-map-keys [x]
(postwalk #(if (map? %) (into (sorted-map) %) %) x))
(defn- snapshots-root-dir ^File []
(io/file (:snapshot-dir *config*)))
(defn- sanitize-filename [filename]
(-> filename
(string/replace #"-" "_")
(string/replace #"[^\w.]+" "")))
(defn- snapshot-filename [id]
(sanitize-filename (name id)))
(defn- snapshot-dir [id]
(loop [[d & ds] (filter seq (string/split (or (namespace id) "") #"\."))
dir (snapshots-root-dir)]
(if d (recur ds (io/file dir (sanitize-filename d))) dir)))
(defn expected-file [id ext]
(io/file (snapshot-dir id) (str (snapshot-filename id) ".expected." ext)))
(defn actual-file [id ext]
(io/file (snapshot-dir id) (str (snapshot-filename id) ".actual." ext)))
(defn read-edn [^File f]
(if (.exists f)
(edn/read-string (slurp f))))
(defn- ensure-snapshot-dir! [id]
(let [dir (snapshot-dir id)
ignore (io/file (snapshots-root-dir) ".gitignore")]
(when-not (.exists dir)
(.mkdirs dir))
(when-not (.exists ignore)
(spit ignore "*.actual*\n"))))
(defn- test-snapshot [id actual predicate read write! ext]
(ensure-snapshot-dir! id)
(let [e-file (expected-file id ext)
a-file (actual-file id ext)
expected (read e-file)]
(if (.exists e-file)
(if-not (predicate expected actual)
(do (write! (io/file a-file) actual)
false)
(do (if (.exists a-file) (.delete a-file))
true))
(do (write! e-file actual)
(println " > Snapshot written : " (.getAbsolutePath e-file))
true))))
(defmacro -with-retry [f assertion-form]
`(let [report# (atom nil)
result# (with-redefs [t/do-report #(reset! report# %)]
(try
(retry/loop* ~f *browser* *config*)
(catch WaitTimeoutException e#
(if-some [cause# (.getCause e#)]
(throw cause#)
(.getActual e#)))))]
(some-> @report# (t/do-report))
(if (and (contains? #{:fail :error} (:type @report#))
(true? (:abort-on-failed-assertion *config*)))
(throw (AbortTestError. (str "Test was aborted due to failed assertion: " ~assertion-form))))
result#))
(defn -assert-snapshot [msg [match? id actual]]
`(let [id# (do ~id)
actual# (do ~actual)
expected# (read-edn (expected-file id# "edn"))
result# (~match? id# actual#)]
(if result#
(t/do-report
{:type :pass
:message ~msg
:expected '(~'matches-snapshot? ~id ~actual)
:actual (cons '~'= [expected# actual#])})
(t/do-report
{:type :fail
:message ~msg
:expected (cons '~'= [expected# actual#])
:actual (list '~'not (cons '~'= [expected# actual#]))}))
result#))
(defn -assert-with-retry [msg form]
`(try
~form
(catch Throwable t#
(t/do-report
{:type :error
:message ~msg
:expected ~(last form)
:actual t#}))))
(defmethod t/assert-expr 'matches-snapshot? [msg form]
(-assert-snapshot msg form))
(defmethod t/assert-expr `matches-snapshot? [msg form]
(-assert-snapshot msg form))
(defmethod t/assert-expr '-with-retry [msg form]
(-assert-with-retry msg form))
(defmethod t/assert-expr `-with-retry [msg form]
(-assert-with-retry msg form))
;; Public API
(defmacro is*
"Assertion macro that works like clojure.test/is but if the result value is
non-truthy or asserted expression throws a cuic exception, then expression
is re-tried until truthy value is received or timeout exceeds."
[form]
`(let [do-report# t/do-report]
(with-redefs [t/do-report #(if (instance? AbortTestError (:actual %))
(throw (:actual %))
(do-report# %))]
(t/is (-with-retry #(do ~(t/assert-expr nil form)) '~form)))))
(defn matches-snapshot?
"Tries to match the given actual data to the snapshot associated to the
given id (keyword). If snapshot does not exist, this function creates it.
All data that can be serialized with `pr-str` can be tested with snapshot
testing.
HINT: Use fully qualified keyword to distinguish snapshots from different
test namespaces. You can also share same snapshots by using same id."
[snapshot-id actual]
{:pre [(keyword? snapshot-id)]}
(let [predicate #(= %1 %2)
read read-edn
write! #(spit (io/file %1) (with-out-str (pprint (sorted-map-keys %2))))]
(test-snapshot snapshot-id actual predicate read write! "edn")))