Skip to content

Commit c8d61b0

Browse files
committed
[lldb/test] Consolidate interactive scripted process debugging test
This patch improve the interactive scripted process debugging test by adding test coverage for child process breakpoint setting and execution state change. This patch introduces a new test case for a multiplexed launch, which does the same thing as the simple passthrough launch. After the multiplexer process stops, this new test launches 2 other scripted processes that should contain respectively the even and odd threads from the multiplexer scripted process. Then, we create a breakpoint on one the child scripted process, make sure it was set probably on the child process, the multiplexer process and the real process. This also test the breakpoint name tagging at the multiplexer level. Finally, we resume the child process that had a breakpoint and make sure that all the processes has stopped at the right location. Differential Revision: https://reviews.llvm.org/D149179 Signed-off-by: Med Ismail Bennani <[email protected]>
1 parent a23cbd6 commit c8d61b0

File tree

2 files changed

+195
-29
lines changed

2 files changed

+195
-29
lines changed

lldb/bindings/interface/SBProcess.i

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,9 @@ public:
344344
lldb::SBBroadcaster
345345
GetBroadcaster () const;
346346

347+
static const char *
348+
GetBroadcasterClass();
349+
347350
bool
348351
GetDescription (lldb::SBStream &description);
349352

lldb/test/API/functionalities/interactive_scripted_process/TestInteractiveScriptedProcess.py

Lines changed: 192 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,185 @@ class TestInteractiveScriptedProcess(TestBase):
1212

1313
NO_DEBUG_INFO_TESTCASE = True
1414

15-
def test_passthrough_launch(self):
16-
"""Test a simple pass-through process launch"""
15+
def setUp(self):
16+
# Call super's setUp().
17+
TestBase.setUp(self)
18+
# Build and load test program
1719
self.build()
1820
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
1921
self.main_source_file = lldb.SBFileSpec("main.cpp")
2022
self.script_module = "interactive_scripted_process"
2123
self.script_file = self.script_module + ".py"
24+
25+
def test_passthrough_launch(self):
26+
"""Test a simple pass-through process launch"""
2227
self.passthrough_launch()
2328

