Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions src/nrepl/middleware/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,31 @@
ns-atom (:ns session)
;; the actual evaluation runs interruptibly: interrupt! on `token` aborts
;; it (throws {:jolt/interrupted true}), which the worker catches below.
{:keys [value out ns err]}
{:keys [value out ns err ex err-msg ex-class root-ex-class]}
(jolt.host/run-interruptible token (fn [] (server/evaluate code (or ns-str @ns-atom))))]
(reset! ns-atom ns)
(when (seq out) (reply {"out" out}))
(if err
(let [e (last-eval-exception err)]
(reply {"err" (str err "\n")})
(cond
(nil? err)
(reply {"value" value "ns" ns "status" ["done"]})

;; a jolt whose evaluate renders the error the way JVM nREPL sends it:
;; err in clojure.main/err->msg form, ex/root-ex as class strings with
;; eval-error, then done on its own message
err-msg
(let [e (or ex (last-eval-exception err))]
(reply {"err" err-msg})
;; the throwable rides the eval-error response only, so a hook runs
;; once per error; wrap-caught dissocs it before the wire
(reply (cond-> {"ex" ex-class "root-ex" root-ex-class "status" ["eval-error"]}
(and caught? e) (assoc caught/throwable-key e)))
(reply {"status" ["done"]}))

:else
(let [e (last-eval-exception err)]
(reply {"err" (str err "\n")})
(reply (cond-> {"ex" (str err) "status" ["eval-error" "done"]}
(and caught? e) (assoc caught/throwable-key e))))
(reply {"value" value "ns" ns "status" ["done"]}))))
(and caught? e) (assoc caught/throwable-key e)))))))

(defn- spawn-worker [session]
;; A daemon, as nREPL's session threads are on the JVM: the worker waits on
Expand All @@ -71,8 +84,12 @@
(catch :default e
(if (:jolt/interrupted (ex-data e))
(reply {"status" ["interrupted" "done"]})
(do (reply {"err" (str (server/err-msg e) "\n")})
(reply {"status" ["eval-error" "done"]}))))
(if-let [err->msg (resolve 'jolt.nrepl/err->msg)]
(do (reply {"err" (err->msg e)})
(reply {"ex" (str (class e)) "status" ["eval-error"]})
(reply {"status" ["done"]}))
(do (reply {"err" (str (server/err-msg e) "\n")})
(reply {"status" ["eval-error" "done"]})))))
(finally (reset! (:current session) nil)))
(recur))))))
(.setDaemon true)
Expand Down
30 changes: 30 additions & 0 deletions test/nrepl/middleware/session_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,33 @@
(is (contains? (set (:sessions (h/combine (h/message t {:op "ls-sessions"})))) s))
(h/message t {:op "close" :session s})
(is (not (contains? (set (:sessions (h/combine (h/message t {:op "ls-sessions"})))) s))))))

(deftest eval-error-has-jvm-shape
(when h/jvm-error-shape?
(with-conn t
(let [s (h/new-session t)
rs (h/eval-code t "(/ 1 0)" :session s)
c (h/combine rs)]
(is (= "Execution error (ArithmeticException) at (REPL:1).\nDivide by zero\n" (:err c)))
(is (= "class java.lang.ArithmeticException" (:ex c)))
(is (= "class java.lang.ArithmeticException" (:root-ex c)))
(is (contains? (:status c) "eval-error"))
(is (contains? (:status c) "done"))
(testing "done arrives on its own message, after eval-error, as on the JVM"
(is (= ["done"] (get (last rs) :status))))))))

(deftest reader-error-has-no-stale-trace
(when h/jvm-error-shape?
(with-conn t
(let [s (h/new-session t)
c (h/combine (h/eval-code t "(defn broken [x]" :session s))]
(is (re-find #"^Syntax error reading source at \(REPL:1:\d+\)\.\nEOF while reading\n$" (:err c)))
(is (not (re-find #"trace:" (:err c))))))))

(deftest lazy-result-output-reaches-the-client
(when h/jvm-error-shape?
(with-conn t
(let [s (h/new-session t)
c (h/combine (h/eval-code t "(map (fn [x] (println \"side\" x) x) [1 2])" :session s))]
(is (= "side 1\nside 2\n" (:out c)))
(is (= ["(1 2)"] (:value c)))))))
6 changes: 6 additions & 0 deletions test/nrepl/test_helpers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@
(def combine nrepl/combine-responses)
(def values nrepl/response-values)
(def send-raw transport/send)

(def jvm-error-shape?
"True when jolt.nrepl/evaluate returns the JVM nREPL error shape (:err-msg,
:ex-class, :root-ex-class) — jolt with `jolt.nrepl/err->msg`. On an older
jolt the session middleware falls back to the message-only shape."
(boolean (resolve 'jolt.nrepl/err->msg)))
Loading