Skip to content

Commit 568b114

Browse files
committed
ci: Added tests
1 parent 9fac323 commit 568b114

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

tests/test_client.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
2+
import pytest
3+
from dap.client import DAPClient, ClientState
4+
from dap.events import InitializedEvent, StoppedEvent, TerminatedEvent
5+
from dap.protocol import Response
6+
import json
7+
8+
def _make_response_bytes(seq, request_seq, command, success=True, body=None):
9+
content = {
10+
"type": "response",
11+
"seq": seq,
12+
"request_seq": request_seq,
13+
"command": command,
14+
"success": success,
15+
}
16+
if body:
17+
content["body"] = body
18+
19+
encoded = json.dumps(content).encode("utf-8")
20+
header = f"Content-Length: {len(encoded)}\r\n\r\n".encode("utf-8")
21+
return header + encoded
22+
23+
def _make_event_bytes(seq, event_name, body=None):
24+
content = {
25+
"type": "event",
26+
"seq": seq,
27+
"event": event_name,
28+
}
29+
if body:
30+
content["body"] = body
31+
32+
encoded = json.dumps(content).encode("utf-8")
33+
header = f"Content-Length: {len(encoded)}\r\n\r\n".encode("utf-8")
34+
return header + encoded
35+
36+
def test_full_lifecycle():
37+
"""Test a full debug session lifecycle."""
38+
client = DAPClient(clientID="test", clientName="Test")
39+
40+
# 1. Initialize
41+
init_req_bytes = client.send()
42+
# Expect seq 0
43+
44+
# Adapter responds to initialize (seq=0)
45+
response_bytes = _make_response_bytes(seq=1, request_seq=0, command="initialize", body={"supportsConfigurationDoneRequest": True})
46+
events = list(client.recv(response_bytes))
47+
48+
assert len(events) == 1
49+
assert isinstance(events[0], InitializedEvent)
50+
51+
# 2. Launch
52+
client.launch(program="main.py")
53+
client.send() # flush
54+
# Expect seq 1
55+
56+
# Adapter responds to launch (seq=1)
57+
response_bytes = _make_response_bytes(seq=2, request_seq=1, command="launch")
58+
events = list(client.recv(response_bytes))
59+
60+
print(f"Launch response event: {events[0]}")
61+
assert events[0].event == "response"
62+
assert events[0].body["command"] == "launch"
63+
64+
# 3. Configuration Done
65+
client.configuration_done()
66+
client.send() # flush
67+
# Expect seq 2
68+
69+
response_bytes = _make_response_bytes(seq=3, request_seq=2, command="configurationDone")
70+
list(client.recv(response_bytes))
71+
72+
# 4. Breakpoint Hit
73+
stop_event_bytes = _make_event_bytes(seq=4, event_name="stopped", body={"reason": "breakpoint", "threadId": 1})
74+
events = list(client.recv(stop_event_bytes))
75+
assert events[0].event == "stopped"
76+
77+
# 5. Continue
78+
client.continue_execution(threadId=1)
79+
client.send()
80+
# Expect seq 3
81+
82+
response_bytes = _make_response_bytes(seq=5, request_seq=3, command="continue")
83+
list(client.recv(response_bytes))
84+
85+
# 6. Disconnect
86+
client.disconnect()
87+
client.send()
88+
# Expect seq 4
89+
90+
print(f"Unanswered requests before disconnect response: {client._unanswered_requests}")
91+
92+
response_bytes = _make_response_bytes(seq=6, request_seq=4, command="disconnect")
93+
events = list(client.recv(response_bytes))
94+
95+
print(f"Disconnect events: {events}")
96+
97+
assert len(events) == 1
98+
assert isinstance(events[0], TerminatedEvent) or events[0].event == "terminated"
99+
100+
def test_unsolicited_events():
101+
"""Test parsing events that come without a request."""
102+
client = DAPClient()
103+
client.send()
104+
105+
out_evt_bytes = _make_event_bytes(seq=1, event_name="output", body={"category": "stdout", "output": "Hello World\n"})
106+
events = list(client.recv(out_evt_bytes))
107+
108+
assert len(events) == 1
109+
assert events[0].event == "output"
110+
assert events[0].body["output"] == "Hello World\n"
111+
112+
def test_request_methods():
113+
"""Test helper methods for sending requests."""
114+
client = DAPClient()
115+
client.send()
116+
client._state = ClientState.NORMAL
117+
118+
client.next(threadId=1)
119+
req = client.send()
120+
assert b'"command": "next"' in req
121+
assert b'"threadId": 1' in req

tests/test_io.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
2+
import pytest
3+
from dap.client import DAPClient
4+
5+
def _wrap(body: bytes) -> bytes:
6+
return f"Content-Length: {len(body)}\r\n\r\n".encode("utf-8") + body
7+
8+
def test_partial_messages():
9+
"""Test parsing logic when messages arrive in chunks."""
10+
client = DAPClient()
11+
12+
body = b'{"seq": 1, "type": "event", "event": "stopped"}'
13+
msg = _wrap(body)
14+
15+
# Split it
16+
part1 = msg[:20]
17+
part2 = msg[20:]
18+
19+
# Feed part 1
20+
events = list(client.recv(part1))
21+
assert len(events) == 0
22+
23+
# Feed part 2
24+
events = list(client.recv(part2))
25+
assert len(events) == 1
26+
assert events[0].event == "stopped"
27+
28+
def test_multiple_messages_in_one_chunk():
29+
"""Test parsing multiple messages arriving in a single buffer."""
30+
client = DAPClient()
31+
32+
body1 = b'{"seq": 1, "type": "event", "event": "stopped"}'
33+
body2 = b'{"seq": 2, "type": "event", "event": "initialized"}'
34+
35+
msg = _wrap(body1) + _wrap(body2)
36+
37+
# Feed both at once
38+
events = list(client.recv(msg))
39+
40+
assert len(events) == 2
41+
assert events[0].event == "stopped"
42+
assert events[1].event == "initialized"
43+
44+
def test_extra_headers():
45+
"""Test messages with extra headers like Content-Type are accepted."""
46+
client = DAPClient()
47+
48+
content = b'{"seq": 1, "type": "event", "event": "stopped"}'
49+
# Manual construction ensuring length is correct
50+
msg = (
51+
f"Content-Length: {len(content)}\r\n"
52+
"Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n"
53+
"\r\n"
54+
).encode("utf-8") + content
55+
56+
events = list(client.recv(msg))
57+
assert len(events) == 1
58+
assert events[0].event == "stopped"
59+
60+
def test_utf8_encoding():
61+
"""Test parsing utf-8 content with special characters."""
62+
client = DAPClient()
63+
64+
body = '{"text": "héllo world 🐛"}'
65+
content = (
66+
f'{{"seq": 1, "type": "event", "event": "output", "body": {body}}}'
67+
).encode("utf-8")
68+
69+
msg = _wrap(content)
70+
71+
events = list(client.recv(msg))
72+
assert len(events) == 1
73+
assert events[0].body["text"] == "héllo world 🐛"

0 commit comments

Comments
 (0)