Skip to content

Commit c2ef541

Browse files
vspinubbatsov
authored andcommittedOct 16, 2018
Implement debugger session and fix continuation
Fix #460, fix clojure-emacs/cider#1869, fix clojure-emacs/cider#2253 fix clojure-emacs/cider#1054
1 parent 79a9504 commit c2ef541

File tree

3 files changed

+151
-107
lines changed

3 files changed

+151
-107
lines changed
 

‎src/cider/nrepl/middleware/debug.clj

+92-79
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@
6767
;;; Variables and functions used for navigating between breakpoints.
6868
(def ^:dynamic *skip-breaks*
6969
"Map used to determine whether to skip a breakpoint.
70-
Don't set or examine this directly, it is bound in the session
71-
binding map, use `skip-breaks!` and `skip-breaks?` instead.
72-
Its value is discarded at the end each eval session."
70+
Don't set or examine this directly, it is bound in the session binding map,
71+
use `skip-breaks!` and `skip-breaks?` instead. Its value is reset at the
72+
beginning each eval session."
7373
(atom nil))
7474

7575
(defn- random-uuid-str
@@ -90,7 +90,9 @@
9090
"True if the breakpoint at coordinates should be skipped.
9191
9292
The `*skip-breaks*` map stores a `mode`, `coordinates`, the `code` that it
93-
applies to, and a `force?` flag. Behaviour depends on the `mode`:
93+
applies to, and a `force?` flag.
94+
95+
Behaviour depends on the `mode`:
9496
- :all - return true, skipping all breaks
9597
- :trace - return false, skip nothing
9698
- :deeper - return true if the given coordinates are deeper than the
@@ -101,29 +103,30 @@
101103
For :deeper and :before, if we are not in the same code (i.e. we have stepped
102104
into another instrumented function and code argument doesn't match old code in
103105
*skip-breaks*), then return the value of `force?`."
104-
[coordinates code]
105-
(if (seq coordinates)
106-
(when-let [{mode :mode skip-coords :coor
107-
code-to-skip :code force? :force?} @*skip-breaks*]
108-
(let [same-defn? (identical? code-to-skip code)]
109-
(case mode
110-
;; From :continue, skip everything.
111-
:all true
112-
;; From :trace, never skip.
113-
:trace false
114-
;; From :out, skip some breaks.
115-
:deeper (if same-defn?
116-
(let [parent (take (count skip-coords) coordinates)]
117-
(and (seq= skip-coords parent)
118-
(> (count coordinates) (count parent))))
119-
force?)
120-
;; From :here, skip some breaks.
121-
:before (if same-defn?
122-
(ins/coord< coordinates skip-coords)
123-
force?))))
124-
;; We don't breakpoint top-level sexps, because their return value
125-
;; is already displayed anyway.
126-
true))
106+
[coor STATE__]
107+
(or (and STATE__ @(:skip STATE__))
108+
(if (seq coor)
109+
(when-let [{mode :mode skip-coords :coor
110+
code-to-skip :code force? :force?} @*skip-breaks*]
111+
(let [same-defn? (identical? code-to-skip (get-in STATE__ [:msg :code]))]
112+
(case mode
113+
;; From :continue, skip everything.
114+
:all true
115+
;; From :trace, never skip.
116+
:trace false
117+
;; From :out, skip some breaks.
118+
:deeper (if same-defn?
119+
(let [parent (take (count skip-coords) coor)]
120+
(and (seq= skip-coords parent)
121+
(> (count coor) (count parent))))
122+
force?)
123+
;; From :here, skip some breaks.
124+
:before (if same-defn?
125+
(ins/coord< coor skip-coords)
126+
force?))))
127+
;; We don't breakpoint top-level sexps, because their return value
128+
;; is already displayed anyway.
129+
true)))
127130

128131
(defn skip-breaks!
129132
"Set the value of *skip-breaks* for the top-level breakpoint.
@@ -281,8 +284,6 @@ this map (identified by a key), and will `dissoc` it afterwards."}
281284
(eval-with-locals (or code (read-debug-input dbg-state :expression prompt))
282285
dbg-state)))
283286

