diff --git a/deps.edn b/deps.edn index 6d3bca04..ec064874 100644 --- a/deps.edn +++ b/deps.edn @@ -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}} diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index fb2a7a25..81522456 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -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 diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index cc4e3c70..e7dd18d3 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -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] diff --git a/test-resources/lib_tests/slingshot/slingshot_test.clj b/test-resources/lib_tests/slingshot/slingshot_test.clj new file mode 100644 index 00000000..09938332 --- /dev/null +++ b/test-resources/lib_tests/slingshot/slingshot_test.clj @@ -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])))))) diff --git a/test-resources/lib_tests/slingshot/support_test.clj b/test-resources/lib_tests/slingshot/support_test.clj new file mode 100644 index 00000000..12d2c142 --- /dev/null +++ b/test-resources/lib_tests/slingshot/support_test.clj @@ -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))))) diff --git a/test-resources/lib_tests/slingshot/test_test.clj b/test-resources/lib_tests/slingshot/test_test.clj new file mode 100644 index 00000000..b156c289 --- /dev/null +++ b/test-resources/lib_tests/slingshot/test_test.clj @@ -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"))))