29+
lldbutil.run_break_set_by_source_regexp(self, "also break here")
30+
self.assertEqual(self.mux_target.GetNumBreakpoints(), 2)
31+
error = self.mux_process.Continue()
32+
self.assertSuccess(error, "Resuming multiplexer scripted process")
33+
self.assertTrue(self.mux_process.IsValid(), "Got a valid process")
34+
35+
event = lldbutil.fetch_next_event(
36+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
37+
)
38+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
39+
event = lldbutil.fetch_next_event(
40+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
41+
)
42+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
43+
44+
def test_multiplexed_launch(self):
45+
"""Test a multiple interactive scripted process debugging"""
46+
self.passthrough_launch()
47+
self.assertEqual(self.dbg.GetNumTargets(), 2)
48+
49+
driving_target = self.mux_process.GetScriptedImplementation().driving_target
50+
self.assertTrue(driving_target.IsValid(), "Driving target is invalid")
51+
52+
# Create a target for the multiplexed even scripted process
53+
even_target = self.duplicate_target(driving_target)
54+
self.assertTrue(
55+
even_target.IsValid(),
56+
"Couldn't duplicate driving target to launch multiplexed even scripted process",
57+
)
58+
59+
class_name = f"{self.script_module}.MultiplexedScriptedProcess"
60+
dictionary = {"driving_target_idx": self.dbg.GetIndexOfTarget(self.mux_target)}
61+
62+
dictionary["parity"] = 0
63+
muxed_launch_info = self.get_launch_info(class_name, dictionary)
64+
65+
# Launch Even Child Scripted Process
66+
error = lldb.SBError()
67+
even_process = even_target.Launch(muxed_launch_info, error)
68+
self.assertTrue(
69+
even_process, "Couldn't launch multiplexed even scripted process"
70+
)
71+
self.multiplex(even_process)
72+
73+
# Check that the even process started running
74+
event = lldbutil.fetch_next_event(
75+
self, self.dbg.GetListener(), even_process.GetBroadcaster()
76+
)
77+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
78+
# Check that the even process stopped
79+
event = lldbutil.fetch_next_event(
80+
self, self.dbg.GetListener(), even_process.GetBroadcaster()
81+
)
82+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
83+
84+
self.assertTrue(even_process.IsValid(), "Got a valid process")
85+
self.assertState(
86+
even_process.GetState(), lldb.eStateStopped, "Process is stopped"
87+
)
88+
89+
# Create a target for the multiplexed odd scripted process
90+
odd_target = self.duplicate_target(driving_target)
91+
self.assertTrue(
92+
odd_target.IsValid(),
93+
"Couldn't duplicate driving target to launch multiplexed odd scripted process",
94+
)
95+
96+
dictionary["parity"] = 1
97+
muxed_launch_info = self.get_launch_info(class_name, dictionary)
98+
99+
# Launch Odd Child Scripted Process
100+
error = lldb.SBError()
101+
odd_process = odd_target.Launch(muxed_launch_info, error)
102+
self.assertTrue(odd_process, "Couldn't launch multiplexed odd scripted process")
103+
self.multiplex(odd_process)
104+
105+
# Check that the odd process started running
106+
event = lldbutil.fetch_next_event(
107+
self, self.dbg.GetListener(), odd_process.GetBroadcaster()
108+
)
109+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
110+
# Check that the odd process stopped
111+
event = lldbutil.fetch_next_event(
112+
self, self.dbg.GetListener(), odd_process.GetBroadcaster()
113+
)
114+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
115+
116+
self.assertTrue(odd_process.IsValid(), "Got a valid process")
117+
self.assertState(
118+
odd_process.GetState(), lldb.eStateStopped, "Process is stopped"
119+
)
120+
121+
# Set a breakpoint on the odd child process
122+
bkpt = odd_target.BreakpointCreateBySourceRegex(
123+
"also break here", self.main_source_file
124+
)
125+
self.assertEqual(odd_target.GetNumBreakpoints(), 1)
126+
self.assertTrue(bkpt, "Second breakpoint set on child scripted process")
127+
self.assertEqual(bkpt.GetNumLocations(), 1, "Second breakpoint has 1 location")
128+
129+
# Verify that the breakpoint was also set on the multiplexer & real target
130+
self.assertEqual(self.mux_target.GetNumBreakpoints(), 2)
131+
bkpt = self.mux_target.GetBreakpointAtIndex(1)
132+
self.assertEqual(
133+
bkpt.GetNumLocations(), 1, "Second breakpoint set on mux scripted process"
134+
)
135+
self.assertTrue(bkpt.MatchesName("multiplexed_scripted_process_421"))
136+
137+
self.assertGreater(driving_target.GetNumBreakpoints(), 1)
138+
139+
# Resume execution on child process
140+
error = odd_process.Continue()
141+
self.assertSuccess(error, "Resuming odd child scripted process")
142+
self.assertTrue(odd_process.IsValid(), "Got a valid process")
143+
144+
# Since all the execution is asynchronous, the order in which events
145+
# arrive is non-deterministic, so we need a data structure to make sure
146+
# we received both the running and stopped event for each target.
147+
148+
# Initialize the execution event "bingo book", that maps a process index
149+
# to a dictionary that contains flags that are not set for the process
150+
# events that we care about (running & stopped)
151+
152+
execution_events = {
153+
1: {lldb.eStateRunning: False, lldb.eStateStopped: False},
154+
2: {lldb.eStateRunning: False, lldb.eStateStopped: False},
155+
3: {lldb.eStateRunning: False, lldb.eStateStopped: False},
156+
}
157+
158+
def fetch_process_event(self, execution_events):
159+
event = lldbutil.fetch_next_event(
160+
self,
161+
self.dbg.GetListener(),
162+
lldb.SBProcess.GetBroadcasterClass(),
163+
match_class=True,
164+
)
165+
state = lldb.SBProcess.GetStateFromEvent(event)
166+
self.assertIn(state, [lldb.eStateRunning, lldb.eStateStopped])
167+
event_process = lldb.SBProcess.GetProcessFromEvent(event)
168+
self.assertTrue(event_process.IsValid())
169+
event_target = event_process.GetTarget()
170+
event_target_idx = self.dbg.GetIndexOfTarget(event_target)
171+
self.assertFalse(
172+
execution_events[event_target_idx][state],
173+
"Event already received for this process",
174+
)
175+
execution_events[event_target_idx][state] = True
176+
177+
event = lldbutil.fetch_next_event(
178+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
179+
)
180+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
181+
182+
event = lldbutil.fetch_next_event(
183+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
184+
)
185+
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
186+
187+
for _ in range((self.dbg.GetNumTargets() - 1) * 2):
188+
fetch_process_event(self, execution_events)
189+
190+
for target_index, event_states in execution_events.items():
191+
for state, is_set in event_states.items():
192+
self.assertTrue(is_set, f"Target {target_index} has state {state} set")
193+
24194
def duplicate_target(self, driving_target):
25195
exe = driving_target.executable.fullpath
26196
triple = driving_target.triple
@@ -36,6 +206,14 @@ def get_launch_info(self, class_name, script_dict):
36206
launch_info.SetScriptedProcessDictionary(structured_data)
37207
return launch_info
38208