284-
(declare read-debug-command)
285-
286287
(defn- debug-inspect
287288
"Inspect `inspect-value`."
288289
[page-size inspect-value]
@@ -305,6 +306,7 @@ this map (identified by a key), and will `dissoc` it afterwards."}
305306

306307
(def debug-commands
307308
{"c" :continue
309+
"C" :Continue
308310
"e" :eval
309311
"h" :here
310312
"i" :in
@@ -336,51 +338,58 @@ this map (identified by a key), and will `dissoc` it afterwards."}
336338
provide additional parameters. For instance, if this map has a :code entry,
337339
its value is used for operations such as :eval, which would otherwise
338340
interactively prompt for an expression."
339-
[value dbg-state]
340-
(let [commands (cond-> debug-commands
341-
(not (map? *msg*)) (dissoc "q")
342-
(nil? (:locals dbg-state)) (dissoc "e" "j" "l" "p")
343-
(cljs/grab-cljs-env *msg*) identity)
344-
response-raw (read-debug-input dbg-state commands nil)
345-
dbg-state (dissoc dbg-state :inspect)
346-
347-
{:keys [code coord response page-size force?]
348-
:or {page-size 32}} (if (map? response-raw)
349-
response-raw
350-
{:response response-raw})]
351-
(reset! step-in-to-next? false)
352-
(case response
353-
:next value
354-
:in (do (reset! step-in-to-next? true)
355-
value)
356-
:continue (do (skip-breaks! :all)
357-
value)
358-
:out (do (skip-breaks! :deeper (butlast (:coor dbg-state)) (:code dbg-state) force?)
359-
value)
360-
:here (do (skip-breaks! :before coord (:code dbg-state) force?)
361-
value)
362-
:stacktrace (do (debug-stacktrace)
363-
(recur value dbg-state))
364-
:trace (do (skip-breaks! :trace)
365-
value)
366-
:locals (->> (debug-inspect page-size (:locals dbg-state))
367-
(assoc dbg-state :inspect)
368-
(recur value))
369-
:inspect (try-if-let [val (read-eval-expression "Inspect value: " dbg-state code)]
370-
(->> (debug-inspect page-size val)
341+
[coor value locals STATE__]
342+
(loop [value value
343+
dbg-state (assoc (:msg STATE__)
344+
:debug-value (pr-short value)
345+
:coor coor
346+
:locals locals)]
347+
(let [commands (cond-> debug-commands
348+
(not (map? *msg*)) (dissoc "q")
349+
(nil? (:locals dbg-state)) (dissoc "e" "j" "l" "p")
350+
(cljs/grab-cljs-env *msg*) identity)
351+
response-raw (read-debug-input dbg-state commands nil)
352+
dbg-state (dissoc dbg-state :inspect)
353+
354+
{:keys [code coord response page-size force?]
355+
:or {page-size 32}} (if (map? response-raw)
356+
response-raw
357+
{:response response-raw})]
358+
(reset! step-in-to-next? false)
359+
(case response
360+
:next value
361+
:in (do (reset! step-in-to-next? true)
362+
value)
363+
:continue (do (reset! (:skip STATE__) true)
364+
value)
365+
:Continue (do (skip-breaks! :all)
366+
value)
367+
:out (do (skip-breaks! :deeper (butlast (:coor dbg-state)) (:code dbg-state) force?)
368+
value)
369+
:here (do (skip-breaks! :before coord (:code dbg-state) force?)
370+
value)
371+
:stacktrace (do (debug-stacktrace)
372+
(recur value dbg-state))
373+
:trace (do (skip-breaks! :trace)
374+
value)
375+
:locals (->> (debug-inspect page-size (:locals dbg-state))
371376
(assoc dbg-state :inspect)
372377
(recur value))
373-
(recur value dbg-state))
374-
:inject (try-if-let [val (read-eval-expression "Expression to inject: " dbg-state code)]
375-
val
376-
(recur value dbg-state))
377-
:eval (try-if-let [val (read-eval-expression "Expression to evaluate: " dbg-state code)]
378-
(recur value (assoc dbg-state :debug-value (pr-short val)))
379-
(recur value dbg-state))
380-
:quit (abort!)
381-
(do (abort!)
382-
(throw (ex-info "Invalid input from `read-debug-input`."
383-
{:response-raw response-raw}))))))
378+
:inspect (try-if-let [val (read-eval-expression "Inspect value: " dbg-state code)]
379+
(->> (debug-inspect page-size val)
380+
(assoc dbg-state :inspect)
381+
(recur value))
382+
(recur value dbg-state))
383+
:inject (try-if-let [val (read-eval-expression "Expression to inject: " dbg-state code)]
384+
val
385+
(recur value dbg-state))
386+
:eval (try-if-let [val (read-eval-expression "Expression to evaluate: " dbg-state code)]
387+
(recur value (assoc dbg-state :debug-value (pr-short val)))
388+
(recur value dbg-state))
389+
:quit (abort!)
390+
(do (abort!)
391+
(throw (ex-info "Invalid input from `read-debug-input`."
392+
{:response-raw response-raw})))))))
384393

385394
(defn print-step-indented [depth form value]
386395
(print (apply str (repeat (dec depth) "| ")))
@@ -473,6 +482,9 @@ this map (identified by a key), and will `dissoc` it afterwards."}
473482
;; top-level sexp, a (= col 1) is much more likely to be
474483
;; wrong than right.
475484
(update :column #(if (= % 1) 0 %))))
485+
;; the coor of first form is used as the debugger session id
486+
:session-id (atom nil)
487+
:skip (atom false)
476488
:forms @*tmp-forms*}]
477489
~@body))
478490

