-
Notifications
You must be signed in to change notification settings - Fork 9
/
protocol.clj
94 lines (88 loc) · 3.58 KB
/
protocol.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
(ns spy.protocol
(:require [spy.core :as spy]))
(defn- protocol-methods
"Generate a list of methods that need to be implemented for
the protocol signatures"
[protocol]
(let [method-builders (:method-builders protocol)]
(loop [signatures (:sigs protocol)
processing nil
mthds {}]
(if processing
(if (empty? (:arglists processing))
(recur signatures nil mthds)
(let [k (keyword (:name processing))
v {:name (:name processing)
:arglists (:arglists processing)
:var (->> method-builders
keys
(filter (fn [x]
(= (:name (meta x))
(:name processing))))
first)}]
(recur signatures
nil
(assoc mthds k v))))
(if (empty? signatures)
mthds
(let [[_ to-process] (first signatures)]
(recur (rest signatures) to-process mthds)))))))
(defn- ->args [arglist]
(vec (map (fn [_] (gensym)) arglist)))
(defn- ->spy-fns [methods instance]
(reduce (fn [acc method]
(assoc acc (keyword (:name method))
`(spy.core/spy
(fn
~@(map (fn [arglist]
(let [args (->args arglist)]
(list args (concat (list (:var method) instance) (rest args)))))
(:arglists method))))))
{}
methods))
(defmacro spy
"Reify the protocol, spies attached to the reified object via metadata"
{:style/indent [:defn [1]]}
[protocol instance]
(let [methods (vals (protocol-methods @(resolve protocol)))
spy-fns-sym (gensym "spy-fns-")]
`(let [~spy-fns-sym ~(->spy-fns methods instance)]
(with-meta
(reify ~protocol
~@(mapcat (fn [{:keys [name arglists]}]
(map (fn [arglist]
(let [args (->args arglist)]
(list name args (concat (list (list (keyword name) spy-fns-sym)) args))))
arglists))
methods))
~spy-fns-sym))))
(defn spies [instance]
(meta instance))
(defmacro mock
"Creates an implementation via `clojure.core/reify` and
a wrapper to spy on the implementation, forwards all calls
to the implementation and records calsl in spies. Matches the
signature and can be used directly instead of `clojure.core/reify`"
[& opts+specs]
(let [[opts specs] (#'clojure.core/parse-opts opts+specs)
impls (#'clojure.core/parse-impls specs)
protocols (map (comp :on deref resolve) (keys impls))
methods (apply concat (map (comp vals
protocol-methods
deref
resolve)
(keys impls)))
spy-fns-sym (gensym "spy-fns-")
instance (gensym "instance-")]
`(let [~instance (clojure.core/reify ~@opts+specs)
~spy-fns-sym ~(->spy-fns methods instance)]
(with-meta
(reify
~@protocols
~@(mapcat (fn [{:keys [name arglists]}]
(map (fn [arglist]
(let [args (->args arglist)]
(list name args (concat (list (list (keyword name) spy-fns-sym)) args))))
arglists))
methods))
~spy-fns-sym))))