This commit is contained in:
Paulus Esterhazy 2025-05-08 14:42:25 +08:00 committed by GitHub
commit d1b238be0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 31 deletions

View file

@ -1,6 +1,7 @@
(ns babashka.impl.clojure.stacktrace (ns babashka.impl.clojure.stacktrace
{:no-doc true} {:no-doc true}
(:require [clojure.stacktrace :as stacktrace] (:require [clojure.stacktrace :as stacktrace]
[clojure.string :as str]
[sci.core :as sci])) [sci.core :as sci]))
(def sns (sci/create-ns 'clojure.stacktrace nil)) (def sns (sci/create-ns 'clojure.stacktrace nil))
@ -13,9 +14,62 @@
(defn new-var [var-sym f] (defn new-var [var-sym f]
(sci/new-var var-sym f {:ns sns})) (sci/new-var var-sym f {:ns sns}))
(defn right-pad [s n]
(let [n (- n (count s))]
(str s (str/join (repeat n " ")))))
(defn format-stacktrace [st]
(let [st (force st)
data (keep (fn [{:keys [:file :ns :line :column :sci/built-in
:local]
nom :name}]
(when (or line built-in)
{:name (str (if nom
(str ns "/" nom)
ns)
(when local
(str "#" local)))
:loc (str (or file
(if built-in
"<built-in>"
"<expr>"))
(when line
(str ":" line ":" column)))}))
st)
max-name (reduce max 0 (map (comp count :name) data))]
(mapv (fn [{:keys [:name :loc]}]
(str (right-pad name max-name) " - " loc))
data)))
(defn print-throwable
[^Throwable tr]
(when tr
(printf "%s: %s" (.getName (class tr)) (.getMessage tr))
(when-let [info (ex-data tr)]
(newline)
(pr info))))
(defn print-stack-trace [e]
(print-throwable (.getCause e))
(newline)
(->> e
(sci/stacktrace)
(format-stacktrace)
(run! println)))
(defn print-cause-trace
([tr] (print-cause-trace tr nil))
([^Throwable tr n]
(print-stack-trace tr)
(when-let [cause (.getCause tr)]
(print "Caused by: ")
(recur cause n))))
(def stacktrace-namespace (def stacktrace-namespace
{'root-cause (sci/copy-var stacktrace/root-cause sns) {'root-cause (sci/copy-var stacktrace/root-cause sns)
'print-trace-element (new-var 'print-trace-element (wrap-out stacktrace/print-trace-element)) 'print-trace-element (new-var 'print-trace-element (wrap-out stacktrace/print-trace-element))
'print-throwable (new-var 'print-throwable (wrap-out stacktrace/print-throwable)) 'print-throwable (new-var 'print-throwable (wrap-out stacktrace/print-throwable))
;; FIXME: expose print-stack-trace as well
'print-stack-trace (new-var 'print-stack-trace (wrap-out stacktrace/print-stack-trace)) 'print-stack-trace (new-var 'print-stack-trace (wrap-out stacktrace/print-stack-trace))
'print-cause-trace (new-var 'print-cause-trace (wrap-out stacktrace/print-cause-trace))}) ;; FIXME: should we make both regular and sci-aware stack printers available?
'print-cause-trace (new-var 'print-cause-trace (wrap-out print-cause-trace))})

View file