@@ -481,31 +493,32 @@ this map (identified by a key), and will `dissoc` it afterwards."}
481493
[form dbg-state original-form]
482494
`(with-initial-debug-bindings
483495
(breakpoint-if-interesting
484-
~form ~dbg-state ~original-form)))
496+
~form ~dbg-state ~original-form)))
485497

486498
(defn break
487499
"Breakpoint function.
488500
Send the result of form and its coordinates to the client and wait for
489501
response with `read-debug-command`'."
490502
[coor val locals STATE__]
503+
(if-let [first-coor @(:session-id STATE__)]
504+
(when (= first-coor coor)
505+
(reset! (:skip STATE__) false))
506+
(reset! (:session-id STATE__) coor))
491507
(cond
492-
(skip-breaks? coor (get-in STATE__ [:msg :code])) val
508+
(skip-breaks? coor STATE__) val
493509
;; The length of `coor` is a good indicator of current code
494510
;; depth.
495511
(= (:mode @*skip-breaks*) :trace)
496512
(do (print-step-indented (count coor) (get-in STATE__ [:forms coor]) val)
497513
val)
498514
;; Most common case - ask for input.
499515
:else
500-
(read-debug-command val (assoc (:msg STATE__)
501-
:debug-value (pr-short val)
502-
:coor coor
503-
:locals locals))))
516+
(read-debug-command coor val locals STATE__)))
504517

505518
(defn apply-instrumented-maybe
506519
"Apply var-fn or its instrumented version to args."
507520
[var-fn args coor STATE__]
508-
(let [stepin (step-in? var-fn coor (get-in STATE__ [:msg :code]))]
521+
(let [stepin (step-in? var-fn coor STATE__)]
509522
(apply (if stepin
510523
(::instrumented (meta var-fn))
511524
var-fn)

‎test/clj/cider/nrepl/middleware/debug_integration_test.clj

+24-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797

9898
(def-debug-op :next)
9999
(def-debug-op :continue)
100+
(def-debug-op :Continue)
100101
(def-debug-op :in)
101102

102103
(defmethod debugger-send :out [_ & [force?]]
@@ -216,6 +217,28 @@
216217
(<-- {:value "8"})
217218
(<-- {:status ["done"]})))
218219

220+
(deftest continue-non-stop
221+
222+
(--> :eval "(ns user.test.debug)")
223+
(<-- {:ns "user.test.debug"})
224+
(<-- {:status ["done"]})
225+
226+
(--> :eval "(do (defn foo [a] #dbg (* a 2))
227+
(defn boo [] (dotimes [n 5] (foo n)) :fin))")
228+
(<-- {:value "#'user.test.debug/boo"})
229+
(<-- {:status ["done"]})
230+
231+
(testing "continue-non-stop"
232+
(--> :eval "(boo)")
233+
(<-- {:debug-value "0" :coor [1 3 1]}) ; a in foo
234+
(--> :continue)
235+
(<-- {:debug-value "1" :coor [1 3 1]}) ; a in foo
236+
(--> :continue)
237+
(<-- {:debug-value "2" :coor [1 3 1]}) ; a in foo
238+
(--> :Continue)
239+
(<-- {:value ":fin"})
240+
(<-- {:status ["done"]})))
241+
219242
(deftest call-instrumented-fn-when-stepping-out-test
220243
;; When we step out of a form, instrumented functions that are
221244
;; called should still be debugged.
@@ -566,6 +589,6 @@
566589
(.startsWith file "jar:file:")
567590
(.endsWith file "/nrepl/server.clj"))
568591

569-
(--> :continue)
592+
(--> :Continue)
570593
(<-- {:value "{:transport 23}"})
571594
(<-- {:status ["done"]}))

‎test/clj/cider/nrepl/middleware/debug_test.clj

+35-27
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010
(def ^:const bfkey :cider.nrepl.middleware.util.instrument/breakfunction)
1111

12+
;; dummy STATE__
13+
(defn STATE []
14+
{:msg {:original-ns "user"}
15+
:session-id (atom nil)
16+
:skip (atom false)})
17+
1218
(deftest irrelevant-return-value-test
1319
(are [x] (let [exp (clojure.walk/macroexpand-all x)]
1420
(= exp (clojure.walk/macroexpand-all `(d/breakpoint-if-interesting ~exp [] nil))))
@@ -40,12 +46,13 @@
4046
(is (not (#'d/skip-breaks? [1 2] nil)))
4147
(is (not (#'d/skip-breaks? [2 2 1] nil)))
4248

43-
(let [code "(foo (bar blah x))"]
49+
(let [code "(foo (bar blah x))"
50+
state (assoc-in (STATE) [:msg :code] code)]
4451
(#'d/skip-breaks! :deeper [1 2] code nil)
45-
(is (d/skip-breaks? [] code))
46-
(is (not (#'d/skip-breaks? [1 2] code)))
47-
(is (not (#'d/skip-breaks? [2 2 1] code)))
48-
(is (#'d/skip-breaks? [1 2 3] code)))))
52+
(is (d/skip-breaks? [] state))
53+
(is (not (#'d/skip-breaks? [1 2] state)))
54+
(is (not (#'d/skip-breaks? [2 2 1] state)))
55+
(is (#'d/skip-breaks? [1 2 3] state)))))
4956

5057
(defn- send-override
5158
[value]
@@ -91,30 +98,30 @@
9198
;; Check functionality
9299
(with-redefs [d/abort! (constantly :aborted)
93100
t/send (send-override :quit)]
94-
(is (= :aborted (#'d/read-debug-command 'value (add-locals {})))))
101+
(is (= :aborted (#'d/read-debug-command nil 'value (locals) (STATE)))))
95102
(with-redefs [t/send (send-override :next)]
96-
(is (= 'value (#'d/read-debug-command 'value (add-locals {})))))
103+
(is (= 'value (#'d/read-debug-command nil 'value (locals) (STATE)))))
97104
(binding [*msg* {:session (atom {})}
98105
d/*skip-breaks* (atom {:mode :all})]
99106
(with-redefs [t/send (send-override :continue)]
100-
(is (= 'value (#'d/read-debug-command 'value (add-locals {}))))
101-
(is (#'d/skip-breaks? nil nil))))
107+
(is (= 'value (#'d/read-debug-command nil 'value (locals) (STATE))))
108+
(is (#'d/skip-breaks? nil (STATE)))))
102109
(binding [*msg* {:session (atom {})}
103110
d/*skip-breaks* (atom {:mode :all})]
104111
(with-redefs [t/send (send-override :out)]
105-
(is (= 'value (#'d/read-debug-command 'value (add-locals {:coor [1 2 3]}))))
106-
(is (#'d/skip-breaks? [1 2 3] nil))
107-
(is (#'d/skip-breaks? [1 2 4] nil))
108-
(is (not (#'d/skip-breaks? [1 2] nil)))))
112+
(is (= 'value (#'d/read-debug-command [1 2 3] 'value (locals) (STATE))))
113+
(is (#'d/skip-breaks? [1 2 3] (STATE)))
114+
(is (#'d/skip-breaks? [1 2 4] (STATE)))
115+
(is (not (#'d/skip-breaks? [1 2] (STATE))))))
109116
(with-redefs [t/send (send-override :inject)]
110-
(is (= :inject (#'d/read-debug-command 'value (add-locals {}))))))
117+
(is (= :inject (#'d/read-debug-command nil 'value (locals) (STATE))))))
111118

112119
(deftest read-debug-command-eval-test
113120
(let [replies (atom [:eval 100 :next])]
114121
(with-redefs [t/send (fn [trans {:keys [key]}]
115122
(deliver (@d/promises key) (str (first @replies)))
116123
(swap! replies rest))]
117-
(is (= 'value (#'d/read-debug-command 'value (add-locals {})))))))
124+
(is (= 'value (#'d/read-debug-command nil 'value (locals) (STATE)))))))
118125

119126
(deftest read-eval-expression-test
120127
(reset! d/debugger-message {})
@@ -192,7 +199,7 @@
192199

193200
(deftest breakpoint
194201
;; Map merging
195-
(with-redefs [d/read-debug-command (fn [v e] (assoc e :value v))
202+
(with-redefs [d/read-debug-command (fn [_ v _ s] (assoc (:msg s) :value v))
196203
d/debugger-message (atom [:fake])
197204
d/*skip-breaks* (atom nil)]
198205
(binding [*msg* {:session (atom {})
@@ -201,12 +208,11 @@
201208
:file :file
202209
:line :line
203210
:column :column}]
204-
(let [m (eval `(d/with-initial-debug-bindings
205-
(d/breakpoint-if-interesting (inc 10) {:coor [6]} ~'(inc 10))))]
211+
(let [form `(d/with-initial-debug-bindings
212+
(d/breakpoint-if-interesting (inc 10) {:coor [6]} ~'(inc 10)))
213+
m (eval form)]
206214
(are [k v] (= (k m) v)
207215
:value 11
208-
:debug-value "11"
209-
:coor [6]
210216
:file :file
211217
:line :line
212218
:column :column
@@ -215,14 +221,16 @@
215221
(reset! d/debugger-message [:fake])
216222
;; Locals capturing
217223
(is (= (:value (eval `(d/with-initial-debug-bindings
218-
(let [~'x 10] (d/breakpoint-if-interesting
219-
(locals)
220-
{:coor [1]} nil)))))
224+
(let [~'x 10]
225+
(d/breakpoint-if-interesting
226+
(locals)
227+
{:coor [1]} nil)))))
221228
'{x 10}))
222229
;; Top-level sexps are not debugged, just returned.
223230
(is (= (eval `(d/with-initial-debug-bindings
224-
(let [~'x 10] (d/breakpoint-if-interesting
225-
(locals)
226-
{:coor []}
227-
nil))))
231+
(let [~'x 10]
232+
(d/breakpoint-if-interesting
233+
(locals)
234+
{:coor []}
235+
nil))))
228236
'{x 10})))))

0 commit comments

Comments
 (0)
Please sign in to comment.