├── .gitignore ├── LICENSE ├── README.md ├── project.clj ├── src ├── clj_stacktrace │ ├── core.clj │ ├── repl.clj │ └── utils.clj └── leiningen │ └── hooks │ └── clj_stacktrace_test.clj └── test └── clj_stacktrace ├── core_test.clj └── repl_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | *.xml 3 | /.lein-failures 4 | /.lein-deps-sum 5 | /target 6 | /pom.xml.asc 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2009-2013 Mark McGranaghan and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-stacktrace 2 | 3 | A library for creating more readable stacktraces in Clojure programs. 4 | 5 | For example, to print a nice stack trace in a REPL: 6 | 7 | => (use 'clj-stacktrace.repl) 8 | => ("foo") 9 | java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0) 10 | Compiler.java:5440 clojure.lang.Compiler.eval 11 | Compiler.java:5391 clojure.lang.Compiler.eval 12 | core.clj:2382 clojure.core/eval 13 | main.clj:183 clojure.main/repl[fn] 14 | main.clj:204 clojure.main/repl[fn] 15 | main.clj:204 clojure.main/repl 16 | RestFn.java:422 clojure.lang.RestFn.invoke 17 | main.clj:262 clojure.main/repl-opt 18 | main.clj:355 clojure.main/main 19 | RestFn.java:398 clojure.lang.RestFn.invoke 20 | Var.java:361 clojure.lang.Var.invoke 21 | AFn.java:159 clojure.lang.AFn.applyToHelper 22 | Var.java:482 clojure.lang.Var.applyTo 23 | main.java:37 clojure.main.main 24 | Caused by: java.lang.String cannot be cast to clojure.lang.IFn 25 | NO_SOURCE_FILE:2 user/eval100 26 | Compiler.java:5424 clojure.lang.Compiler.eval 27 | 28 | 29 | In stack traces printed by `pst`: 30 | 31 | * Java methods are described with the usual `name.space.ClassName.methodName` convention and Clojure functions with their own `name.space/function-name` convention. 32 | * Anonymous clojure functions are denoted by adding an `[fn]` to their enclosing, named function. 33 | * "Caused by" cascades are shown as in regular java stack traces. 34 | * Elements are vertically aligned for better readability. 35 | * Printing is directed to `*out*`. 36 | 37 | If you want to direct the printing to somewhere other than `*out*`, either use `pst-on` to specify the output location or `pst-str` to capture the printing as a string. 38 | 39 | The library also offers an API for programatically 'parsing' exceptions. This API is used internal for `pst` and can be used to e.g. improve development tools. Try for example: 40 | 41 | ```clj 42 | (use 'clj-stacktrace.core) 43 | (try 44 | ("nofn") 45 | (catch Exception e 46 | (parse-exception e))) 47 | ``` 48 | 49 | ## Leiningen 50 | 51 | If you use Leiningen, you can install clj-stacktrace on a user-wide 52 | basis. Just add the following to `~/.lein/profiles.clj`: 53 | 54 | ```clj 55 | {:user {:dependencies [[clj-stacktrace "0.2.8"]] 56 | :injections [(let [orig (ns-resolve (doto 'clojure.stacktrace require) 57 | 'print-cause-trace) 58 | new (ns-resolve (doto 'clj-stacktrace.repl require) 59 | 'pst)] 60 | (alter-var-root orig (constantly (deref new))))]}} 61 | ``` 62 | 63 | The `:injections` clause replaces the built-in stack trace printing 64 | with enhanced clj-stacktrace version; you can leave it out if you plan 65 | on invoking clj-stacktrace functions directly or are using tools which 66 | are already clj-stacktrace-aware. 67 | 68 | ## License 69 | 70 | Copyright © 2009-2013 Mark McGranaghan and contributors. 71 | 72 | Released under an MIT license. 73 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-stacktrace "0.2.8" 2 | :description "More readable stacktraces in Clojure programs." 3 | :url "https://github.com/mmcgrana/clj-stacktrace" 4 | :license {:name "MIT" :url "http://opensource.org/licenses/MIT"} 5 | :dependencies [[org.clojure/clojure "1.4.0"]]) 6 | -------------------------------------------------------------------------------- /src/clj_stacktrace/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-stacktrace.core 2 | (:require [clojure.string :as string])) 3 | 4 | (defn- clojure-code? 5 | "Returns true if the filename is non-null and indicates a clj source file." 6 | [class-name file] 7 | (or (re-find #"^user" class-name) 8 | (= file "NO_SOURCE_FILE") 9 | (and file (re-find #"\.clj$" file)))) 10 | 11 | (defn- clojure-ns 12 | "Returns the clojure namespace name implied by the bytecode class name." 13 | [class-name] 14 | (string/replace (or (get (re-find #"([^$]+)\$" class-name) 1) 15 | (get (re-find #"(.+)\.[^.]+$" class-name) 1)) 16 | #"_" "-")) 17 | 18 | ;; drop everything before and including the first $ 19 | ;; drop everything after and including and the second $ 20 | ;; drop any __xyz suffixes 21 | ;; sub _PLACEHOLDER_ for the corresponding char 22 | (def clojure-fn-subs 23 | [[#"^[^$]*\$" ""] 24 | [#"\$.*" ""] 25 | [#"__\d+.*" ""] 26 | [#"_QMARK_" "?"] 27 | [#"_BANG_" "!"] 28 | [#"_PLUS_" "+"] 29 | [#"_GT_" ">"] 30 | [#"_LT_" "<"] 31 | [#"_EQ_" "="] 32 | [#"_STAR_" "*"] 33 | [#"_SLASH_" "/"] 34 | [#"_" "-"]]) 35 | 36 | (defn- clojure-fn 37 | "Returns the clojure function name implied by the bytecode class name." 38 | [class-name] 39 | (reduce 40 | (fn [base-name [pattern sub]] (string/replace base-name pattern sub)) 41 | class-name 42 | clojure-fn-subs)) 43 | 44 | (defn- clojure-anon-fn? 45 | "Returns true if the bytecode class name implies an anonymous inner fn." 46 | [class-name] 47 | (boolean (re-find #"\$.*\$" class-name))) 48 | 49 | (defn parse-trace-elem 50 | "Returns a map of information about the java trace element. 51 | All returned maps have the keys: 52 | :file String of source file name. 53 | :line Number of source line number of the enclosing form. 54 | Additionally for elements from Java code: 55 | :java true, to indicate a Java elem. 56 | :class String of the name of the class to which the method belongs. 57 | Additionally for elements from Clojure code: 58 | :clojure true, to inidcate a Clojure elem. 59 | :ns String representing the namespace of the function. 60 | :fn String representing the name of the enclosing var for the function. 61 | :anon-fn true iff the function is an anonymous inner fn." 62 | [^StackTraceElement elem] 63 | (let [class-name (.getClassName elem) 64 | file (.getFileName elem) 65 | line (let [l (.getLineNumber elem)] (if (pos? l) l)) 66 | parsed {:file file :line line}] 67 | (if (clojure-code? class-name file) 68 | (assoc parsed 69 | :clojure true 70 | :ns (clojure-ns class-name) 71 | :fn (clojure-fn class-name) 72 | :anon-fn (clojure-anon-fn? class-name)) 73 | (assoc parsed 74 | :java true 75 | :class class-name 76 | :method (.getMethodName elem))))) 77 | 78 | (defn parse-trace-elems 79 | "Returns a seq of maps providing usefull information about the java stack 80 | trace elements. See parse-trace-elem." 81 | [elems] 82 | (map parse-trace-elem elems)) 83 | 84 | (defn- trim-redundant 85 | "Returns the portion of the tail of causer-elems that is not duplicated in 86 | the tail of caused-elems. This corresponds to the \"...26 more\" that you 87 | see at the bottom of regular trace dumps." 88 | [causer-parsed-elems caused-parsed-elems] 89 | (loop [rcauser-parsed-elems (reverse causer-parsed-elems) 90 | rcaused-parsed-elems (reverse caused-parsed-elems)] 91 | (if-let [rcauser-bottom (first rcauser-parsed-elems)] 92 | (if (= rcauser-bottom (first rcaused-parsed-elems)) 93 | (recur (next rcauser-parsed-elems) (next rcaused-parsed-elems)) 94 | (reverse rcauser-parsed-elems))))) 95 | 96 | (defn- parse-cause-exception 97 | "Like parse-exception, but for causing exceptions. The returned map has all 98 | of the same keys as the map returned by parse-exception, and one added one: 99 | :trimmed-elems A subset of :trace-elems representing the portion of the 100 | top of the stacktrace not shared with that of the caused 101 | exception." 102 | [^Throwable causer-e caused-parsed-elems] 103 | (let [parsed-elems (parse-trace-elems (.getStackTrace causer-e)) 104 | base {:class (class causer-e) 105 | :message (.getMessage causer-e) 106 | :trace-elems parsed-elems 107 | :trimmed-elems (trim-redundant parsed-elems caused-parsed-elems)}] 108 | (if-let [cause (.getCause causer-e)] 109 | (assoc base :cause (parse-cause-exception cause parsed-elems)) 110 | base))) 111 | 112 | (defn parse-exception 113 | "Returns a Clojure map providing usefull informaiton about the exception. 114 | The map has keys 115 | :class Class of the exception. 116 | :message Regular exception message string. 117 | :trace-elems Parsed stack trace elems, see parse-trace-elem. 118 | :cause See parse-cause-exception." 119 | [^Throwable e] 120 | (let [parsed-elems (parse-trace-elems (.getStackTrace e)) 121 | base {:class (class e) 122 | :message (.getMessage e) 123 | :trace-elems parsed-elems}] 124 | (if-let [cause (.getCause e)] 125 | (assoc base :cause (parse-cause-exception cause parsed-elems)) 126 | base))) 127 | -------------------------------------------------------------------------------- /src/clj_stacktrace/repl.clj: -------------------------------------------------------------------------------- 1 | (ns clj-stacktrace.repl 2 | (:use clj-stacktrace.core) 3 | (:require [clj-stacktrace.utils :as utils])) 4 | 5 | (def color-codes 6 | {:red "\033[31m" 7 | :green "\033[32m" 8 | :yellow "\033[33m" 9 | :blue "\033[34m" 10 | :magenta "\033[35m" 11 | :cyan "\033[36m" 12 | :default "\033[39m"}) 13 | 14 | (defn- colored 15 | [color? color text] 16 | (if color? 17 | (str (color-codes color) text (color-codes :default)) 18 | text)) 19 | 20 | (defn elem-color 21 | "Returns a symbol identifying the color appropriate for the given trace elem. 22 | :green All Java elems 23 | :yellow Any fn in the user or repl* namespaces (i.e. entered at REPL) 24 | :blue Any fn in clojure.* (e.g. clojure.core, clojure.contrib.*) 25 | :magenta Anything else - i.e. Clojure libraries and app code." 26 | [elem] 27 | (if (:java elem) 28 | (if (re-find #"^clojure\." (:class elem)) 29 | :cyan 30 | :blue) 31 | (cond (nil? (:ns elem)) :yellow 32 | (re-find #"^(user|repl)" (:ns elem)) :yellow 33 | (re-find #"^clojure\." (:ns elem)) :magenta 34 | :user-code :green))) 35 | 36 | (defn- guarded-fence [coll] 37 | (case (count coll) 38 | 0 0 39 | 1 (first coll) 40 | 2 (quot (+ (first coll) 41 | (second coll)) 2) 42 | (utils/fence coll))) 43 | 44 | (defn source-str [parsed] 45 | (if (and (:file parsed) (:line parsed)) 46 | (str (:file parsed) ":" (:line parsed)) 47 | "(Unknown Source)")) 48 | 49 | (defn clojure-method-str [parsed] 50 | (str (:ns parsed) "/" (:fn parsed) (if (:anon-fn parsed) "[fn]"))) 51 | 52 | (defn java-method-str [parsed] 53 | (str (:class parsed) "." (:method parsed))) 54 | 55 | (defn method-str [parsed] 56 | (if (:java parsed) (java-method-str parsed) (clojure-method-str parsed))) 57 | 58 | (defn pst-class-on [^java.io.Writer on color? ^Class class] 59 | (.append on ^String (colored color? :red (str (.getName class) ": "))) 60 | (.flush on)) 61 | 62 | (defn pst-message-on [^java.io.Writer on color? message] 63 | (.append on ^String (colored color? :red message)) 64 | (.append on "\n") 65 | (.flush on)) 66 | 67 | (defn pst-elem-str 68 | [color? parsed-elem print-width] 69 | (colored color? (elem-color parsed-elem) 70 | (str (utils/rjust print-width (source-str parsed-elem)) 71 | " " (method-str parsed-elem)))) 72 | 73 | (defn pst-elems-on 74 | [^java.io.Writer on color? parsed-elems & [source-width]] 75 | (let [print-width (+ 6 (or source-width 76 | (guarded-fence 77 | (sort 78 | (map #(.length ^String %) 79 | (map source-str parsed-elems))))))] 80 | (doseq [parsed-elem parsed-elems] 81 | (.append on ^String (pst-elem-str color? parsed-elem print-width)) 82 | (.append on "\n") 83 | (.flush on)))) 84 | 85 | (defn pst-caused-by-on 86 | [^java.io.Writer on color?] 87 | (.append on ^String (colored color? :red "Caused by: ")) 88 | (.flush on)) 89 | 90 | (defn- pst-cause-on 91 | [^java.io.Writer on color? exec source-width] 92 | (pst-caused-by-on on color?) 93 | (pst-class-on on color? (:class exec)) 94 | (pst-message-on on color? (:message exec)) 95 | (pst-elems-on on color? (:trimmed-elems exec) source-width) 96 | (if-let [cause (:cause exec)] 97 | (pst-cause-on on color? cause source-width))) 98 | 99 | (defn find-source-width 100 | "Returns the width of the longest source-string among all trace elems of the 101 | excp and its causes." 102 | [excp] 103 | (let [this-source-width (->> (:trace-elems excp) 104 | (map (comp count source-str)) 105 | (sort) 106 | (guarded-fence))] 107 | (if (not-empty (-> excp :cause :trace-elems)) 108 | (max this-source-width (find-source-width (:cause excp))) 109 | this-source-width))) 110 | 111 | (defn pst-on [on color? e] 112 | "Prints to the given Writer on a pretty stack trace for e which can be an exception 113 | or already parsed exception (clj-stacktrace.core/parse-exception). 114 | ANSI colored if color? is true." 115 | (let [exec (if (instance? Throwable e) 116 | (parse-exception e) 117 | e) 118 | source-width (find-source-width exec)] 119 | (pst-class-on on color? (:class exec)) 120 | (pst-message-on on color? (:message exec)) 121 | (pst-elems-on on color? (:trace-elems exec) source-width) 122 | (if-let [cause (:cause exec)] 123 | (pst-cause-on on color? cause source-width)))) 124 | 125 | (defn pst 126 | "Print to *out* a pretty stack trace for a (parsed) exception, by default *e." 127 | [& [e]] 128 | (pst-on *out* false (or e *e))) 129 | 130 | (defn pst-str 131 | "Like pst, but returns a string instead of printing that string to *out*" 132 | [& [e]] 133 | (let [sw (java.io.StringWriter.)] 134 | (pst-on sw false (or e *e)) 135 | (str sw))) 136 | 137 | (defn pst+ 138 | "Like pst, but with ANSI terminal color coding." 139 | [& [e]] 140 | (pst-on *out* true (or e *e))) 141 | -------------------------------------------------------------------------------- /src/clj_stacktrace/utils.clj: -------------------------------------------------------------------------------- 1 | (ns clj-stacktrace.utils) 2 | 3 | (defn rjust 4 | "If width is greater than the length of s, returns a new string 5 | of length width with s right justified within it, otherwise returns s." 6 | [width s] 7 | (format (str "%" width "s") s)) 8 | 9 | (defn quartile1 10 | "Compute the first quartile for the given collection according to 11 | Tukey (Hoaglin et al. 1983). coll must be sorted." 12 | ;; Hoaglin, D.; Mosteller, F.; and Tukey, J. (Ed.). 13 | ;; Understanding Robust and Exploratory Data Analysis. 14 | ;; New York: Wiley, pp. 39, 54, 62, 223, 1983. 15 | [coll] 16 | (let [c (count coll)] 17 | (nth coll (if (even? c) 18 | (/ (+ c 2) 4) 19 | (/ (+ c 3) 4))))) 20 | 21 | (defn quartile3 22 | "Compute the third quartile for the given collection according to 23 | Tukey (Hoaglin et al. 1983). coll must be sorted." 24 | ;; Hoaglin, D.; Mosteller, F.; and Tukey, J. (Ed.). 25 | ;; Understanding Robust and Exploratory Data Analysis. 26 | ;; New York: Wiley, pp. 39, 54, 62, 223, 1983. 27 | [coll] 28 | (let [c (count coll)] 29 | (nth coll (if (even? c) 30 | (/ (+ (* 3 c) 2) 4) 31 | (/ (inc (* 3 c)) 4))))) 32 | 33 | (defn fence 34 | "Compute the upper outer fence for the given coll. coll must be sorted." 35 | [coll] 36 | (let [q1 (quartile1 coll) 37 | q3 (quartile3 coll) 38 | iqr (- q3 q1)] 39 | (int (+ q3 (/ (* 3 iqr) 2))))) 40 | -------------------------------------------------------------------------------- /src/leiningen/hooks/clj_stacktrace_test.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.hooks.clj-stacktrace-test 2 | (:use [leiningen.compile :only [eval-in-project]] 3 | [robert.hooke :only [add-hook]])) 4 | 5 | (defn- hook-form [form project] 6 | (let [pst (if (:test-color (:clj-stacktrace project)) 7 | 'clj-stacktrace.repl/pst+ 8 | 'clj-stacktrace.repl/pst)] 9 | `(do (alter-var-root (resolve '~'clojure.stacktrace/print-cause-trace) 10 | (constantly @(resolve '~pst))) 11 | ~form))) 12 | 13 | (defn- add-stacktrace-hook [eval-in-project project form & [h s init]] 14 | (eval-in-project project (hook-form form project) 15 | h s `(do (try (require '~'clj-stacktrace.repl) 16 | (require '~'clojure.stacktrace) 17 | (catch Exception _#)) 18 | ~init))) 19 | 20 | (add-hook #'eval-in-project add-stacktrace-hook) 21 | -------------------------------------------------------------------------------- /test/clj_stacktrace/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-stacktrace.core-test 2 | (:use clojure.test) 3 | (:use clj-stacktrace.core) 4 | (:use clj-stacktrace.utils)) 5 | 6 | (def cases 7 | [["foo.bar$biz__123" "invoke" "bar.clj" 456 8 | {:clojure true :ns "foo.bar" :fn "biz" 9 | :file "bar.clj" :line 456 :anon-fn false}] 10 | 11 | ["foo.bar$biz_bat__123" "invoke" "bar.clj" 456 12 | {:clojure true :ns "foo.bar" :fn "biz-bat" 13 | :file "bar.clj" :line 456 :anon-fn false}] 14 | 15 | ["foo.bar$biz_bat_QMARK___448" "invoke" "bar.clj" 456 16 | {:clojure true :ns "foo.bar" :fn "biz-bat?" 17 | :file "bar.clj" :line 456 :anon-fn false}] 18 | 19 | ["foo.bar$biz_bat_QMARK___448$fn__456" "invoke" "bar.clj" 456 20 | {:clojure true :ns "foo.bar" :fn "biz-bat?" 21 | :file "bar.clj" :line 456 :anon-fn true}] 22 | 23 | ["foo.bar$repl$fn__5629.invoke" "invoke" "bar.clj" 456 24 | {:clojure true :ns "foo.bar" :fn "repl" 25 | :file "bar.clj" :line 456 :anon-fn true}] 26 | 27 | ["foo.bar$repl$read_eval_print__5624" "invoke" "bar.clj" 456 28 | {:clojure true :ns "foo.bar" :fn "repl" 29 | :file "bar.clj" :line 456 :anon-fn true}] 30 | 31 | ["foo.bar$biz__123$fn__456" "invoke" "bar.clj" 789 32 | {:clojure true :ns "foo.bar" :fn "biz" 33 | :file "bar.clj" :line 789 :anon-fn true}] 34 | 35 | ["foo.bar_bat$biz__123" "invoke" "bar.clj" 456 36 | {:clojure true :ns "foo.bar-bat" :fn "biz" 37 | :file "bar.clj" :line 456 :anon-fn false}] 38 | 39 | ["user$eval__345" "invoke" nil -1 40 | {:clojure true :ns "user" :fn "eval" 41 | :file nil :line nil :anon-fn false}] 42 | 43 | ["lamina.core.observable.ConstantObservable" "message" "observable.clj" 198 44 | {:clojure true :ns "lamina.core.observable" 45 | :fn "lamina.core.observable.ConstantObservable" 46 | :file "observable.clj" :line 198 :anon-fn false}] 47 | 48 | ["clojure.lang.Var" "invoke" "Var.java" 123 49 | {:java true :class "clojure.lang.Var" :method "invoke" 50 | :file "Var.java" :line 123}] 51 | 52 | ["clojure.proxy.space.SomeClass" "someMethod" "SomeClass.java" 123 53 | {:java true :class "clojure.proxy.space.SomeClass" :method "someMethod" 54 | :file "SomeClass.java" :line 123}] 55 | 56 | ["some.space.SomeClass" "someMethod" "SomeClass.java" 123 57 | {:java true :class "some.space.SomeClass" :method "someMethod" 58 | :file "SomeClass.java" :line 123}] 59 | 60 | ["some.space.SomeClass$SomeInner" "someMethod" "SomeClass.java" 123 61 | {:java true :class "some.space.SomeClass$SomeInner" :method "someMethod" 62 | :file "SomeClass.java" :line 123}] 63 | 64 | ["some.space.SomeClass" "someMethod" nil -1 65 | {:java true :class "some.space.SomeClass" :method "someMethod" 66 | :file nil :line nil}]]) 67 | 68 | (deftest test-parse-trace-elem 69 | (doseq [[class method file line parsed] cases 70 | :let [elem (StackTraceElement. class method file line)]] 71 | (is (= parsed (parse-trace-elem elem))))) 72 | 73 | (deftest test-trim-redundant 74 | (let [trim-fn (resolve 'clj-stacktrace.core/trim-redundant)] 75 | (is (= '(d c) (trim-fn '(d c b a) '(f e b a)))) 76 | (is (= '(c) (trim-fn '(c b a) '(f e b a)))) 77 | (is (= '(d c) (trim-fn '(d c b a) '(e b a)))))) 78 | 79 | (deftest test-parse-exception 80 | (try 81 | (eval '(/)) 82 | (catch Exception e 83 | (is (parse-exception e))))) 84 | -------------------------------------------------------------------------------- /test/clj_stacktrace/repl_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-stacktrace.repl-test 2 | (:use clojure.test) 3 | (:use clj-stacktrace.utils) 4 | (:use clj-stacktrace.repl)) 5 | 6 | (defmacro with-cascading-exception 7 | "Execute body in the context of a variable bound to an exception instance 8 | that includes a caused-by cascade." 9 | [binding-sym & body] 10 | `(try (first (lazy-seq (cons (/) nil))) 11 | (catch Exception e# 12 | (let [~binding-sym e#] 13 | ~@body)))) 14 | 15 | (deftest test-pst 16 | (with-cascading-exception e 17 | (is (with-out-str (pst e))) 18 | (binding [*e e] 19 | (is (with-out-str (pst)))))) 20 | 21 | (deftest test-pst-str 22 | (with-cascading-exception e 23 | (is (pst-str e)) 24 | (binding [*e e] 25 | (is (pst-str))))) 26 | 27 | (deftest test-pst+ 28 | (with-cascading-exception e 29 | (is (with-out-str (pst+ e))) 30 | (binding [*e e] 31 | (is (with-out-str (pst+)))))) 32 | --------------------------------------------------------------------------------