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
30 changes: 20 additions & 10 deletions lib/debug/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,6 @@ def register_default_command
# * Step in. Resume the program until next breakable point.
# * `s[tep] <n>`
# * Step in, resume the program at `<n>`th breakable point.
# * `s[tep] into <name>` or `s[tep] into /regexp/`
# * Stop at the beggining of method `<name>` or the name matched to `/regexp/`
register_command 's', 'step',
repeat: true,
cancel_auto_continue: true,
Expand Down Expand Up @@ -466,6 +464,21 @@ def register_default_command
step_command :finish, arg
end

# * `u[ntil]`
# * Similar to `next` command, but only stop later lines or the end of the current frame.
# * Similar to gdb's `advance` command.
# * `u[ntil] <[file:]line>
# * Run til the program reaches given location or the end of the current frame.
# * `u[ntil] <name>
# * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
register_command 'u', 'until',
repeat: true,
cancel_auto_continue: true,
postmortem: false do |arg|

step_command :until, arg
end

# * `c[ontinue]`
# * Resume the program.
register_command 'c', 'continue',
Expand Down Expand Up @@ -1105,6 +1118,11 @@ def repl_open_vscode
end

def step_command type, arg
if type == :until
leave_subsession [:step, type, arg]
return
end

case arg
when nil, /\A\d+\z/
if type == :in && @tc.recorder&.replaying?
Expand All @@ -1121,14 +1139,6 @@ def step_command type, arg
iter = $2&.to_i
request_tc [:step, type, iter]
end
when /\Ainto\s+(\S+)(\s+(\d+))?\z/
pat = $1
iter = $3&.to_i
if /\A\/(.+)\/\z/ =~ pat
pat = Regexp.new($1)
end

request_tc [:step, :into, pat, iter]
else
@ui.puts "Unknown option: #{arg}"
:retry
Expand Down
48 changes: 41 additions & 7 deletions lib/debug/thread_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -863,13 +863,6 @@ def wait_next_action_
break
end

when :into
pat, iter = args[1], args[2]
step_tp iter, [:call, :c_call] do |tp|
pat === tp.callee_id.to_s
end
break

when :next
frame = @target_frames.first
path = frame.location.absolute_path || "!eval:#{frame.path}"
Expand Down Expand Up @@ -908,6 +901,47 @@ def wait_next_action_
end
break

when :until
location = iter&.strip
frame = @target_frames.first
depth = frame.frame_depth
target_location_label = frame.location.base_label

case location
when nil, /\A(?:(.+):)?(\d+)\z/
file = $1
line = ($2 || frame.location.lineno + 1).to_i

step_tp nil, [:line, :return] do |tp|
if tp.event == :line
next true if file && tp.path.end_with?(file)
next true if tp.lineno >= line
else
next true if depth >= DEBUGGER__.frame_depth - 3 &&
caller_locations(2, 1).first.label == target_location_label
# TODO: imcomplete condition
end
end
else
pat = location
if /\A\/(.+)\/\z/ =~ pat
pat = Regexp.new($1)
end

step_tp nil, [:call, :c_call, :return] do |tp|
case tp.event
when :call, :c_call
next true if pat === tp.callee_id.to_s
else # :return, :b_return
next true if depth >= DEBUGGER__.frame_depth - 3 &&
caller_locations(2, 1).first.label == target_location_label
# TODO: imcomplete condition
end
end
end

break

when :back
iter = iter || 1
if @recorder&.can_step_back?
Expand Down
60 changes: 46 additions & 14 deletions test/console/control_flow_commands_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,6 @@ def test_step_with_number_goes_to_the_next_nth_statement
end
end

def test_step_into
debug_code program do
type 'step into name'
assert_line_num 7
type 'step into xyzzy' # doesn't match
end

debug_code program do
type 'step into /.ame/'
assert_line_num 7
type 'step into xyzzy' # doesn't match
end
end

def test_next_goes_to_the_next_line
debug_code(program) do
type 'b 11'
Expand Down Expand Up @@ -419,6 +405,52 @@ def test_finish_should_be_canceled
end
end

class UntilTest < ConsoleTestCase
def program
<<~RUBY
1| 3.times do
2| a = 1
3| b = 2
4| end
5| c = 3
6| def foo
7| x = 1
8| end
9| foo
RUBY
end

def test_until_line
debug_code program do
type 'u 2'
assert_line_num 2
type 'u'
assert_line_num 3
type 'u'
assert_line_num 5
type 'c'
end
end

def test_until_line_overrun
debug_code program do
type 'u 2'
assert_line_num 2
type 'u 100'
end
end

def test_until_method
debug_code program do
type 'u foo'
assert_line_num 7
type 'u bar'
assert_line_num 8
type 'c'
end
end
end

#
# Tests that next/finish work for a deep call stack.
# We use different logic for computing frame depth when the call stack is above/below 4096.
Expand Down