/
sweet.clj
255 lines (210 loc) · 9.86 KB
/
sweet.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
(require 'midje.bootstrap)
(midje.bootstrap/bootstrap)
(ns ^{:doc "A TDD library for Clojure that supports top-down ('mockish') TDD,
encourages readable tests, provides a smooth migration path from
clojure.test, balances abstraction and concreteness, and strives for
graciousness."}
midje.sweet
(:require midje.config) ; This should load first.
;; The following lets us avoid a circular dependency. Sigh.
(:require midje.parsing.1-to-explicit-form.future-facts)
(:require [midje.parsing.util.core :refer :all]
[midje.production-mode :refer :all])
;; For immigration
(:require [midje.doc :as doc]
midje.checking.checkables
midje.checkers)
(:require [clojure.string :as str]
[such.ns :as ns]
[midje.util.pile :as pile]
[midje.util.exceptions :as exceptions]
[midje.util.ecosystem :as ecosystem]
[midje.parsing.util.future-variants :as future-variants]
[midje.parsing.util.error-handling :as error]
[midje.parsing.util.wrapping :as wrapping]
[pointer.core :as pointer]
[midje.parsing.0-to-fact-form.tabular :as parse-tabular]
[midje.parsing.0-to-fact-form.formulas :as parse-formulas]
[midje.parsing.1-to-explicit-form.facts :as parse-facts]
[midje.parsing.1-to-explicit-form.parse-background :as parse-background]
[midje.parsing.1-to-explicit-form.metadata :as parse-metadata]
[midje.parsing.1-to-explicit-form.metaconstants :as parse-metaconstants]
[midje.data.nested-facts :as nested-facts]
[midje.emission.api :as emit]
[clojure.pprint :as pprint]
[such.immigration :as immigrate]))
(immigrate/import-all-vars midje.parsing.arrow-symbols)
(immigrate/import-all-vars midje.checkers)
(def include-midje-checks
"True by default. If set to false, Midje checks are not
included into production code, whether compiled or loaded.
Note that the variable must be set, as with `alter-var-root`
or `def`, not bound, as with `binding`."
true)
(defonce
^{:doc "This variable is defunct. Use `include-midje-checks` instead."
:dynamic true}
*include-midje-checks* :original-truthy-value)
(set-validator! #'*include-midje-checks*
(fn [val]
(when-not (= val :original-truthy-value)
(emit/fail {:type :parse-error
:notes ["*include-midje-checks* is defunct. Use `include-midje-checks` instead."]
:position (pointer/compile-time-fallback-position)}))
true))
(defn- unfinished* [names]
(pile/macro-for [name names]
`(do
(defn ~name [& args#]
(let [pprint# (partial pprint/cl-format nil "~S")]
(throw (exceptions/user-error (format "#'%s has no implementation, but it was called like this:%s(%s %s)"
'~name ecosystem/line-separator '~name
(str/join " " (map pprint# args#)))))))
;; A reliable way of determining if an `unfinished` function has since been defined.
(alter-meta! (var ~name) assoc :midje/unfinished-fun ~name)
:ok)))
(defmacro unfinished
"Defines a list of names as functions that have no implementation yet. They will
throw Errors if ever called."
[& names] (unfinished* names))
(ns/defalias before parse-background/before)
(ns/defalias after parse-background/after)
(ns/defalias around parse-background/around)
(ns/defalias formula parse-formulas/formula)
(declare #^{:doc "A declaration of the provided form"} provided)
(when (doc/appropriate?)
(immigrate/import-vars [midje.doc
midje midje-facts midje-fact midje-checkers midje-defining-checkers
midje-prerequisites midje-arrows midje-setup midje-teardown
midje-configuration midje-print-level midje-print-levels
guide])
(doc/midje-notice))
(defmacro background
" Puts a series of *background changers* into effect from the point of
execution until the end of the file. They are also in effect when
a loaded fact is rechecked (as with `midje.repl/recheck-fact`).
See `(doc midje-background-changers)` for details on background
changers.
Examples:
(background (f 33) => 12, (f 34) => 21)
(background (before :facts (reset! some-atom 0)))
"
[& background-changers]
(when (user-desires-checking?)
(error/parse-and-catch-failure &form
#(do
(parse-background/assert-right-shape! &form)
(wrapping/put-wrappers-into-effect (parse-background/make-unification-templates
(arglist-undoing-nesting background-changers)))))))
(defmacro against-background
" Puts a series of *background changers* into effect until the end
of the `against-background` scope. They remain in effect when a
loaded fact is rechecked (as with `midje.repl/recheck-fact`).
See `(doc midje-background-changers)` for details on background
changers.
`against-background` can be used in two ways. In the first, it has
a `let`-like syntax that wraps a series of facts:
(against-background [(f 33) => 12
(f 34) => 21
(before :facts (reset! some-atom 0))]
(fact...)
(fact...))
In the second, it can be placed as either the first or last form in
a fact, in which case it is taken to \"wrap\" the entire fact:
(against-background (f 1) => :default, (g 1) => :default)
Note that in this case the square brackets can be omitted."
[background-forms & foreground-forms]
(when (user-desires-checking?)
(error/parse-and-catch-failure &form #(parse-facts/midjcoexpand &form))))
(defmacro with-state-changes
"Describe how state should change before or after enclosed facts. Example:
(with-state-changes [(before :facts (reset! state 0))
(after :facts (reset! state 1111))]
(fact ...)
(fact ...))"
[& forms]
(pointer/positioned-form `(midje.sweet/against-background ~@forms) &form))
(defmacro namespace-state-changes
"Applies arguments to any facts later created in the namespace. Often used
in the repl. Example:
(namespace-state-changes (before :facts (reset! state 0))
(after :facts (reset! state 1111)))
A later use of `namespace-state-changes` replaces the effect of an earlier
one. To \"turn off\" a previous use, do this:
(namespace-state-changes)
"
[& instructions]
(pointer/positioned-form `(midje.sweet/background ~@instructions) &form))
(defmacro fact
"A fact is a statement about code:
(fact \"one plus one is two\"
(+ 1 1) => 2)
Facts may describe one functions dependency on another:
(fact
(f ..x..) => 12
(provided (g ..x..) => 6))
"
[& _] ; we work off &form, not the arguments
(let [defn-name (if (string? (second &form))
(str/replace (second &form) #"[^\w\d]" "-")
(second &form))
body (error/parse-and-catch-failure &form
#(do (pointer/set-fallback-line-number-from &form)
(let [[metadata forms] (parse-metadata/separate-metadata &form)
[background remainder] (parse-background/separate-extractable-background-changing-forms forms)]
(if (seq background)
(pointer/positioned-form `(against-background [~@background]
~(parse-facts/wrap-fact-around-body metadata remainder))
&form)
(parse-facts/complete-fact-transformation metadata remainder)))))]
(if (user-desires-checking?)
body
`(defn ~(gensym defn-name) [] ~body))))
(defmacro facts
"Alias for fact."
[& forms]
(when (user-desires-checking?)
(with-meta `(fact ~@forms) (meta &form))))
(future-variants/generate-future-fact-variants)
(future-variants/generate-future-formula-variants)
(defmacro tabular
"Generate a table of related facts.
Ex. (tabular \"table of simple math\"
(fact (+ ?a ?b) => ?c)
?a ?b ?c
1 2 3
3 4 7
9 10 19 )"
{:arglists '([doc-string? fact table])}
[& _]
(pointer/set-fallback-line-number-from &form)
(parse-tabular/parse (keys &env) &form))
(defmacro metaconstants
"For a few operations, such as printing and equality checking,
the Clojure AOT-compiler becomes confused by Midje's auto-generation
of metaconstants. If AOT-compiled tests fail when on-the-fly
compiled tests failed, declare your metaconstants before use.
Example:
(metaconstants ..m.. ..m.... .mc.)"
[& symbols]
`(parse-metaconstants/predefine-metaconstants-from-form '~symbols))
(defmacro fact-group
"Supply default metadata to all facts in the body."
[& forms]
(when (user-desires-checking?)
(let [[metadata body] (parse-metadata/separate-multi-fact-metadata forms)]
(parse-metadata/with-wrapped-metadata metadata
(parse-facts/midjcoexpand `(do ~@body))))))
(defn add-midje-fact-symbols
"If you use a macro to wrap Midje's fact forms, `with-state-changes` will
complain that it contains no facts. You can avoid that by \"registering\"
your macro with Midje."
[symbols]
(parse-background/add-midje-fact-symbols symbols))
(add-midje-fact-symbols '[fact facts
future-fact future-facts
pending-fact pending-facts ; Sick of the other variants
formula future-formula
;; `check-one` appears because expansion of facts can
;; happen from the bottom up.
check-one])