Add support for slingshot #675 (#881)

* Add java.util.Arrays/copyOfRange for slingshot

* add exception for tests

* Add slingshot tests
This commit is contained in:
Michiel Borkent 2021-06-09 10:39:43 +02:00 committed by GitHub
parent 426e97d1df
commit 15e54ef0a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 646 additions and 2 deletions

View file

@ -78,7 +78,8 @@
gaka/gaka {:mvn/version "0.3.0"}
failjure/failjure {:mvn/version "2.1.1"}
io.helins/binf {:mvn/version "1.1.0-beta0"}
rm-hull/jasentaa {:mvn/version "0.2.5"}}
rm-hull/jasentaa {:mvn/version "0.2.5"}
slingshot/slingshot {:mvn/version "0.12.2"}}
:classpath-overrides {org.clojure/clojure nil
org.clojure/spec.alpha nil
org.clojure/core.specs.alpha nil}}

View file

@ -71,7 +71,8 @@
{:name "toString"}
{:name "toURI"}]}
java.util.Arrays
{:methods [{:name "copyOf"}]}
{:methods [{:name "copyOf"}
{:name "copyOfRange"}]}
;; this fixes clojure.lang.Reflector for Java 11
java.lang.reflect.AccessibleObject
{:methods [{:name "canAccess"}]}}
@ -235,6 +236,7 @@
java.time.temporal.Temporal
java.time.temporal.TemporalAccessor
java.time.temporal.TemporalAdjuster])
java.util.concurrent.ExecutionException
java.util.concurrent.LinkedBlockingQueue
java.util.jar.JarFile
java.util.jar.JarEntry

View file

