Skip to content

Commit cdd7e8e

Browse files
committed
Add tests for multi-process debugging
Tests for breakpoint sync (fork_bp_sync_test.rb): - Breakpoint set/deleted after fork syncs to child - Multiple children receive synced breakpoints - Catch breakpoint syncs to child - Late-forked child catches up - Stress test with binding.break Tests for well-known lock (wk_lock_test.rb): - Single-process debugging unaffected - fork_mode: :both uses ProcessGroup not well-known lock - Independent workers serialized by well-known lock
1 parent b5020e7 commit cdd7e8e

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

test/console/fork_bp_sync_test.rb

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../support/console_test_case'
4+
5+
module DEBUGGER__
6+
class ForkBpSyncTest < ConsoleTestCase
7+
8+
# Breakpoint set in parent AFTER fork is synced to child.
9+
# The child uses binding.break as a sync checkpoint -- when it enters
10+
# subsession there, bp_sync_check fires and picks up the new bp.
11+
def test_bp_set_after_fork_reaches_child
12+
code = <<~RUBY
13+
1| pid = fork do
14+
2| sleep 2
15+
3| binding.break # sync checkpoint
16+
4| a = 1 # bp target (set from parent after fork)
17+
5| end
18+
6| sleep 0.1
19+
7| binding.break # parent stops here after fork
20+
8| Process.waitpid pid
21+
RUBY
22+
23+
debug_code(code) do
24+
type 'c'
25+
assert_line_num 7
26+
type 'b 4' # set bp AFTER fork
27+
type 'c' # parent continues, bp_sync_publish writes to file
28+
assert_line_num 3 # child at sync checkpoint
29+
type 'c' # child continues, hits synced bp
30+
assert_line_num 4
31+
type 'c'
32+
end
33+
end
34+
35+
# Breakpoint deleted in parent after fork is removed from child.
36+
# Child inherits bp via COW, but parent deletes it and publishes.
37+
# When child syncs at its binding.break, the bp is reconciled away.
38+
def test_bp_deleted_after_fork_removed_from_child
39+
code = <<~RUBY
40+
1| pid = fork do
41+
2| sleep 2
42+
3| binding.break # sync checkpoint
43+
4| a = 1 # bp was inherited, then deleted via sync
44+
5| binding.break # child should stop here instead
45+
6| end
46+
7| sleep 0.1
47+
8| binding.break # parent stops here
48+
9| Process.waitpid pid
49+
RUBY
50+
51+
debug_code(code) do
52+
type 'b 4' # set bp (will be inherited by child via COW)
53+
type 'c'
54+
assert_line_num 8
55+
type 'del 0' # delete bp in parent
56+
type 'c' # parent continues, bp_sync_publish (empty set)
57+
assert_line_num 3 # child at sync checkpoint
58+
type 'c' # child continues, line 4 bp was removed by sync
59+
assert_line_num 5 # child skipped line 4
60+
type 'c'
61+
end
62+
end
63+
64+
# Catch breakpoint syncs to child process.
65+
def test_catch_bp_sync_after_fork
66+
code = <<~RUBY
67+
1| pid = fork do
68+
2| sleep 2
69+
3| binding.break # sync checkpoint
70+
4| raise "test_error"
71+
5| rescue
72+
6| binding.break # child stops after rescue
73+
7| end
74+
8| sleep 0.5
75+
9| binding.break
76+
10| Process.waitpid pid
77+
RUBY
78+
79+
debug_code(code) do
80+
type 'c'
81+
assert_line_num 9
82+
type 'catch RuntimeError' # set catch bp after fork
83+
type 'c' # parent continues, publish
84+
assert_line_num 3 # child at sync checkpoint
85+
type 'c'
86+
assert_line_num 4 # catch bp fires
87+
type 'c'
88+
assert_line_num 6 # child at rescue binding.break
89+
type 'c'
90+
end
91+
end
92+
93+
# Breakpoints set before fork work in child (inherited via COW + sync).
94+
# Regression test that sync doesn't break the existing behavior.
95+
def test_bp_before_fork_works_in_child
96+
code = <<~RUBY
97+
1| pid = fork do
98+
2| sleep 0.5
99+
3| a = 1
100+
4| end
101+
5| Process.waitpid pid
102+
RUBY
103+
104+
debug_code(code) do
105+
type 'b 3'
106+
type 'c'
107+
assert_line_num 3
108+
type 'c'
109+
end
110+
end
111+
112+
# Stress test with multiple children and binding.break.
113+
# Regression test that fork_mode: :both behavior isn't broken by sync.
114+
def test_bp_sync_stress
115+
code = <<~RUBY
116+
1| pids = 3.times.map do
117+
2| fork do
118+
3| sleep 1
119+
4| 2.times do
120+
5| binding.break
121+
6| end
122+
7| end
123+
8| end
124+
9| sleep 0.1
125+
10| binding.break
126+
11| pids.each { |pid| Process.waitpid pid rescue nil }
127+
RUBY
128+
129+
debug_code(code) do
130+
type 'c'
131+
assert_line_num 10
132+
type 'c'
133+
# 3 children x 2 iterations = 6 stops at line 5
134+
6.times do
135+
assert_line_num 5
136+
type 'c'
137+
end
138+
end
139+
end
140+
end
141+
end

test/console/wk_lock_test.rb

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../support/console_test_case'
4+
5+
module DEBUGGER__
6+
class WellKnownLockConsoleTest < ConsoleTestCase
7+
8+
# Single-process debugging is unaffected by the well-known lock.
9+
def test_single_process_unaffected
10+
code = <<~RUBY
11+
1| require "debug"
12+
2| DEBUGGER__.start
13+
3| a = 1
14+
4| binding.break
15+
5| b = 2
16+
RUBY
17+
18+
run_ruby(code) do
19+
assert_line_num 3
20+
type 'c'
21+
assert_line_num 4
22+
type 'c'
23+
end
24+
end
25+
26+
# The well-known lock is skipped when MultiProcessGroup is active
27+
# (fork_mode: :both via rdbg).
28+
def test_fork_mode_both_uses_process_group_not_wk_lock
29+
code = <<~RUBY
30+
1| pid = fork do
31+
2| sleep 0.5
32+
3| a = 1
33+
4| end
34+
5| Process.waitpid pid
35+
RUBY
36+
37+
debug_code(code) do
38+
type 'b 3'
39+
type 'c'
40+
assert_line_num 3
41+
type 'c'
42+
end
43+
end
44+
45+
# Independent workers that load the debugger after fork are serialized
46+
# by the well-known lock. Each worker enters the debugger in sequence.
47+
def test_independent_workers_serialized
48+
code = <<~RUBY
49+
1| 2.times do |i|
50+
2| fork do
51+
3| require "debug"
52+
4| DEBUGGER__.start(nonstop: true)
53+
5| sleep 1
54+
6| debugger
55+
7| $stdout.puts "worker_\#{i}_done"
56+
8| end
57+
9| end
58+
10| Process.waitall
59+
RUBY
60+
61+
run_ruby(code) do
62+
assert_line_num 6
63+
type 'c'
64+
assert_line_num 6
65+
type 'c'
66+
end
67+
end
68+
end
69+
end

0 commit comments

Comments
 (0)