-
Notifications
You must be signed in to change notification settings - Fork 1
/
core_test.clj
373 lines (289 loc) · 9.4 KB
/
core_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
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
(ns maker.core-test
(:require [clojure.test :refer :all]
[maker.core :as m :refer :all]
[clojure.pprint :refer [pprint]]
[ns2 :refer [ns2a' ns3a-proxy' ns2i' ns2b']] ;the point is: ns3 shouldn't be required directly here ever
[ns1 :refer [ns1a']]))
;-------------------------------------------------------------------------------
(defn simple'
[]
"simple")
;; simple is called a 'goal'
;; simple' is the 'maker function
;; "simple" is the value of the goal
(deftest test-simple
(is (= "simple"
;let's make the goal
(make simple)
;the make macro expands to
(let [simple (simple')]
simple))))
;; to define a maker function just put a ''' at the end of name.
(defn other'
[simple]
(str simple "-other"))
;; a direct definition
(def other2' "other2")
;; the defgoal macro puts ''' at the end of the goal name
(defgoal another
"Just another goal but now using the defgoal - the same effect."
[other other2]
(str other "-" other2 "-another"))
(deftest base-transitive-dep-test
(is (= (make another)
"simple-other-other2-another"
(let [simple (simple')
other (other' simple)
other2 other2'
another (another' other other2)]
another)))
;; shadow the original definition simply with let
(is (= (let [simple "changed"]
(make another))
"changed-other-other2-another")))
;-------------------------------------------------------------------------------
(deftest test-across-nss
;; for external namespaces maker munges the binding names,
;; check it with macroexpansion
(is (= 222 (make ns2a)))
;with-goals is an extended let, works well with goals from another namespaces.
(is (= 22 (let [ns1a 11]
(make ns2a)))))
;-------------------------------------------------------------------------------
(declare dd')
(defn goal-with-dyn-dep' [dd] (+ 10 dd))
(deftest test-dynamic-goal
(is (= (let [dd 1]
(make goal-with-dyn-dep))
11)))
;-------------------------------------------------------------------------------
(def call-counter (atom 0))
(defn factor'
[]
(swap! call-counter inc)
2)
;; the next is a declaration of a goal without definition
(defgoal? iterator-item)
(defn iterator-items'
[]
(range 10))
(defn collected-item'
[factor ns2a iterator-item]
(* iterator-item factor))
;;Look at iterator-item, it was declared above and the actual value is defined
;;here locally.
(defgoal collected-items
"A goal with a make call in it."
[iterator-items]
(map (fn [iterator-item] ;binds to iterator-item goal, declared above
(make collected-item))
iterator-items))
(deftest test-static-collectors
(reset! call-counter 0)
(is (= (last (make collected-items))
18))
;;factor' was called 10 times, usually this is not what you want...so read on.
(is (= @call-counter 10)))
(defgoalfn collected-item-fn [iterator-item] collected-item)
;TBD What if the collected-item is a <>
(defn another-collected-items2'
[iterator-items collected-item-fn]
(map collected-item-fn iterator-items))
(deftest iterator-with-goalfn
(reset! call-counter 0)
(is (= (last (make another-collected-items2))
18))
(is (= 1 @call-counter)))
(defgoalfn g-fn [ns2i] ns2b)
(deftest cross-ns-goalfn
(is (= "221" ((make g-fn) 1))))
(deftest test-spec
(eval '(do (use 'maker.core)
(defgoal sb [])
(defgoal sa [sb] 1)
(make sa))))
;-------------------------------------------------------------------------------
;works with multimethods
(defn multi-dep'
[]
"123")
(defmulti multi' {:arglists '([multi-dep])} count)
(defmethod multi' 3
[_]
"yes")
(deftest test-multi
(is (= (make multi)
"yes")))
;-------------------------------------------------------------------------------
(def choice-env')
(defn choice-dep-a'
[]
"a")
(defn choice-dep-b'
[choice-dep-a]
(str choice-dep-a "b"))
(defn choice-add'
[]
"add")
(defn choice-dispatch'
[choice-env choice-add]
choice-env)
(defmulticase choice choice-dispatch)
(defn choice1'
[choice-dep-a choice-add]
(str choice-dep-a choice-add "1"))
(defcasegoal choice :choice1
[choice-dep-a choice-add]
(str choice-dep-a choice-add "1"))
(register-case choice :choice1 choice1)
(defcasegoal choice :choice2
[choice-dep-b]
(str choice-dep-b "2"))
(defn end'
[choice]
(str choice ":end"))
; b/c choice has the goal type m/case meta, the expansion of creating it will be
; (case ..) and the return value of 'choice' is used as the dispatcher and the
; matching choice (in our case choice1) will be 'made'.
; Check the expansion of make below.
(deftest choice-test
(is (= (let [choice-env :choice1]
(make end))
"aadd1:end"))
(is (= (let [choice-env :choice1]
(make choice))
"aadd1"))
(is (= (let [choice-env :choice2]
(make end))
"ab2:end")))
;-------------------------------------------------------------------------------
(defn m' [] {:a 1 :b 2})
(defn v' [] [11 22])
;; maker works together with destructuring
(defn destr-goal'
[{:keys [a b] :as m} [c :as v]]
[a b m c v])
(deftest test-destr
(is (= (make destr-goal)
[1 2 {:a 1 :b 2} 11 [11 22]])))
;; destructuring wiht dynamic goals
(defn d-destr-goal'
[{:keys [a b] :as dm} [c :as dv]]
[a b dm c dv])
(defgoal? dm)
(defgoal? dv)
(deftest test-d-destr
(is (= (let [dm {:a 111 :b 222}
dv [1 2]]
(make d-destr-goal))
[111 222 {:a 111 :b 222} 1 [1 2]])))
;-------------------------------------------------------------------------------
;; circular dependency is an error at compile time
(deftest circular-dep
(is (re-find #"Circular"
(try
(eval '(do
(use 'maker.core)
(defn self'
[self])
(make self)))
(catch Throwable th
(str th))))))
;-------------------------------------------------------------------------------
;Example support for reloaded framework.
(def stop-fns (atom ()))
(def stop-fn
(partial swap! stop-fns conj))
(defn stop-system
[]
(doseq [f @stop-fns]
(f)
(swap! stop-fns rest)))
;The three things above consist the generic support for a reloaded workflow.
;Find below the sample 'components'.
(defgoal config
[]
(stop-fn #(println "stop the config."))
"the config")
(defgoal db-conn
[config]
(stop-fn #(println "stop the db-conn."))
(str "the db-conn"))
(deftest my-little-component-framework
(is (= (make db-conn)
"the db-conn"))
(is (= (count @stop-fns)
2))
(is (= (with-out-str
(stop-system))
"stop the db-conn.\nstop the config.\n"))
(is (= (count @stop-fns)
0)))
;-------------------------------------------------------------------------------
(deftest missing-def
(is (re-find #"Undefined dependency"
(try
(eval '(do (use 'maker.core)
(defgoal a [b])
(make a)))
(catch Throwable ei
(-> ei (.getCause) str)))))
(is (= 'aaaa'
(try
(eval '(do (use 'maker.core)
(defgoal? aaaa)
(make aaaa)
nil))
(catch Throwable ei
(-> ei (.getCause) ex-data :meta :name))))))
;-------------------------------------------------------------------------------
;configuration support
(defgoal? config-a)
(defgoal configured
[config-a]
(str config-a "ured!!!"))
(defgoal? profile)
(defgoal my-config
[profile]
{:maker.core-test/config-a (str (name profile) " is config")})
(deftest test-config
(let [profile :staging]
(with-config [(let [profile :test]
(keys (make my-config)))
(make my-config)]
(is (= (make configured)
"staging is configured!!!")))))
(def direct-config {:maker.core-test/config-a "config"})
(deftest test-direct-config
;if the compile time and runtime configs are different
(with-config [(keys direct-config) direct-config]
(is (= (make configured)
"configured!!!")))
;if the two config is the same
(with-config direct-config
(is (= (make configured)
"configured!!!"))))
(defgoal shouldnt-be-called
[]
(throw (ex-info "Never" {})))
(defgoal misconfigured
[shouldnt-be-called config-a]
(throw (ex-info "Never" {})))
(deftest check-fails-fast-for-wrong-config
;here the runtime config doesn't contain the 'promised' key
;the other goal constructors are not called although the order of parameters
;would implies that and it fails fast as it should
(is (thrown-with-msg? Throwable #"Missing config keys"
(with-config [[:maker.core-test/config-a]
{}]
(make misconfigured)))))
(deftest with-non-required-ns
(is (= (with-config {:ns3/ns3a 11}
(make ns3a-proxy))
11)))
;-------------------------------------------------------------------------------
(deftest munge-test
(are [s res] (-> s inj-munge (= res))
"aa" "aa"
"a.b/c" "a+_b+!c"
"ab/c" "ab+!c"))