-
Notifications
You must be signed in to change notification settings - Fork 24
/
test.cljc
160 lines (139 loc) · 5.57 KB
/
test.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns orchestra-cljs.spec.test
(:require
[cljs.analyzer :as ana]
[cljs.analyzer.api :as ana-api]
[clojure.string :as string]
[cljs.spec.alpha :as s]
[cljs.spec.gen.alpha :as gen]))
(defonce ^:private instrumented-vars (atom #{}))
(defn- collectionize
[x]
(if (symbol? x)
(list x)
x))
(defn- fn-spec-name?
[s]
(symbol? s))
(defn- form->sym-or-syms
"Helper for extracting a symbol or symbols from a (potentially
user-supplied) quoted form. In the case that the form has ::no-eval meta, we
know it was generated by us and we directly extract the result, assuming the
shape of the form. This avoids applying eval to extremely large forms in the
latter case."
[sym-or-syms]
(if (::no-eval (meta sym-or-syms))
(second sym-or-syms)
(do
(println "eval" sym-or-syms)
(eval sym-or-syms))))
(defmacro with-instrument-disabled
"Disables instrument's checking of calls, within a scope."
[& body]
`(binding [orchestra-cljs.spec.test/*instrument-enabled* nil]
~@body))
(defmacro instrument-1
[[quote s] opts]
(when-let [v (ana-api/resolve &env s)]
(when (nil? (:const v))
(swap! instrumented-vars conj (:name v))
`(let [checked# (instrument-1* ~s (var ~s) ~opts)]
(when checked# (set! ~s checked#))
'~(:name v)))))
(defmacro unstrument-1
[[quote s]]
(when-let [v (ana-api/resolve &env s)]
(when (@instrumented-vars (:name v))
(swap! instrumented-vars disj (:name v))
`(let [raw# (unstrument-1* ~s (var ~s))]
(when raw# (set! ~s raw#))
'~(:name v)))))
(defn- sym-or-syms->syms [sym-or-syms]
(into []
(mapcat
(fn [sym]
(if (and (string/includes? (str sym) ".")
(ana-api/find-ns sym))
(->> (vals (ana-api/ns-interns sym))
(filter #(not (:macro %)))
(map :name)
(map
(fn [name-sym]
(symbol (name sym) (name name-sym)))))
[sym])))
(collectionize sym-or-syms)))
(defmacro instrument
"Instruments the vars named by sym-or-syms, a symbol or collection
of symbols, or all instrumentable vars if sym-or-syms is not
specified. If a symbol identifies a namespace then all symbols in that
namespace will be enumerated.
If a var has an :args fn-spec, sets the var's root binding to a
fn that checks arg conformance (throwing an exception on failure)
before delegating to the original fn.
The opts map can be used to override registered specs, and/or to
replace fn implementations entirely. Opts for symbols not included
in sym-or-syms are ignored. This facilitates sharing a common
options map across many different calls to instrument.
The opts map may have the following keys:
:spec a map from var-name symbols to override specs
:stub a set of var-name symbols to be replaced by stubs
:gen a map from spec names to generator overrides
:replace a map from var-name symbols to replacement fns
:spec overrides registered fn-specs with specs your provide. Use
:spec overrides to provide specs for libraries that do not have
them, or to constrain your own use of a fn to a subset of its
spec'ed contract.
:stub replaces a fn with a stub that checks :args, then uses the
:ret spec to generate a return value.
:gen overrides are used only for :stub generation.
:replace replaces a fn with a fn that checks args conformance, then
invokes the fn you provide, enabling arbitrary stubbing and mocking.
:spec can be used in combination with :stub or :replace.
Returns a collection of syms naming the vars instrumented."
([]
`(instrument ^::no-eval '[~@(#?(:clj s/speced-vars
:cljs cljs.spec.alpha$macros/speced-vars))]))
([xs]
`(instrument ~xs nil))
([sym-or-syms opts]
(let [syms (sym-or-syms->syms (form->sym-or-syms sym-or-syms))
opts-sym (gensym "opts")]
`(let [~opts-sym ~opts]
(reduce
(fn [ret# [_# f#]]
(let [sym# (f#)]
(cond-> ret# sym# (conj sym#))))
[]
(->> (zipmap '~syms
[~@(map
(fn [sym]
`(fn [] (instrument-1 '~sym ~opts-sym)))
syms)])
(filter #((instrumentable-syms ~opts-sym) (first %)))
(distinct-by first)))))))
(defmacro unstrument
"Undoes instrument on the vars named by sym-or-syms, specified
as in instrument. With no args, unstruments all instrumented vars.
Returns a collection of syms naming the vars unstrumented."
([]
`(unstrument ^::no-eval '[~@(deref instrumented-vars)]))
([sym-or-syms]
(let [syms (sym-or-syms->syms (form->sym-or-syms sym-or-syms))]
`(reduce
(fn [ret# f#]
(let [sym# (f#)]
(cond-> ret# sym# (conj sym#))))
[]
[~@(->> syms
(map
(fn [sym]
(when (symbol? sym)
`(fn []
(unstrument-1 '~sym)))))
(remove nil?))]))))