@ -212,6 +212,12 @@
'honey.sql.helpers-test
'honey.sql.postgres-test)
(test-namespaces 'slingshot.slingshot-test
'slingshot.support-test
;; TODO:
;; 'slingshot.test-test
)
;;;; final exit code
(let [{:keys [:test :fail :error] :as m} @status]

View file

@ -0,0 +1,516 @@
(ns slingshot.slingshot-test
(:require [clojure.test :refer :all]
[slingshot.slingshot :refer :all]
[clojure.string :as str])
(:import java.util.concurrent.ExecutionException))
(defrecord exception-record [error-code duration-ms message])
(defrecord x-failure [message])
(def a-sphere ^{:type ::sphere} {:radius 3})
(def h1 (derive (make-hierarchy) ::square ::shape))
(def a-square ^{:type ::square} {:size 4})
(def exception-1 (Exception. "exceptional"))
(def exception-record-1 (exception-record. 6 1000 "pdf failure"))
(defn mult-func [x y]
(let [a 7 b 11]
(if (= x 3)
(* a b x y)
(throw+ (x-failure. "x isn't 3... really??")))))
(defmacro mega-try [body]
`(try+
~body
;; by class derived from Throwable
(catch IllegalArgumentException e#
[:class-iae e#])
(catch Exception e#
[:class-exception e#])
;; by java class generically
(catch String e#
[:class-string e#])
;; by clojure record type
(catch exception-record e#
[:class-exception-record e#])
;; by key-value
(catch [:a-key 4] e#
[:key-yields-value e#])
;; by multiple-key-value
(catch [:key1 4 :key2 5] e#
[:keys-yield-values e#])
;; by key present
(catch (and (set? ~'%) (contains? ~'% :a-key)) e#
[:key-is-present e#])
;; by clojure type, with optional hierarchy
(catch (isa? (type ~'%) ::sphere) e#
[:type-sphere (type e#) e#])
(catch (isa? h1 (type ~'%) ::shape) e#
[:type-shape-in-h1 (type e#) e#])
;; by predicate
(catch nil? e#
[:pred-nil e#])
(catch keyword? e#
[:pred-keyword e#])
(catch symbol? e#
[:pred-symbol e#])
(catch map? e#
[:pred-map e# (meta e#)])))
(deftest test-try+
(testing "catch by class derived from Throwable"
(testing "treat throwables exactly as throw does, interop with try/throw"
(is (= [:class-exception exception-1]
(mega-try (throw+ exception-1))
(mega-try (throw exception-1))
(try (throw+ exception-1)
(catch Exception e [:class-exception e]))
(try (throw exception-1)
(catch Exception e [:class-exception e])))))
(testing "IllegalArgumentException thrown by clojure/core"
(is (= :class-iae (first (mega-try (str/replace "foo" 1 1)))))))
(testing "catch by java class generically"
(is (= [:class-string "fail"] (mega-try (throw+ "fail")))))
#_(testing "catch by clojure record type"
(is (= [:class-exception-record exception-record-1]
(mega-try (throw+ exception-record-1)))))
(testing "catch by key is present"
(is (= [:key-is-present #{:a-key}] (mega-try (throw+ #{:a-key})))))
(testing "catch by keys and values"
(is (= [:key-yields-value {:a-key 4}] (mega-try (throw+ {:a-key 4}))))
(is (= [:keys-yield-values {:key1 4 :key2 5}]
(mega-try (throw+ {:key1 4 :key2 5})))))
(testing "catch by clojure type with optional hierarchy"
(is (= [:type-sphere ::sphere a-sphere] (mega-try (throw+ a-sphere))))
(is (= [:type-shape-in-h1 ::square a-square] (mega-try (throw+ a-square)))))
(testing "catch by predicate"
(is (= [:pred-nil nil] (mega-try (throw+ nil))))
(is (= [:pred-keyword :awesome] (mega-try (throw+ :awesome))))
(is (= [:pred-symbol 'yuletide] (mega-try (throw+ 'yuletide))))
(is (= [:pred-map {:error-code 4} nil] (mega-try (throw+ {:error-code 4}))))
(testing "preservation of metadata"
(is (= [:pred-map {:error-code 4} {:severity 4}]
(mega-try (throw+ ^{:severity 4} {:error-code 4})))))))
(deftest test-clauses
(let [bumps (atom 0)
bump (fn [] (swap! bumps inc))]
(is (nil? (try+)))
(is (nil? (try+ (catch integer? i (inc i)))))
(is (nil? (try+ (finally (bump)))))
(is (nil? (try+ (catch integer? i (inc i)) (finally (bump)))))
(is (nil? (try+ (catch integer? i (inc i)) (catch map? m m)
(finally (bump)))))
(is (= 3 (try+ 3)))
(is (= 3 (try+ 3 (catch integer? i 4))))
(is (= 3 (try+ 3 (finally (bump)))))
(is (= 3 (try+ 3 (catch integer? i 4) (finally (bump)))))
(is (= 4 (try+ (throw+ 3) (catch integer? i (inc i)) (finally (bump)))))
(is (= 4 (try+ (throw+ 3) (catch integer? i (inc i)) (catch map? m m)
(finally (bump)))))
(is (= 4 (try+ (throw+ {:sel 4}) (catch integer? i (inc i))
(catch map? m (:sel m)) (finally (bump)))))
(is (= 4 (try+ 3 4)))
(is (= 4 (try+ 3 4 (catch integer? i 4))))
(is (= 4 (try+ 3 4 (finally (bump)))))
(is (= 4 (try+ 3 4 (catch integer? i 4) (finally (bump)))))
(is (= 5 (try+ (throw+ 4) 4 (catch integer? i (inc i)) (finally (bump)))))
(is (= 11 @bumps))))
(defn ax [] (throw+ 1))
(defn bx [] (try+ (ax) (catch integer? p (throw+ 2))))
(defn cx [] (try+ (bx) (catch integer? q (throw+ 3))))
(defn dx [] (try+ (cx) (catch integer? r (throw+ 4))))
(defn ex [] (try+ (dx) (catch integer? s (throw+ 5))))
(defn fx [] (try+ (ex) (catch integer? t (throw+ 6))))
(defn gx [] (try+ (fx) (catch integer? u (throw+ 7))))
(defn hx [] (try+ (gx) (catch integer? v (throw+ 8))))
(defn ix [] (try+ (hx) (catch integer? w &throw-context)))
(defn next-context [x]
(-> x :cause get-throw-context))
(deftest test-throw-context
(let [context (ix)
context1 (next-context context)
context2 (next-context context1)]
(is (= #{:object :message :cause :stack-trace :wrapper :throwable}
(set (keys context))
(set (keys context1))
(set (keys context2))))
(is (= 8 (-> context :object)))
(is (= 7 (-> context1 :object)))
(is (= 6 (-> context2 :object)))))
(defn e []
(try+
(throw (Exception. "uncaught"))
(catch integer? i i)))
(defn f []
(try+
(throw+ 3.2)
(catch integer? i i)))
(defn g []
(try+
(throw+ 3.2 "wasn't caught")
(catch integer? i i)))
(deftest test-uncaught
(is (thrown-with-msg? Exception #"^uncaught$" (e)))
(is (thrown-with-msg? Exception #"^throw\+: .*" (f)))
(is (thrown-with-msg? Exception #"wasn't caught" (g))))
(defn h []
(try+
(try+
(throw+ 0)
(catch zero? e
(throw+)))
(catch zero? e
:zero)))
(deftest test-rethrow
(is (= :zero (h))))
(defn i []
(try
(try+
(doall (map (fn [x] (throw+ (str x))) [1]))
(catch string? x
x))
(catch Throwable x)))
(defn j []
(try+
(let [fut (future (throw+ "whoops"))]
@fut)
(catch string? e
e)))
(deftest test-issue-5
(is (= "1" (i)))
(is (= "whoops" (j))))
(deftest test-unmacroed-pct
(is (= :was-eee (try+ (throw+ "eee")
(catch (= % "eee") _ :was-eee)
(catch string? _ :no!)))))
(deftest test-x-ray-vision
(let [[val wrapper] (try+
(try
(try
(try
(throw+ "x-ray!")
(catch Throwable x
(throw (RuntimeException. x))))
(catch Throwable x
(throw (ExecutionException. x))))
(catch Throwable x
(throw (RuntimeException. x))))
(catch string? x
[x (:throwable &throw-context)]))]
(is (= "x-ray!" val))
(is (= "x-ray!" (get-thrown-object wrapper)))))
(deftest test-catching-wrapper
(let [e (Exception.)]
(try
(try+
(throw e)
(catch Exception _
(throw+ :a "msg: %s" %)))
(is false)
(catch Exception s
(is (= "msg: :a" (.getMessage s)))
(is (= e (.getCause s)))))))
(deftest test-eval-object-once
(let [bumps (atom 0)
bump (fn [] (swap! bumps inc))]
(try+
(throw+ (bump) "this is it: %s %s %s" % % %)
(catch Object _))
(is (= @bumps 1))))
(deftest test-get-throw-context
(let [object (Object.)
exception1 (Exception.)
exception2 (Exception. "ex1" exception1)
t1 (try
(throw+ object)
(catch Throwable t t))
t2 (try
(throw+ exception2)
(catch Throwable t t))
t3 (try
(throw exception2)
(catch Throwable t t))]
(is (= #{:object :message :cause :stack-trace :wrapper
:throwable}
(-> t1 get-throw-context keys set)))
(is (= #{:object :message :cause :stack-trace :throwable}
(-> t2 get-throw-context keys set)))
(is (= #{:object :message :cause :stack-trace :throwable}
(-> t3 get-throw-context keys set)))
(is (identical? object (:object (get-throw-context t1))))
(is (identical? exception2 (:object (get-throw-context t2))))
(is (identical? exception2 (:object (get-throw-context t3))))
(is (identical? exception1 (:cause (get-throw-context t2))))
(is (identical? exception1 (:cause (get-throw-context t3))))
(is (= "ex1" (:message (get-throw-context t2))))
(is (= "ex1" (:message (get-throw-context t3))))))
(deftest test-get-thrown-object
(let [object (Object.)
exception (Exception.)
t1 (try
(throw+ object)
(catch Throwable t t))
t2 (try
(throw+ exception)
(catch Throwable t t))
t3 (try
(throw exception)
(catch Throwable t t))]
(is (identical? object (get-thrown-object t1)))
(is (identical? exception (get-thrown-object t2)))
(is (identical? exception (get-thrown-object t3)))))
(deftest test-wrapper-and-throwable
(let [context (try+
(try
(throw+ :afp "wrapper-0")
(catch Exception e
(throw (RuntimeException. "wrapper-1" e))))
(catch Object _
&throw-context))]
(is (= "wrapper-0" (.getMessage ^Throwable (:wrapper context))))
(is (= "wrapper-1" (.getMessage ^Throwable (:throwable context))))))
(deftest test-inline-predicate
(is (= :not-caught (try+
(throw+ {:foo true})
(catch #(-> % :foo (= false)) data
:caught)
(catch Object _
:not-caught)))))
(defn gen-body
[rec-sym throw?]
(let [body `(swap! ~rec-sym #(conj % :body))]
(if throw?
(list 'do body `(throw+ (Exception.)))
body)))
(defn gen-catch-clause
[rec-sym]
`(catch Exception e# (swap! ~rec-sym #(conj % :catch))))
(defn gen-else-clause
[rec-sym broken?]
(let [else-body `(swap! ~rec-sym #(conj % :else))]
(if broken?
(list 'else (list 'do else-body `(throw+ (Exception.))))
(list 'else else-body))))
(defn gen-finally-clause
[rec-sym]
`(finally (swap! ~rec-sym #(conj % :finally))))
(defn gen-try-else-form
"Generate variations of (try ... (else ...) ...) forms, which (when eval'd)
will return a vector describing the sequence in which things were evaluated,
e.g. [:body :catch :finally]"
[throw? catch? finally? broken-else?]
(let [rec-sym (gensym "rec")
body (gen-body rec-sym throw?)
catch-clause (if catch? (gen-catch-clause rec-sym))
else-clause (gen-else-clause rec-sym broken-else?)
finally-clause (if finally? (gen-finally-clause rec-sym))]
`(let [~rec-sym (atom [])]
(try+
~(remove nil? `(try+
~body
~catch-clause
~else-clause
~finally-clause))
(catch Object e#
;; if the inner try+ threw, report it as a :bang! in the return vec
(swap! ~rec-sym #(conj % :bang!))))
@~rec-sym)))
(deftest test-else
(doseq [throw? [true false]
catch? [true false]
broken-else? [true false]
finally? [true false]]
(testing (str "test-else: throw? " throw? " catch? " catch?
" broken-else? " broken-else? " finally? " finally?)
(let [try-else-form (gen-try-else-form throw? catch? finally? broken-else?)
actual (eval try-else-form)
expected (vec (remove nil?
[:body
(if (and throw? catch?) :catch)
(if (not throw?) :else)
(if finally? :finally)
;; expect an escaped exception when either:
;; a) the else clause runs, and throws
;; b) the body throws, and is not caught
(if (or (and (not throw?) broken-else?)
(and throw? (not catch?))) :bang!)]))]
(is (= actual expected))))))
(deftest test-reflection
(try+
nil
(catch Exception e
(.getMessage e))))
(deftest test-ex-info-compatibility
(let [data {:type :fail :reason :not-found}
message "oops"
wrapper (ex-info message data)
rte1 (RuntimeException. "one" wrapper)
rte2 (RuntimeException. "two" rte1)
direct (try+
(throw wrapper)
(catch [:type :fail] e
&throw-context)
(catch Object _
:whoops))
cause-chain (try+
(throw rte2)
(catch [:type :fail] e
&throw-context)
(catch Object _
:whoops))]
(is (= (:object direct) data))
(is (= (:object cause-chain) data))
(is (= (:message direct) message))
(is (= (:message cause-chain) message))
(is (= (:wrapper direct) wrapper))
(is (= (:wrapper cause-chain) wrapper))
(is (= (:throwable direct) wrapper))
(is (= (:throwable cause-chain) rte2))))
;; helpers for test-optional-cause
(defmacro caught-result [& body]
`(try+
~@body
(catch Object ~'o
[(:cause ~'&throw-context)
(:message ~'&throw-context)])))
(defmacro caught-result-from-catch [cause & body]
`(caught-result
(try+
(throw+ ~cause)
(catch Object ~'o
~@body))))
(deftest test-optional-cause
(let [imp (Exception. "I did it implicitly.")
exp (Exception. "I did it explicitly.")
def-msg "throw+: 1"
msg "message two %s"
fmt "aha! %s"
fmt-msg "aha! 1"
fmt2 "%s leading to %s"
fmt2-msg "1 leading to [1 1]"
;; throw from outside catch, no implicit cause
result1 (caught-result (throw+ 1))
result2 (caught-result (throw+ 1 msg))
result3 (caught-result (throw+ 1 fmt %))
result4 (caught-result (throw+ 1 fmt2 % [% %]))
result5 (caught-result (throw+ 1 nil))
result6 (caught-result (throw+ 1 nil msg))
result7 (caught-result (throw+ 1 nil fmt %))
result8 (caught-result (throw+ 1 nil fmt2 % [% %]))
result9 (caught-result (throw+ 1 exp))
result10 (caught-result (throw+ 1 exp msg))
result11 (caught-result (throw+ 1 exp fmt %))
result12 (caught-result (throw+ 1 exp fmt2 % [% %]))
;; throw from inside catch, implicit cause available
result13 (caught-result-from-catch imp (throw+))
result14 (caught-result-from-catch imp (throw+ 1))
result15 (caught-result-from-catch imp (throw+ 1 msg))
result16 (caught-result-from-catch imp (throw+ 1 fmt %))
result17 (caught-result-from-catch imp (throw+ 1 fmt2 % [% %]))
result18 (caught-result-from-catch imp (throw+ 1 nil))
result19 (caught-result-from-catch imp (throw+ 1 nil msg))
result20 (caught-result-from-catch imp (throw+ 1 nil fmt %))
result21 (caught-result-from-catch imp (throw+ 1 nil fmt2 % [% %]))
result22 (caught-result-from-catch imp (throw+ 1 exp))
result23 (caught-result-from-catch imp (throw+ 1 exp msg))
result24 (caught-result-from-catch imp (throw+ 1 exp fmt %))
result25 (caught-result-from-catch imp (throw+ 1 exp fmt2 % [% %]))]
(testing "outside catch"
(testing "implicit cause"
(is (= result1 [nil def-msg]))
(is (= result2 [nil msg]))
(is (= result3 [nil fmt-msg]))
(is (= result4 [nil fmt2-msg])))
(testing "erased cause"
(is (= result5 [nil def-msg]))
(is (= result6 [nil msg]))
(is (= result7 [nil fmt-msg]))
(is (= result8 [nil fmt2-msg])))
(testing "explicit cause"
(is (= result9 [exp def-msg]))
(is (= result10 [exp msg]))
(is (= result11 [exp fmt-msg]))
(is (= result12 [exp fmt2-msg]))))
(testing "inside catch"
(testing "rethrow"
(is (= result13 [nil "I did it implicitly."])))
(testing "implicit cause"
(is (= result14 [imp def-msg]))
(is (= result15 [imp msg]))
(is (= result16 [imp fmt-msg]))
(is (= result17 [imp fmt2-msg])))
(testing "erased cause"
(is (= result18 [nil def-msg]))
(is (= result19 [nil msg]))
(is (= result20 [nil fmt-msg]))
(is (= result21 [nil fmt2-msg])))
(testing "explicit cause"
(is (= result22 [exp def-msg]))
(is (= result23 [exp msg]))
(is (= result24 [exp fmt-msg]))
(is (= result25 [exp fmt2-msg]))))))

View file

@ -0,0 +1,111 @@
(ns slingshot.support-test
(:require [clojure.test :refer :all]
[slingshot.slingshot :refer [throw+ try+]]
[slingshot.support :refer :all])
(:import (java.util.concurrent ExecutionException)))
(deftest test-parse-try+
(let [f parse-try+]
(is (= [nil nil nil nil] (f ())))
(is (= ['(1) nil nil nil] (f '(1))))
(is (= [nil '((catch 1)) nil nil] (f '((catch 1)))))
(is (= [nil nil '(else 1) nil] (f '((else 1)))))
(is (= [nil nil nil '(finally 1)] (f '((finally 1)))))
(is (= ['(1) '((catch 1)) nil nil] (f '(1 (catch 1)))))
(is (= ['(1) nil '(else 1) nil] (f '(1 (else 1)))))
(is (= ['(1) nil nil '(finally 1)] (f '(1 (finally 1)))))
(is (= ['(1) '((catch 1)) nil '(finally 1)]
(f '(1 (catch 1) (finally 1)))))
(is (= ['(1) '((catch 1) (catch 2)) nil '(finally 1)]
(f '(1 (catch 1) (catch 2) (finally 1)))))
(is (= ['(1) '((catch 1)) '(else 1) nil]
(f '(1 (catch 1) (else 1)))))
(is (= ['(1) '((catch 1) (catch 2)) '(else 1) nil]
(f '(1 (catch 1) (catch 2) (else 1)))))
(is (= [nil nil '(else 1) '(finally 1)]
(f '((else 1) (finally 1)))))
(is (= ['(1) nil '(else 1) '(finally 1)]
(f '(1 (else 1) (finally 1)))))
(is (= [nil '((catch 1)) '(else 1) nil]
(f '((catch 1) (else 1)))))
(is (= ['(1) '((catch 1)) '(else 1) nil]
(f '(1 (catch 1) (else 1)))))
(is (thrown? IllegalArgumentException (f '((catch 1) (1)))))
(is (thrown? IllegalArgumentException (f '((finally 1) (1)))))
(is (thrown? IllegalArgumentException (f '((finally 1) (catch 1)))))
(is (thrown? IllegalArgumentException (f '((finally 1) (finally 2)))))
(is (thrown? IllegalArgumentException (f '((else 1) (1)))))
(is (thrown? IllegalArgumentException (f '((else 1) (catch 1)))))
(is (thrown? IllegalArgumentException (f '((else 1) (else 2)))))))
(defn stack-trace-fn []
(stack-trace))
#_(deftest test-stack-trace
(let [{:keys [methodName className]} (-> (stack-trace-fn) first bean)]
(is (= methodName "invoke"))
(is (re-find #"stack_trace_fn" className))))
(deftest test-resolve-local
(let [a 4]
(is (= 4 (resolve-local a)))
(is (nil? (resolve-local b)))))
(deftest test-wrap
(let [tmessage "test-wrap-1"
tobject 4
tcause (Exception.)
tstack-trace (stack-trace)
tdata {:object tobject}
tcontext (assoc tdata
:message tmessage
:cause tcause
:stack-trace tstack-trace)
tthrowable (wrap tcontext)
{:keys [message cause data stackTrace]} (bean tthrowable)]
(is (ex-data tthrowable))
(is (= [message cause (seq stackTrace) data]
[tmessage tcause (seq tstack-trace) tdata]))))
(def test-hooked (atom nil))
(deftest test-throw-hook
(binding [*throw-hook* #(reset! test-hooked %)]
(throw+ "throw-hook-string")
(is (= (set (keys @test-hooked))
(set [:object :message :cause :stack-trace])))
(is (= "throw-hook-string" (:object @test-hooked))))
(binding [*throw-hook* (fn [x] 42)]
(is (= (throw+ "something") 42))))
(def catch-hooked (atom nil))
(defn catch-hook-return [object]
(fn [x] (assoc x :catch-hook-return object)))
(defn catch-hook-throw [object]
(fn [x] (assoc x :catch-hook-throw object)))
(deftest test-catch-hook
(binding [*catch-hook* #(reset! catch-hooked %)]
(try+ (throw+ "catch-hook-string") (catch string? x x))
(is (= (set (keys @catch-hooked))
(set [:object :message :cause :stack-trace :wrapper :throwable])))
(is (= "catch-hook-string" (:object @catch-hooked))))
(binding [*catch-hook* (catch-hook-return 42)]
(is (= 42 (try+ (throw+ "boo") (catch string? x x)))))
(binding [*catch-hook* (catch-hook-throw (IllegalArgumentException. "bleh"))]
(is (thrown-with-msg? IllegalArgumentException #"bleh"
(try+ (throw+ "boo") (catch string? x x)))))
(is (= "soup!"
(try+
(binding [*catch-hook* (catch-hook-throw "soup!")]
(try+
(throw+ "boo")
(catch string? x x)))
(catch string? x x)))))

View file

@ -0,0 +1,8 @@
(ns slingshot.test-test
(:require [clojure.test :refer :all]
[slingshot.slingshot :refer [throw+]]
[slingshot.test]))
(deftest test-slingshot-test-macros
(is (thrown+? string? (throw+ "test")))
(is (thrown+-with-msg? string? #"th" (throw+ "test" "hi there"))))