197 changes: 172 additions & 25 deletions lldb/test/API/tools/lldb-server/TestGdbRemoteFork.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ class TestGdbRemoteFork(gdbremote_testcase.GdbRemoteTestCaseBase):

fork_regex = ("[$]T05thread:p([0-9a-f]+)[.]([0-9a-f]+);.*"
"{}:p([0-9a-f]+)[.]([0-9a-f]+).*")
fork_regex_nonstop = ("%Stop:T05thread:p([0-9a-f]+)[.]([0-9a-f]+);.*"
"{}:p([0-9a-f]+)[.]([0-9a-f]+).*")
fork_capture = {1: "parent_pid", 2: "parent_tid",
3: "child_pid", 4: "child_tid"}

def start_fork_test(self, args, variant="fork"):
def start_fork_test(self, args, variant="fork", nonstop=False):
self.build()
self.prep_debug_monitor_and_inferior(inferior_args=args)
self.add_qSupported_packets(["multiprocess+",
Expand All @@ -22,11 +24,24 @@ def start_fork_test(self, args, variant="fork"):
self.reset_test_sequence()

# continue and expect fork
self.test_sequence.add_log_lines([
"read packet: $c#00",
{"direction": "send", "regex": self.fork_regex.format(variant),
"capture": self.fork_capture},
], True)
if nonstop:
self.test_sequence.add_log_lines([
"read packet: $QNonStop:1#00",
"send packet: $OK#00",
"read packet: $c#00",
"send packet: $OK#00",
{"direction": "send",
"regex": self.fork_regex_nonstop.format(variant),
"capture": self.fork_capture},
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
else:
self.test_sequence.add_log_lines([
"read packet: $c#00",
{"direction": "send", "regex": self.fork_regex.format(variant),
"capture": self.fork_capture},
], True)
ret = self.expect_gdbremote_sequence()
self.reset_test_sequence()

Expand All @@ -45,9 +60,9 @@ def test_fork_multithreaded(self):
], True)
self.expect_gdbremote_sequence()

def fork_and_detach_test(self, variant):
def fork_and_detach_test(self, variant, nonstop=False):
parent_pid, parent_tid, child_pid, child_tid = (
self.start_fork_test([variant], variant))
self.start_fork_test([variant], variant, nonstop=nonstop))

# detach the forked child
self.test_sequence.add_log_lines([
Expand Down Expand Up @@ -77,6 +92,20 @@ def test_fork(self):
], True)
self.expect_gdbremote_sequence()

@add_test_categories(["fork"])
def test_fork_nonstop(self):
parent_pid, _ = self.fork_and_detach_test("fork", nonstop=True)

# resume the parent
self.test_sequence.add_log_lines([
"read packet: $c#00",
"send packet: $OK#00",
"send packet: %Stop:W00;process:{}#00".format(parent_pid),
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
self.expect_gdbremote_sequence()

@add_test_categories(["fork"])
def test_vfork(self):
parent_pid, parent_tid = self.fork_and_detach_test("vfork")
Expand All @@ -93,9 +122,32 @@ def test_vfork(self):
], True)
self.expect_gdbremote_sequence()

def fork_and_follow_test(self, variant):
@add_test_categories(["fork"])
def test_vfork_nonstop(self):
parent_pid, parent_tid = self.fork_and_detach_test("vfork",
nonstop=True)

# resume the parent
self.test_sequence.add_log_lines([
"read packet: $c#00",
"send packet: $OK#00",
{"direction": "send",
"regex": r"%Stop:T05thread:p{}[.]{}.*vforkdone.*".format(
parent_pid, parent_tid),
},
"read packet: $vStopped#00",
"send packet: $OK#00",
"read packet: $c#00",
"send packet: $OK#00",
"send packet: %Stop:W00;process:{}#00".format(parent_pid),
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
self.expect_gdbremote_sequence()

def fork_and_follow_test(self, variant, nonstop=False):
parent_pid, parent_tid, child_pid, child_tid = (
self.start_fork_test([variant], variant))
self.start_fork_test([variant], variant, nonstop=nonstop))

# switch to the forked child
self.test_sequence.add_log_lines([
Expand All @@ -113,18 +165,37 @@ def fork_and_follow_test(self, variant):
"send packet: $OK#00",
# then resume the child
"read packet: $c#00",
"send packet: $W00;process:{}#00".format(child_pid),
], True)

if nonstop:
self.test_sequence.add_log_lines([
"send packet: $OK#00",
"send packet: %Stop:W00;process:{}#00".format(child_pid),
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
else:
self.test_sequence.add_log_lines([
"send packet: $W00;process:{}#00".format(child_pid),
], True)
self.expect_gdbremote_sequence()

@add_test_categories(["fork"])
def test_fork_follow(self):
self.fork_and_follow_test("fork")

@add_test_categories(["fork"])
def test_fork_follow_nonstop(self):
self.fork_and_follow_test("fork", nonstop=True)

@add_test_categories(["fork"])
def test_vfork_follow(self):
self.fork_and_follow_test("vfork")

@add_test_categories(["fork"])
def test_vfork_follow_nonstop(self):
self.fork_and_follow_test("vfork", nonstop=True)

@add_test_categories(["fork"])
def test_select_wrong_pid(self):
self.build()
Expand Down Expand Up @@ -191,10 +262,9 @@ def test_detach_current(self):
], True)
self.expect_gdbremote_sequence()

@add_test_categories(["fork"])
def test_detach_all(self):
def detach_all_test(self, nonstop=False):
parent_pid, parent_tid, child_pid, child_tid = (
self.start_fork_test(["fork"]))
self.start_fork_test(["fork"], nonstop=nonstop))

self.test_sequence.add_log_lines([
# double-check our PIDs
Expand All @@ -213,6 +283,14 @@ def test_detach_all(self):
], True)
self.expect_gdbremote_sequence()

@add_test_categories(["fork"])
def test_detach_all(self):
self.detach_all_test()

@add_test_categories(["fork"])
def test_detach_all_nonstop(self):
self.detach_all_test(nonstop=True)

@add_test_categories(["fork"])
def test_kill_all(self):
parent_pid, _, child_pid, _ = self.start_fork_test(["fork"])
Expand All @@ -230,10 +308,51 @@ def test_kill_all(self):
self.assertEqual(set([ret["pid1"], ret["pid2"]]),
set([parent_pid, child_pid]))

def vkill_test(self, kill_parent=False, kill_child=False):
@add_test_categories(["fork"])
def test_kill_all_nonstop(self):
parent_pid, _, child_pid, _ = self.start_fork_test(["fork"],
nonstop=True)

exit_regex = "X09;process:([0-9a-f]+)"
# Depending on a potential race, the second kill may make it into
# the async queue before we issue vStopped or after. In the former
# case, we should expect the exit status in reply to vStopped.
# In the latter, we should expect an OK response (queue empty),
# followed by another async notification.
vstop_regex = "[$](OK|{})#.*".format(exit_regex)
self.test_sequence.add_log_lines([
# kill all processes
"read packet: $k#00",
"send packet: $OK#00",
{"direction": "send", "regex": "%Stop:{}#.*".format(exit_regex),
"capture": {1: "pid1"}},
"read packet: $vStopped#00",
{"direction": "send", "regex": vstop_regex,
"capture": {1: "vstop_reply", 2: "pid2"}},
], True)
ret = self.expect_gdbremote_sequence()
pid1 = ret["pid1"]
if ret["vstop_reply"] == "OK":
self.reset_test_sequence()
self.test_sequence.add_log_lines([
{"direction": "send", "regex": "%Stop:{}#.*".format(exit_regex),
"capture": {1: "pid2"}},
], True)
ret = self.expect_gdbremote_sequence()
pid2 = ret["pid2"]
self.reset_test_sequence()
self.test_sequence.add_log_lines([
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
self.expect_gdbremote_sequence()
self.assertEqual(set([ret["pid1"], ret["pid2"]]),
set([parent_pid, child_pid]))

def vkill_test(self, kill_parent=False, kill_child=False, nonstop=False):
assert kill_parent or kill_child
parent_pid, parent_tid, child_pid, child_tid = (
self.start_fork_test(["fork"]))
self.start_fork_test(["fork"], nonstop=nonstop))

if kill_parent:
self.test_sequence.add_log_lines([
Expand Down Expand Up @@ -269,17 +388,21 @@ def test_vkill_parent(self):
def test_vkill_both(self):
self.vkill_test(kill_parent=True, kill_child=True)

def resume_one_test(self, run_order, use_vCont=False):
@add_test_categories(["fork"])
def test_vkill_both_nonstop(self):
self.vkill_test(kill_parent=True, kill_child=True, nonstop=True)

def resume_one_test(self, run_order, use_vCont=False, nonstop=False):
parent_pid, parent_tid, child_pid, child_tid = (
self.start_fork_test(["fork", "trap"]))
self.start_fork_test(["fork", "trap"], nonstop=nonstop))

parent_expect = [
"[$]T05thread:p{}.{};.*".format(parent_pid, parent_tid),
"[$]W00;process:{}#.*".format(parent_pid),
"T05thread:p{}.{};.*".format(parent_pid, parent_tid),
"W00;process:{}#.*".format(parent_pid),
]
child_expect = [
"[$]T05thread:p{}.{};.*".format(child_pid, child_tid),
"[$]W00;process:{}#.*".format(child_pid),
"T05thread:p{}.{};.*".format(child_pid, child_tid),
"W00;process:{}#.*".format(child_pid),
]

for x in run_order:
Expand All @@ -304,9 +427,17 @@ def resume_one_test(self, run_order, use_vCont=False):
"send packet: $OK#00",
"read packet: $c#00",
], True)
self.test_sequence.add_log_lines([
{"direction": "send", "regex": expect},
], True)
if nonstop:
self.test_sequence.add_log_lines([
"send packet: $OK#00",
{"direction": "send", "regex": "%Stop:" + expect},
"read packet: $vStopped#00",
"send packet: $OK#00",
], True)
else:
self.test_sequence.add_log_lines([
{"direction": "send", "regex": "[$]" + expect},
], True)
# if at least one process remained, check both PIDs
if parent_expect or child_expect:
self.test_sequence.add_log_lines([
Expand Down Expand Up @@ -352,6 +483,14 @@ def test_c_child_then_parent(self):
def test_c_interspersed(self):
self.resume_one_test(run_order=["parent", "child", "parent", "child"])

@expectedFailureAll(archs=["arm"]) # TODO
@expectedFailureAll(archs=["aarch64"],
bugnumber="https://github.com/llvm/llvm-project/issues/56268")
@add_test_categories(["fork"])
def test_c_interspersed_nonstop(self):
self.resume_one_test(run_order=["parent", "child", "parent", "child"],
nonstop=True)

@expectedFailureAll(archs=["arm"]) # TODO
@expectedFailureAll(archs=["aarch64"],
bugnumber="https://github.com/llvm/llvm-project/issues/56268")
Expand Down Expand Up @@ -390,6 +529,14 @@ def test_vCont_interspersed(self):
self.resume_one_test(run_order=["parent", "child", "parent", "child"],
use_vCont=True)

@expectedFailureAll(archs=["arm"]) # TODO
@expectedFailureAll(archs=["aarch64"],
bugnumber="https://github.com/llvm/llvm-project/issues/56268")
@add_test_categories(["fork"])
def test_vCont_interspersed_nonstop(self):
self.resume_one_test(run_order=["parent", "child", "parent", "child"],
use_vCont=True, nonstop=True)

@add_test_categories(["fork"])
def test_vCont_two_processes(self):
parent_pid, parent_tid, child_pid, child_tid = (
Expand Down