@ -234,7 +234,7 @@
babashka.impl.clojure.test babashka.impl.clojure.test
(:require (:require
[babashka.impl.common :refer [ctx]] [babashka.impl.common :refer [ctx]]
[clojure.stacktrace :as stack] [babashka.impl.clojure.stacktrace :as bbstack]
[clojure.template :as temp] [clojure.template :as temp]
[sci.core :as sci] [sci.core :as sci]
[sci.impl.namespaces :as sci-namespaces] [sci.impl.namespaces :as sci-namespaces]
@ -261,7 +261,6 @@
stack-trace-depth stack-trace-depth
(sci/new-dynamic-var '*stack-trace-depth* nil {:ns tns})) (sci/new-dynamic-var '*stack-trace-depth* nil {:ns tns}))
;;; GLOBALS USED BY THE REPORTING FUNCTIONS ;;; GLOBALS USED BY THE REPORTING FUNCTIONS
(def report-counters (sci/new-dynamic-var '*report-counters* nil {:ns tns})) ; bound to a ref of a map in test-ns (def report-counters (sci/new-dynamic-var '*report-counters* nil {:ns tns})) ; bound to a ref of a map in test-ns
@ -372,7 +371,7 @@
(print " actual: ") (print " actual: ")
(let [actual (:actual m)] (let [actual (:actual m)]
(if (instance? Throwable actual) (if (instance? Throwable actual)
(stack/print-cause-trace actual @stack-trace-depth) (bbstack/print-cause-trace actual @stack-trace-depth)
(prn actual))))) (prn actual)))))
(defmethod report-impl :summary [m] (defmethod report-impl :summary [m]
@ -390,8 +389,6 @@
(defmethod report-impl :begin-test-var [m]) (defmethod report-impl :begin-test-var [m])
(defmethod report-impl :end-test-var [m]) (defmethod report-impl :end-test-var [m])
;;; UTILITIES FOR ASSERTIONS ;;; UTILITIES FOR ASSERTIONS
(defn get-possibly-unbound-var (defn get-possibly-unbound-var
@ -453,8 +450,6 @@
:expected '~form, :actual value#})) :expected '~form, :actual value#}))
value#)) value#))
;;; ASSERTION METHODS ;;; ASSERTION METHODS
;; You don't call these, but you can add methods to extend the 'is' ;; You don't call these, but you can add methods to extend the 'is'
@ -530,21 +525,18 @@
:expected '~form, :actual e#}))) :expected '~form, :actual e#})))
e#)))) e#))))
(defmacro try-expr (defmacro try-expr
"Used by the 'is' macro to catch unexpected exceptions. "Used by the 'is' macro to catch unexpected exceptions.
You don't call this." You don't call this."
{:added "1.1"} {:added "1.1"}
[msg form] [msg form]
`(try ~(assert-expr msg form) `(try ~(assert-expr msg form)
(catch Throwable t# (catch ~(with-meta 'Exception {:sci/error true}) t#
(clojure.test/do-report {:file clojure.core/*file* (clojure.test/do-report {:file clojure.core/*file*
:line ~(:line (meta form)) :line ~(:line (meta form))
:type :error, :message ~msg, :type :error, :message ~msg,
:expected '~form, :actual t#})))) :expected '~form, :actual t#}))))
;;; ASSERTION MACROS ;;; ASSERTION MACROS
;; You use these in your tests. ;; You use these in your tests.
@ -602,8 +594,6 @@
`(binding [clojure.test/*testing-contexts* (conj clojure.test/*testing-contexts* ~string)] `(binding [clojure.test/*testing-contexts* (conj clojure.test/*testing-contexts* ~string)]
~@body)) ~@body))
;;; DEFINING TESTS ;;; DEFINING TESTS
(defmacro with-test (defmacro with-test
@ -618,7 +608,6 @@
`(doto ~definition (alter-meta! assoc :test (fn [] ~@body))) `(doto ~definition (alter-meta! assoc :test (fn [] ~@body)))
definition)) definition))
(defmacro deftest (defmacro deftest
"Defines a test function with no arguments. Test functions may call "Defines a test function with no arguments. Test functions may call
other tests, so tests may be composed. If you compose tests, you other tests, so tests may be composed. If you compose tests, you
@ -644,7 +633,6 @@
`(def ~(vary-meta name assoc :test `(fn [] ~@body) :private true) `(def ~(vary-meta name assoc :test `(fn [] ~@body) :private true)
(fn [] (test-var (var ~name)))))) (fn [] (test-var (var ~name))))))
(defmacro set-test (defmacro set-test
"Experimental. "Experimental.
Sets :test metadata of the named var to a fn with the given body. Sets :test metadata of the named var to a fn with the given body.
@ -656,8 +644,6 @@
(when @load-tests (when @load-tests
`(alter-meta! (var ~name) assoc :test (fn [] ~@body)))) `(alter-meta! (var ~name) assoc :test (fn [] ~@body))))
;;; DEFINING FIXTURES ;;; DEFINING FIXTURES
(defn- add-ns-meta (defn- add-ns-meta
@ -700,9 +686,6 @@
[fixtures] [fixtures]
(reduce compose-fixtures default-fixture fixtures)) (reduce compose-fixtures default-fixture fixtures))
;;; RUNNING TESTS: LOW-LEVEL FUNCTIONS ;;; RUNNING TESTS: LOW-LEVEL FUNCTIONS
(defn test-var-impl (defn test-var-impl
@ -766,8 +749,6 @@
(do-report {:type :end-test-ns, :ns ns-obj})) (do-report {:type :end-test-ns, :ns ns-obj}))
@@report-counters)) @@report-counters))
;;; RUNNING TESTS: HIGH-LEVEL FUNCTIONS ;;; RUNNING TESTS: HIGH-LEVEL FUNCTIONS
(defn run-tests (defn run-tests

View file

@ -125,3 +125,15 @@ true")))))
(bb "(clojure.test/testing-vars-str {:file \"x\" :line 1})") (bb "(clojure.test/testing-vars-str {:file \"x\" :line 1})")
"() (x:1)") "() (x:1)")
"includes explicit line number + file name in test report")) "includes explicit line number + file name in test report"))
(deftest is-should-include-name-of-function-test
(let [output (bb "(require '[clojure.test :as t]) (defn function-under-test [] (zero? nil)) (t/deftest foo (t/is (= false (function-under-test)))) (foo)")]
(is (str/includes? output "user/function-under-test"))))
(deftest is-should-throw-wrapped-exception-assert-test
(let [output (bb "(require '[clojure.test :as t]) (t/deftest foo (t/is (assert false))) (foo)")]
;; FIXME: doesn't work for assert yet
#_(is (str/includes? output ":type :sci/error"))))
;; FIXME: handle thrown?
;; FIXME: handle thrown-with-message?