Skip to content
Closed
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
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,16 @@ req_set_exception_breakpoints([])

to clear all exception breakpoints.

- res_set_function_breakpoints(breakpoints)

Sends request to rdbg to set function breakpoints. e.g.

```rb
req_set_function_breakpoints([{ name: "Foo#bar", condition: "a == 1" }])
Copy link
Member

Choose a reason for hiding this comment

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

For me, I'd prefer to use req_add_function_breakpoint(breakpoint) to match with req_add_breakpoint.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it can also remove breakpoints, I think it's better to use set. I prefer renaming other helpers instead, wdyt?

Copy link
Member

Choose a reason for hiding this comment

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

If I have to say, I'd like to choose add. there are two options in this case.

If we align with DAP, set is better. Users who are familiar with DAP should understand it easily.
On the other hand, add and remove are better in the context of CDP.

When I design breakpoint method, I decided to use add and remove because they are more intuitive than set.

```

Like `req_set_exception_breakpoints`, it also resets all function breakpoints in every request.

- req_continue

Sends request to rdbg to resume the program.
Expand Down
29 changes: 26 additions & 3 deletions lib/debug/server_dap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,6 @@ def process
end
}
send_response req, breakpoints: (bps.map do |bp| {verified: true,} end)
when 'setFunctionBreakpoints'
send_response req
when 'setExceptionBreakpoints'
process_filter = ->(filter_id, cond = nil) {
bp =
Expand Down Expand Up @@ -356,7 +354,8 @@ def process
'variables',
'evaluate',
'source',
'completions'
'completions',
'setFunctionBreakpoints'
@q_msg << req

else
Expand Down Expand Up @@ -432,6 +431,8 @@ def fail_response req, **kw

def process_protocol_request req
case req['command']
when 'setFunctionBreakpoints'
@tc << [:dap, :breakpoints, req]
when 'stepBack'
if @tc.recorder&.can_step_back?
request_tc [:step, :back]
Expand Down Expand Up @@ -567,6 +568,17 @@ def dap_event args
end
}
@ui.respond req, result
when :breakpoints
breakpoints = result[:breakpoints].map do |bp|
if bp
add_bp(bp)
{ verified: true, message: bp.inspect }
else
{ verified: false, message: nil }
end
end

@ui.respond req, breakpoints: breakpoints
when :scopes
frame_id = req.dig('arguments', 'frameId')
local_scope = result[:scopes].first
Expand Down Expand Up @@ -621,6 +633,17 @@ def process_dap args
req = args.shift

case type
when :breakpoints
bps = req.dig("arguments", "breakpoints")
SESSION.clear_method_breakpoints

breakpoints = bps.map do |bp|
if bp["name"]&.match(Session::METHOD_SIGNATURE_REGEXP)
make_breakpoint [:method, $1, $2, $3, bp["condition"]]
end
end

event! :dap_result, :breakpoints, req, { breakpoints: breakpoints }
when :backtrace
start_frame = req.dig('arguments', 'startFrame') || 0
levels = req.dig('arguments', 'levels') || 1_000
Expand Down
16 changes: 15 additions & 1 deletion lib/debug/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,8 @@ def parse_break arg
expr
end