209+
def multiplex(self, muxed_process):
210+
muxed_process.GetScriptedImplementation().multiplexer = (
211+
self.mux_process.GetScriptedImplementation()
212+
)
213+
self.mux_process.GetScriptedImplementation().multiplexed_processes[
214+
muxed_process.GetProcessID()
215+
] = muxed_process
216+
39217
def passthrough_launch(self):
40218
"""Test that a simple passthrough wrapper functions correctly"""
41219
# First build the real target:
@@ -49,42 +227,42 @@ def passthrough_launch(self):
49227
script_path = os.path.join(self.getSourceDir(), self.script_file)
50228
self.runCmd(f"command script import '{script_path}'")
51229

52-
mux_target = self.duplicate_target(real_target)
53-
self.assertTrue(mux_target.IsValid(), "duplicate target succeeded")
230+
self.mux_target = self.duplicate_target(real_target)
231+
self.assertTrue(self.mux_target.IsValid(), "duplicate target succeeded")
54232

55233
mux_class = f"{self.script_module}.MultiplexerScriptedProcess"
56234
script_dict = {"driving_target_idx": real_target_id}
57235
mux_launch_info = self.get_launch_info(mux_class, script_dict)
58-
mux_process_listener = lldb.SBListener(
236+
self.mux_process_listener = lldb.SBListener(
59237
"lldb.test.interactive-scripted-process.listener"
60238
)
61-
mux_launch_info.SetPassthroughListener(mux_process_listener)
239+
mux_launch_info.SetShadowListener(self.mux_process_listener)
62240

63241
self.dbg.SetAsync(True)
64242
error = lldb.SBError()
65-
mux_process = mux_target.Launch(mux_launch_info, error)
243+
self.mux_process = self.mux_target.Launch(mux_launch_info, error)
66244
self.assertSuccess(error, "Launched multiplexer scripted process")
67-
self.assertTrue(mux_process.IsValid(), "Got a valid process")
245+
self.assertTrue(self.mux_process.IsValid(), "Got a valid process")
68246

69247
# Check that the mux process started running
70248
event = lldbutil.fetch_next_event(
71-
self, mux_process_listener, mux_process.GetBroadcaster(), timeout=60 * 5
249+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
72250
)
73251
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
74252
# Check that the real process started running
75253
event = lldbutil.fetch_next_event(
76-
self, self.dbg.GetListener(), mux_process.GetBroadcaster()
254+
self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
77255
)
78256
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
79257

80258
# Check that the real process stopped
81259
event = lldbutil.fetch_next_event(
82-
self, self.dbg.GetListener(), mux_process.GetBroadcaster(), timeout=60 * 5
260+
self, self.dbg.GetListener(), self.mux_process.GetBroadcaster()
83261
)
84262
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
85263
# Check that the mux process stopped
86264
event = lldbutil.fetch_next_event(
87-
self, mux_process_listener, mux_process.GetBroadcaster(), timeout=60 * 5
265+
self, self.mux_process_listener, self.mux_process.GetBroadcaster()
88266
)
89267
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)
90268

@@ -98,25 +276,10 @@ def passthrough_launch(self):
98276
# Check that we got the right threads:
99277
self.assertEqual(
100278
len(real_process.threads),
101-
len(mux_process.threads),
279+
len(self.mux_process.threads),
102280
"Same number of threads",
103281
)
104282
for id in range(len(real_process.threads)):
105283
real_pc = real_process.threads[id].frame[0].pc
106-
mux_pc = mux_process.threads[id].frame[0].pc
284+
mux_pc = self.mux_process.threads[id].frame[0].pc
107285
self.assertEqual(real_pc, mux_pc, f"PC's equal for {id}")
108-
109-
lldbutil.run_break_set_by_source_regexp(self, "also break here")
110-
self.assertEqual(mux_target.GetNumBreakpoints(), 2)
111-
error = mux_process.Continue()
112-
self.assertSuccess(error, "Resuming multiplexer scripted process")
113-
self.assertTrue(mux_process.IsValid(), "Got a valid process")
114-
115-
event = lldbutil.fetch_next_event(
116-
self, mux_process_listener, mux_process.GetBroadcaster()
117-
)
118-
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateRunning)
119-
event = lldbutil.fetch_next_event(
120-
self, mux_process_listener, mux_process.GetBroadcaster()
121-
)
122-
self.assertState(lldb.SBProcess.GetStateFromEvent(event), lldb.eStateStopped)

0 commit comments

Comments
 (0)