225
225
spec (specize spec)]
226
226
(if-let [g (c/or (get overrides path) (gen* spec overrides path rmap))]
227
227
(gen/such-that #(valid? spec %) g 100 )
228
- (throw (IllegalStateException. (str " Unable to construct gen at: " path " for: " (abbrev form)))))))
228
+ (let [abbr (abbrev form)]
229
+ (throw (ex-info (str " Unable to construct gen at: " path " for: " abbr)
230
+ {::path path ::no-gen-for form}))))))
229
231
230
232
(defn gen
231
233
" Given a spec, returns the generator for it, or throws if none can
277
279
(defn ns-qualify
278
280
" Qualify symbol s by resolving it or using the current *ns*."
279
281
[s]
280
- (if (namespace s)
281
- (let [v (resolve s)]
282
- (assert v (str " Unable to resolve: " s))
283
- (->sym v))
282
+ (if-let [ns-sym (some-> s namespace symbol)]
283
+ (c/or (some-> (get (ns-aliases *ns*) ns-sym) str (symbol (name s)))
284
+ s)
284
285
(symbol (str (.name *ns*)) (str s))))
285
286
286
287
(defmacro def
585
586
~@body))
586
587
587
588
(defn- spec-checking-fn
588
- [v f]
589
- (let [conform! (fn [v role spec data args]
589
+ [v f fn-spec]
590
+ (let [fn-spec (maybe-spec fn-spec)
591
+ conform! (fn [v role spec data args]
590
592
(let [conformed (conform spec data)]
591
593
(if (= ::invalid conformed)
592
594
(let [ed (assoc (explain-data* spec [role] [] [] data)
599
601
[& args]
600
602
(if *instrument-enabled*
601
603
(with-instrument-disabled
602
- (let [specs (get-spec v)]
603
- (when (:args specs) (conform! v :args (:args specs) args args))
604
- (binding [*instrument-enabled* true ]
605
- (.applyTo ^clojure.lang.IFn f args))))
604
+ (when (:args fn-spec) (conform! v :args (:args fn-spec) args args))
605
+ (binding [*instrument-enabled* true ]
606
+ (.applyTo ^clojure.lang.IFn f args)))
606
607
(.applyTo ^clojure.lang.IFn f args)))))
607
608
608
609
(defn- macroexpand-check
609
610
[v args]
610
- (let [specs (get-spec v)]
611
- (when-let [arg-spec (:args specs )]
611
+ (let [fn-spec (get-spec v)]
612
+ (when-let [arg-spec (:args fn-spec )]
612
613
(when (= ::invalid (conform arg-spec args))
613
614
(let [ed (assoc (explain-data* arg-spec [:args ]
614
615
(if-let [name (spec-name arg-spec)] [name] []) [] args)
654
655
`(clojure.spec/def ~fn-sym (clojure.spec/fspec ~@specs)))
655
656
656
657
(defn- no-fn-spec
657
- [v specs ]
658
+ [v spec ]
658
659
(ex-info (str " Fn at " v " is not spec'ed." )
659
- {:var v :specs specs }))
660
+ {:var v :spec spec }))
660
661
661
662
(def ^:private instrumented-vars
662
663
" Map for instrumented vars to :raw/:wrapped fns"
663
- (atom {}))
664
+ (atom {}))
664
665
665
666
(defn- ->var
666
667
[s-or-v]
671
672
v
672
673
(throw (IllegalArgumentException. (str (pr-str s-or-v) " does not name a var" )))))))
673
674
675
+ (defn- instrument-choose-fn
676
+ " Helper for instrument."
677
+ [f spec sym {:keys [stub replace]}]
678
+ (if (some #{sym} stub)
679
+ (-> spec gen gen/generate)
680
+ (get replace sym f)))
681
+
682
+ (defn- instrument-choose-spec
683
+ " Helper for instrument"
684
+ [spec sym {overrides :spec }]
685
+ (get overrides sym spec))
686
+
687
+ (defn- as-seqable
688
+ [x]
689
+ (if (seqable? x) x (list x)))
690
+
691
+ (defn- instrument-1
692
+ [s opts]
693
+ (when-let [v (resolve s)]
694
+ (let [spec (get-spec v)
695
+ {:keys [raw wrapped]} (get @instrumented-vars v)
696
+ current @v
697
+ to-wrap (if (= wrapped current) raw current)
698
+ ospec (c/or (instrument-choose-spec spec s opts)
699
+ (throw (no-fn-spec v spec)))
700
+ ofn (instrument-choose-fn to-wrap ospec s opts)
701
+ checked (spec-checking-fn v ofn ospec)]
702
+ (alter-var-root v (constantly checked))
703
+ (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked}))
704
+ (->sym v)))
705
+
674
706
(defn instrument
675
- " Instruments the var at v, a var or symbol, to check specs
676
- registered with fdef. Wraps the fn at v to check the :args
677
- spec, if it exists, throwing an ex-info with explain-data if a
678
- check fails. Idempotent."
679
- [v]
680
- (let [v (->var v)
681
- spec (get-spec v)]
682
- (if (fn-spec? spec)
683
- (locking instrumented-vars
684
- (let [{:keys [raw wrapped]} (get @instrumented-vars v)
685
- current @v]
686
- (when-not (= wrapped current)
687
- (let [checked (spec-checking-fn v current)]
688
- (alter-var-root v (constantly checked))
689
- (swap! instrumented-vars assoc v {:raw current :wrapped checked}))))
690
- v)
691
- (throw (no-fn-spec v spec)))))
707
+ " Instruments the vars named by sym-or-syms, a symbol or a
708
+ collection of symbols. Idempotent.
709
+
710
+ If a var has an :args fn-spec, sets the var's root binding to a
711
+ fn that checks arg conformance (throwing an exception on failure)
712
+ before delegating to the original fn.
713
+
714
+ The opts map can be used to override registered specs, and/or to
715
+ replace fn implementations entirely:
716
+
717
+ :spec a map from fn symbols to spec overrides
718
+ :stub a collection of fn symbols to stub
719
+ :replace a map from fn symbols to fn overrides
720
+
721
+ :spec overrides registered fn-specs with specs your provide. Use
722
+ :spec overrides to provide specs for libraries that do not have
723
+ them, or to constrain your own use of a fn to a subset of its
724
+ spec'ed contract.
725
+
726
+ :stub replaces a fn with a stub that checks :args, then uses the
727
+ :ret spec to generate a return value.
728
+
729
+ :replace replaces a fn with a fn that check :args, then invokes
730
+ a fn you provide, enabling arbitrary stubbing and mocking.
731
+
732
+ :spec can be used in combination with :stub or :replace.
733
+
734
+ Opts for symbols not named by sym-or-syms are ignored. This
735
+ facilitates sharing a common options map across many different
736
+ calls to instrument.
737
+
738
+ Returns a collection of syms naming the vars instrumented."
739
+ ([sym-or-syms] (instrument sym-or-syms nil ))
740
+ ([sym-or-syms opts]
741
+ (locking instrumented-vars
742
+ (into
743
+ []
744
+ (comp (map #(instrument-1 % opts))
745
+ (remove nil?))
746
+ (as-seqable sym-or-syms)))))
747
+
748
+ (defn- unstrument-1
749
+ [s]
750
+ (when-let [v (resolve s)]
751
+ (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)]
752
+ (let [current @v]
753
+ (when (= wrapped current)
754
+ (alter-var-root v (constantly raw))))
755
+ (swap! instrumented-vars dissoc v))
756
+ (->sym v)))
692
757
693
758
(defn unstrument
694
- " Undoes instrument on the var at v, a var or symbol. Idempotent."
695
- [v]
696
- (let [v (->var v)]
697
- (locking instrumented-vars
698
- (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)]
699
- (let [current @v]
700
- (when (= wrapped current)
701
- (alter-var-root v (constantly raw))))
702
- (swap! instrumented-vars dissoc v))
703
- v)))
704
-
705
- (defn speced-vars
706
- " Returns the set of vars whose namespace is in ns-syms AND
707
- whose vars have been speced with fdef. If no ns-syms are
708
- specified, return speced vars from all namespaces."
709
- [& ns-syms]
710
- (let [ns-match? (if (seq ns-syms)
711
- (set (map str ns-syms))
712
- (constantly true ))]
713
- (reduce-kv
714
- (fn [s k _]
715
- (if (c/and (symbol? k)
716
- (ns-match? (namespace k)))
717
- (if-let [v (resolve k)]
718
- (conj s v)
719
- s)
720
- s))
721
- #{}
722
- (registry ))))
759
+ " Undoes instrument on the vars named by sym-or-syms. Idempotent.
760
+ Returns a collection of syms naming the vars unstrumented."
761
+ [sym-or-syms]
762
+ (locking instrumented-vars
763
+ (into
764
+ []
765
+ (comp (map #(unstrument-1 %))
766
+ (remove nil?))
767
+ (as-seqable sym-or-syms))))
768
+
769
+ (defn- opt-syms
770
+ " Returns set of symbols referenced by 'instrument' opts map"
771
+ [opts]
772
+ (reduce into #{} [(:stub opts) (c/keys (:replace opts)) (c/keys (:spec opts))]))
773
+
774
+ (defn- ns-matcher
775
+ [ns-syms]
776
+ (let [ns-names (into #{} (map str) ns-syms)]
777
+ (fn [s]
778
+ (contains? ns-names (namespace s)))))
723
779
724
780
(defn instrument-ns
725
- " Call instrument for all speced-vars in namespaces named
726
- by ns-syms. Idempotent."
727
- [& ns-syms]
728
- (when (seq ns-syms)
729
- (locking instrumented-vars
730
- (doseq [v (apply speced-vars ns-syms)]
731
- (instrument v)))))
781
+ " Like instrument, but works on all symbols whose namespace is
782
+ in ns-or-nses, specified as a symbol or a seq of symbols."
783
+ ([] (instrument-ns (.name ^clojure.lang.Namespace *ns*)))
784
+ ([ns-or-nses] (instrument-ns ns-or-nses nil ))
785
+ ([ns-or-nses opts]
786
+ (let [ns-match? (ns-matcher (as-seqable ns-or-nses))]
787
+ (locking instrumented-vars
788
+ (into
789
+ []
790
+ (comp c/cat
791
+ (filter symbol?)
792
+ (filter ns-match?)
793
+ (distinct )
794
+ (map #(instrument-1 % opts))
795
+ (remove nil?))
796
+ [(c/keys (registry )) (opt-syms opts)])))))
732
797
733
798
(defn unstrument-ns
734
- " Call unstrument for all speced-vars in namespaces named
735
- by ns-syms. Idempotent ."
736
- [& ns-syms ]
737
- (when ( seq ns-syms)
799
+ " Like unstrument, but works on all symbols whose namespace is
800
+ in ns-or-nses, specified as a symbol or a seq of symbols ."
801
+ [ns-or-nses ]
802
+ (let [ns-match? ( ns-matcher ( as-seqable ns-or-nses))]
738
803
(locking instrumented-vars
739
- (doseq [v (apply speced-vars ns-syms)]
740
- (unstrument v)))))
804
+ (into
805
+ []
806
+ (comp (map ->sym)
807
+ (filter ns-match?)
808
+ (map unstrument-1)
809
+ (remove nil?))
810
+ (c/keys @instrumented-vars)))))
741
811
742
812
(defn instrument-all
743
- " Call instrument for all speced-vars. Idempotent."
744
- []
745
- (locking instrumented-vars
746
- (doseq [v (speced-vars )]
747
- (instrument v))))
813
+ " Like instrument, but works on all vars."
814
+ ([] (instrument-all nil ))
815
+ ([opts]
816
+ (locking instrumented-vars
817
+ (into
818
+ []
819
+ (comp c/cat
820
+ (filter symbol?)
821
+ (distinct )
822
+ (map #(instrument-1 % opts))
823
+ (remove nil?))
824
+ [(c/keys (registry )) (opt-syms opts)]))))
748
825
749
826
(defn unstrument-all
750
- " Call unstrument for all speced- vars. Idempotent "
827
+ " Like unstrument, but works on all vars."
751
828
[]
752
829
(locking instrumented-vars
753
- (doseq [v (speced-vars )]
754
- (unstrument v))))
830
+ (into
831
+ []
832
+ (comp (map ->sym)
833
+ (map unstrument-1)
834
+ (remove nil?))
835
+ (c/keys @instrumented-vars))))
755
836
756
837
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
757
838
(defn- recur-limit? [rmap id path k]
@@ -1674,4 +1755,4 @@ by ns-syms. Idempotent."
1674
1755
~@(when-not NaN? '[#(not (Double/isNaN %))])
1675
1756
~@(when max `[#(<= % ~max)])
1676
1757
~@(when min `[#(<= ~min %)]))
1677
- :gen #(gen/double* ~m)))
1758
+ :gen #(gen/double* ~m)))
0 commit comments