Skip to content

Conditional breakpoints #303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

grammati
Copy link
Contributor

Not entirely ready, but I would like to get some more eyes on this and see whether others agree with the approach.

The feature is conditional breakpoints - that is, breakpoints that will only enter the debugger when an expression is truthy.

Conditions are attached to forms as :break/when metadata. Example:

(for [i (range 3)]
  #dbg ^{:break/when (odd? i)
  (inc i))

Evaluating this, you will stop in the debugger only once, when i is 1.

However, the primary use case (at least for me) is likely to be instrumenting a function with a conditional breakpoint. I have implemented this in cider as C-u C-u C-M-x, which prompts for an expression. This can be an expression of the function arguments. The form is then sent to cider-nrepl with #dbg ^{:break/when (the expression)} prefixed.

Making this work required a slightly hairy special-case that moves :break/when metadata down from the defn form and onto the forms inside the body of the macroexpanded function.

To get some confidence that I have the slightest idea what I'm doing, I wrote a bunch of new tests, in a new test namespace. These interact with cider-nrepl only by sending and receiving nrepl messages, just as a client (i.e. emacs) would.

Thanks everyone, please let me know what you think.

@Malabarba
Copy link
Member

Wow. First of all, thanks a ton! :-)
This is a feature that's been requested several times already, so it's very welcome.
Also, thanks so much for writing so many tests! :-)

I'll make some comments directly in the code, but overall I like how you've implemented it.

@@ -139,10 +143,10 @@
;;; `wrap-debug` receives an initial message from the client, stores
;;; it in `debugger-message`, and `breakpoint` answers it when asking
;;; for input.
(def debugger-message
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this change, but I'd rather leave it to a separate commit (unless it has some relation to this feature).

@grammati
Copy link
Contributor Author

I pushed a PR for a rudimentary version of the front-end portion, here: clojure-emacs/cider#1591

;; If there is a condition and it is falsy, we need to skip
;; the current level (:deeper than parent coor), but only
;; once. Next time, we need to test the condition again.
(binding [*skip-breaks* (if skip?#
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need :deeper? Isn't that the same as skipping everything inside this binding?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking at the time was that if the "skipped" form calls another function which is instrumented, we do want to stop in the debugger, and that using :all would prevent that. I can add some test cases to verify.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!
And yes, a test case would be nice.

@grammati grammati force-pushed the conditional-break-squashed branch from e9e00a3 to 4a7ab0e Compare February 28, 2016 16:24
@grammati
Copy link
Contributor Author

@Malabarba, I pushed a commit that addresses all your comments, I think.

I pared back the functionality to be just the basic conditional break. I removed the special-casing of defns that moved the condition down into the body of the fn. I can re-introduce that in another PR, where we can discuss the best interface.

I also added a few more tests, including one (unrelated to conditional breakpoints) that I think should pass but does not. Can you have a look at call-instrumented-fn-when-stepping-out? Perhaps my expectations of how the debugger should behave are just wrong.

Thanks!

@Malabarba
Copy link
Member

Ok, thanks! I'll have a look at the tests when I get a chance. 👍

Also some new, high-level tests for the debugger.
@grammati grammati force-pushed the conditional-break-squashed branch from 4a7ab0e to df91bb7 Compare March 3, 2016 14:31
@grammati
Copy link
Contributor Author

grammati commented Mar 3, 2016

I rebased this onto master and pushed again

@Malabarba
Copy link
Member

Ok. Looks like your tests are good. The only 2 failures are the cljs tests which are also failing on master. I've also tested your code locally and it seems to work well.
I'm going to give this a final read and merge. 👍

Malabarba added a commit that referenced this pull request Mar 3, 2016
@Malabarba Malabarba merged commit 97a0113 into clojure-emacs:master Mar 3, 2016
@Malabarba
Copy link
Member

Ok. This should now be on clojar.
Thanks again for the feature and specially for the tests. :-)

@grammati grammati deleted the conditional-break-squashed branch April 22, 2017 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants