Skip to content

Commit 7a0ec7f

Browse files
dsandersllvmLukacma
authored andcommitted
[lldb] Add bidirectional packetLog to gdbclientutils.py (llvm#162176)
While debugging the tests for llvm#155000 I found it helpful to have both sides of the simulated gdb-rsp traffic rather than just the responses so I've extended the packetLog in MockGDBServerResponder to record traffic in both directions. Tests have been updated accordingly
1 parent cebb236 commit 7a0ec7f

File tree

6 files changed

+102
-66
lines changed

6 files changed

+102
-66
lines changed

lldb/packages/Python/lldbsuite/test/gdbclientutils.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
import threading
66
import socket
77
import traceback
8+
from enum import Enum
89
from lldbsuite.support import seven
9-
from typing import Optional, List, Tuple
10+
from typing import Optional, List, Tuple, Union, Sequence
1011

1112

1213
def checksum(message):
@@ -76,6 +77,35 @@ def hex_decode_bytes(hex_bytes):
7677
return out
7778

7879

80+
class PacketDirection(Enum):
81+
RECV = "recv"
82+
SEND = "send"
83+
84+
85+
class PacketLog:
86+
def __init__(self):
87+
self._packets: list[tuple[PacketDirection, str]] = []
88+
89+
def add_sent(self, packet: str):
90+
self._packets.append((PacketDirection.SEND, packet))
91+
92+
def add_received(self, packet: str):
93+
self._packets.append((PacketDirection.RECV, packet))
94+
95+
def get_sent(self):
96+
return [
97+
pkt for direction, pkt in self._packets if direction == PacketDirection.SEND
98+
]
99+
100+
def get_received(self):
101+
return [
102+
pkt for direction, pkt in self._packets if direction == PacketDirection.RECV
103+
]
104+
105+
def __iter__(self):
106+
return iter(self._packets)
107+
108+
79109
class MockGDBServerResponder:
80110
"""
81111
A base class for handling client packets and issuing server responses for
@@ -90,21 +120,33 @@ class MockGDBServerResponder:
90120

91121
registerCount: int = 40
92122

93-
class RESPONSE_DISCONNECT:
94-
pass
123+
class SpecialResponse(Enum):
124+
RESPONSE_DISCONNECT = 0
125+
RESPONSE_NONE = 1
95126

96-
class RESPONSE_NONE:
97-
pass
127+
RESPONSE_DISCONNECT = SpecialResponse.RESPONSE_DISCONNECT
128+
RESPONSE_NONE = SpecialResponse.RESPONSE_NONE
129+
Response = Union[str, SpecialResponse]
98130

99131
def __init__(self):
100-
self.packetLog: List[str] = []
132+
self.packetLog = PacketLog()
101133

102-
def respond(self, packet):
134+
def respond(self, packet: str) -> Sequence[Response]:
103135
"""
104136
Return the unframed packet data that the server should issue in response
105137
to the given packet received from the client.
106138
"""
107-
self.packetLog.append(packet)
139+
self.packetLog.add_received(packet)
140+
response = self._respond_impl(packet)
141+
if not isinstance(response, list):
142+
response = [response]
143+
for part in response:
144+
if isinstance(part, self.SpecialResponse):
145+
continue
146+
self.packetLog.add_sent(part)
147+
return response
148+
149+
def _respond_impl(self, packet) -> Union[Response, List[Response]]:
108150
if packet is MockGDBServer.PACKET_INTERRUPT:
109151
return self.interrupt()
110152
if packet == "c":
@@ -664,24 +706,28 @@ def _handlePacket(self, packet):
664706
# adding validation code to make sure the client only sends ACKs
665707
# when it's supposed to.
666708
return
667-
response = ""
709+
response = [""]
668710
# We'll handle the ack stuff here since it's not something any of the
669711
# tests will be concerned about, and it'll get turned off quickly anyway.
670712
if self._shouldSendAck:
671713
self._socket.sendall(seven.bitcast_to_bytes("+"))
672714
if packet == "QStartNoAckMode":
673715
self._shouldSendAck = False
674-
response = "OK"
716+
response = ["OK"]
675717
elif self.responder is not None:
676718
# Delegate everything else to our responder
677719
response = self.responder.respond(packet)
720+
# MockGDBServerResponder no longer returns non-lists but others like
721+
# ReverseTestBase still do
678722
if not isinstance(response, list):
679723
response = [response]
680724
for part in response:
681725
if part is MockGDBServerResponder.RESPONSE_NONE:
682726
continue
683727
if part is MockGDBServerResponder.RESPONSE_DISCONNECT:
684728
raise self.TerminateConnectionException()
729+
# Should have handled the non-str's above
730+
assert isinstance(part, str)
685731
self._sendPacket(part)
686732

687733
PACKET_ACK = object()

lldb/packages/Python/lldbsuite/test/lldbgdbclient.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class GDBRemoteTestBase(TestBase):
1010
Base class for GDB client tests.
1111
1212
This class will setup and start a mock GDB server for the test to use.
13-
It also provides assertPacketLogContains, which simplifies the checking
13+
It also provides assertPacketLogReceived, which simplifies the checking
1414
of packets sent by the client.
1515
"""
1616

@@ -60,30 +60,32 @@ def connect(self, target, plugin="gdb-remote"):
6060
self.assertTrue(process, PROCESS_IS_VALID)
6161
return process
6262

63-
def assertPacketLogContains(self, packets, log=None):
63+
def assertPacketLogReceived(self, packets, log: PacketLog = None):
6464
"""
65-
Assert that the mock server's packet log contains the given packets.
65+
Assert that the mock server's packet log received the given packets.
6666
6767
The packet log includes all packets sent by the client and received
68-
by the server. This fuction makes it easy to verify that the client
68+
by the server. This function makes it easy to verify that the client
6969
sent the expected packets to the server.
7070
7171
The check does not require that the packets be consecutive, but does
7272
require that they are ordered in the log as they ordered in the arg.
7373
"""
7474
if log is None:
75-
log = self.server.responder.packetLog
75+
received = self.server.responder.packetLog.get_received()
76+
else:
77+
received = log.get_received()
7678
i = 0
7779
j = 0
7880

79-
while i < len(packets) and j < len(log):
80-
if log[j] == packets[i]:
81+
while i < len(packets) and j < len(received):
82+
if received[j] == packets[i]:
8183
i += 1
8284
j += 1
8385
if i < len(packets):
8486
self.fail(
8587
"Did not receive: %s\nLast 10 packets:\n\t%s"
86-
% (packets[i], "\n\t".join(log))
88+
% (packets[i], "\n\t".join(received))
8789
)
8890

8991

lldb/test/API/functionalities/gdb_remote_client/TestContinue.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def qfThreadInfo(self):
4141
lldbutil.expect_state_changes(
4242
self, self.dbg.GetListener(), process, [lldb.eStateExited]
4343
)
44-
self.assertPacketLogContains(["vCont;C13:401"])
44+
self.assertPacketLogReceived(["vCont;C13:401"])
4545

4646
def test_continue_no_vCont(self):
4747
class MyResponder(self.BaseResponder):
@@ -61,7 +61,7 @@ def other(self, packet):
6161
lldbutil.expect_state_changes(
6262
self, self.dbg.GetListener(), process, [lldb.eStateExited]
6363
)
64-
self.assertPacketLogContains(["Hc401", "C13"])
64+
self.assertPacketLogReceived(["Hc401", "C13"])
6565

6666
def test_continue_multiprocess(self):
6767
class MyResponder(self.BaseResponder):
@@ -74,4 +74,4 @@ class MyResponder(self.BaseResponder):
7474
lldbutil.expect_state_changes(
7575
self, self.dbg.GetListener(), process, [lldb.eStateExited]
7676
)
77-
self.assertPacketLogContains(["vCont;C13:p400.401"])
77+
self.assertPacketLogReceived(["vCont;C13:p400.401"])

lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_connect(self):
3636
"""Test connecting to a remote gdb server"""
3737
target = self.createTarget("a.yaml")
3838
process = self.connect(target)
39-
self.assertPacketLogContains(["qProcessInfo", "qfThreadInfo"])
39+
self.assertPacketLogReceived(["qProcessInfo", "qfThreadInfo"])
4040

4141
def test_attach_fail(self):
4242
error_msg = "mock-error-msg"
@@ -142,29 +142,24 @@ def test_read_registers_using_g_packets(self):
142142
# But there certainly should be no p packets after the g packet.
143143

144144
self.read_registers(process)
145-
print(f"\nPACKET LOG:\n{self.server.responder.packetLog}\n")
145+
received = self.server.responder.packetLog.get_received()
146+
print(f"\nPACKET LOG:\n{received}\n")
146147
g_pos = 0
147148
try:
148-
g_pos = self.server.responder.packetLog.index("g")
149+
g_pos = received.index("g")
149150
except err:
150151
self.fail("'g' packet not found after fetching registers")
151152

152153
try:
153-
second_g = self.server.responder.packetLog.index("g", g_pos)
154+
second_g = received.index("g", g_pos + 1)
154155
self.fail("Found more than one 'g' packet")
155156
except:
156157
pass
157158

158159
# Make sure there aren't any `p` packets after the `g` packet:
159160
self.assertEqual(
160161
0,
161-
len(
162-
[
163-
p
164-
for p in self.server.responder.packetLog[g_pos:]
165-
if p.startswith("p")
166-
]
167-
),
162+
len([p for p in received[g_pos:] if p.startswith("p")]),
168163
)
169164

170165
def test_read_registers_using_p_packets(self):
@@ -177,10 +172,9 @@ def test_read_registers_using_p_packets(self):
177172
process = self.connect(target)
178173

179174
self.read_registers(process)
180-
self.assertNotIn("g", self.server.responder.packetLog)
181-
self.assertGreater(
182-
len([p for p in self.server.responder.packetLog if p.startswith("p")]), 0
183-
)
175+
received = self.server.responder.packetLog.get_received()
176+
self.assertNotIn("g", received)
177+
self.assertGreater(len([p for p in received if p.startswith("p")]), 0)
184178

185179
def test_write_registers_using_P_packets(self):
186180
"""Test writing registers using 'P' packets (default behavior)"""
@@ -189,12 +183,9 @@ def test_write_registers_using_P_packets(self):
189183
process = self.connect(target)
190184

191185
self.write_registers(process)
192-
self.assertEqual(
193-
0, len([p for p in self.server.responder.packetLog if p.startswith("G")])
194-
)
195-
self.assertGreater(
196-
len([p for p in self.server.responder.packetLog if p.startswith("P")]), 0
197-
)
186+
received = self.server.responder.packetLog.get_received()
187+
self.assertEqual(0, len([p for p in received if p.startswith("G")]))
188+
self.assertGreater(len([p for p in received if p.startswith("P")]), 0)
198189

199190
def test_write_registers_using_G_packets(self):
200191
"""Test writing registers using 'G' packets"""
@@ -209,12 +200,9 @@ def readRegister(self, register):
209200
process = self.connect(target)
210201

211202
self.write_registers(process)
212-
self.assertEqual(
213-
0, len([p for p in self.server.responder.packetLog if p.startswith("P")])
214-
)
215-
self.assertGreater(
216-
len([p for p in self.server.responder.packetLog if p.startswith("G")]), 0
217-
)
203+
received = self.server.responder.packetLog.get_received()
204+
self.assertEqual(0, len([p for p in received if p.startswith("P")]))
205+
self.assertGreater(len([p for p in received if p.startswith("G")]), 0)
218206

219207
def read_registers(self, process):
220208
self.for_each_gpr(
@@ -291,7 +279,7 @@ def qLaunchSuccess(self):
291279
self.assertTrue(process, PROCESS_IS_VALID)
292280
self.assertEqual(process.GetProcessID(), 16)
293281

294-
self.assertPacketLogContains(
282+
self.assertPacketLogReceived(
295283
[
296284
"A%d,0,%s,8,1,61726731,8,2,61726732,8,3,61726733"
297285
% (len(exe_hex), exe_hex),
@@ -352,7 +340,7 @@ def A(self, packet):
352340
self.assertTrue(process, PROCESS_IS_VALID)
353341
self.assertEqual(process.GetProcessID(), 16)
354342

355-
self.assertPacketLogContains(
343+
self.assertPacketLogReceived(
356344
["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]
357345
)
358346

@@ -424,7 +412,7 @@ def A(self, packet):
424412
self.assertTrue(process, PROCESS_IS_VALID)
425413
self.assertEqual(process.GetProcessID(), 16)
426414

427-
self.assertPacketLogContains(
415+
self.assertPacketLogReceived(
428416
["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]
429417
)
430418

@@ -468,7 +456,7 @@ def vRun(self, packet):
468456
lldb.SBError(),
469457
) # error
470458

471-
self.assertPacketLogContains(
459+
self.assertPacketLogReceived(
472460
[
473461
"QEnvironment:EQUALS=foo=bar",
474462
"QEnvironmentHexEncoded:4e45454453454e433d66726f6224",
@@ -522,7 +510,7 @@ def QEnvironment(self, packet):
522510
lldb.SBError(),
523511
) # error
524512

525-
self.assertPacketLogContains(
513+
self.assertPacketLogReceived(
526514
[
527515
"QEnvironmentHexEncoded:455155414c533d666f6f3d626172",
528516
"QEnvironmentHexEncoded:4e45454453454e433d66726f6224",

lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteLoad.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_ram_load(self):
2222
target = self.createTarget("a.yaml")
2323
process = self.connect(target)
2424
self.dbg.HandleCommand("target modules load -l -s0")
25-
self.assertPacketLogContains(["M1000,4:c3c3c3c3", "M1004,2:3232"])
25+
self.assertPacketLogReceived(["M1000,4:c3c3c3c3", "M1004,2:3232"])
2626

2727
@skipIfXmlSupportMissing
2828
def test_flash_load(self):
@@ -63,7 +63,7 @@ def other(self, packet):
6363
target = self.createTarget("a.yaml")
6464
process = self.connect(target)
6565
self.dbg.HandleCommand("target modules load -l -s0")
66-
self.assertPacketLogContains(
66+
self.assertPacketLogReceived(
6767
[
6868
"vFlashErase:1000,100",
6969
"vFlashWrite:1000:\xc3\xc3\xc3\xc3",

0 commit comments

Comments
 (0)