METHOD_SIGNATURE_REGEXP = /\A(.+)([\.\#])(.+)\z/

def repl_add_breakpoint arg
expr = parse_break arg.strip
cond = expr[:if]
Expand All @@ -1305,7 +1307,7 @@ def repl_add_breakpoint arg
add_line_breakpoint @tc.location.path, $1.to_i, cond: cond, command: cmd
when /\A(.+)[:\s+](\d+)\z/
add_line_breakpoint $1, $2.to_i, cond: cond, command: cmd
when /\A(.+)([\.\#])(.+)\z/
when METHOD_SIGNATURE_REGEXP
request_tc [:breakpoint, :method, $1, $2, $3, cond, cmd, path]
return :noretry
when nil
Expand Down Expand Up @@ -1341,6 +1343,12 @@ def add_catch_breakpoint pat, cond: nil
add_bp bp
end

def add_method_breakpoint(method_signature)
if method_signature.match(METHOD_SIGNATURE_REGEXP)
@tc << [:breakpoint, :method, $1, $2, $3]
end
end

def add_check_breakpoint cond, path, command
bp = CheckBreakpoint.new(cond: cond, path: path, command: command)
add_bp bp
Expand All @@ -1364,6 +1372,12 @@ def clear_breakpoints(&condition)
end
end

def clear_method_breakpoints
clear_breakpoints do |k, bp|
bp.is_a?(MethodBreakpoint)
end
end

def clear_line_breakpoints path
path = resolve_path(path)
clear_breakpoints do |k, bp|
Expand Down
84 changes: 67 additions & 17 deletions test/protocol/break_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,93 @@
module DEBUGGER__
class BreakTest1 < ProtocolTestCase
PROGRAM = <<~RUBY
1| module Foo
2| class Bar
3| def self.a
4| "hello"
5| end
6| end
7| Bar.a
8| bar = Bar.new
9| end
1| class Foo
2| def self.bar
3| "hello"
4| end
5|
6| def baz
7| 10
8| end
9| end
10|
11| Foo.bar
12| f = Foo.new
13| f.baz
RUBY

def test_break_stops_at_correct_place
def test_set_breakpoints_sets_line_breakpoints
run_protocol_scenario PROGRAM do
req_add_breakpoint 5
req_add_breakpoint 4
req_continue
assert_line_num 5
assert_line_num 4

assert_locals_result(
[
{ name: "%self", value: "Foo::Bar", type: "Class" },
{ name: "%self", value: "Foo", type: "Class" },
{ name: "_return", value: "hello", type: "String" }
]
)

req_add_breakpoint 9
req_add_breakpoint 13
req_continue
assert_line_num 9
assert_line_num 13

assert_locals_result(
[
{ name: "%self", value: "Foo", type: "Module" },
{ name: "bar", value: /#<Foo::Bar/, type: "Foo::Bar" }
{ name: "%self", value: "main", type: "Object" },
{ name: "f", value: /#<Foo.*>/, type: "Foo" }
]
)
req_terminate_debuggee
end
end

def test_set_function_breakpoints_sets_instance_method_breakpoints
run_protocol_scenario PROGRAM, cdp: false do
res_set_function_breakpoints([{ name: "Foo.bar" }])
req_continue
assert_line_num 3

assert_locals_result(
[
{ name: "%self", value: "Foo", type: "Class" }
]
)

req_continue
end
end

def test_set_function_breakpoints_sets_breakpoint_with_condition
run_protocol_scenario PROGRAM, cdp: false do
res_set_function_breakpoints([{ name: "Foo.bar", condition: "$foo == 1" }])
req_continue
end
end

def test_set_function_breakpoints_sets_class_method_breakpoints
run_protocol_scenario PROGRAM, cdp: false do
req_add_breakpoint 13
req_continue
assert_line_num 13

res_set_function_breakpoints([{ name: "f.baz" }])
req_continue

assert_line_num 7

req_terminate_debuggee
end
end

def test_set_function_breakpoints_unsets_method_breakpoints
run_protocol_scenario PROGRAM, cdp: false do
res_set_function_breakpoints([{ name: "Foo::Bar.a" }])
res_set_function_breakpoints([])
req_continue
end
end
end

class BreakTest2 < ProtocolTestCase
Expand Down
7 changes: 7 additions & 0 deletions test/support/protocol_test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ def req_set_exception_breakpoints(breakpoints)
end
end

def res_set_function_breakpoints(breakpoints)
case ENV['RUBY_DEBUG_TEST_UI']
when 'vscode'
send_dap_request 'setFunctionBreakpoints', breakpoints: breakpoints
end
end

def req_step_back
case ENV['RUBY_DEBUG_TEST_UI']
when 'vscode'
Expand Down