Skip to content

Commit efd42b6

Browse files
committed
fix: pass dap-server to ct/start-replay
1 parent 6ec3d43 commit efd42b6

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use db_backend::dap::{self, DapClient, DapMessage, LaunchRequestArguments};
2+
use db_backend::dap_server;
3+
use db_backend::dap_types::StackTraceArguments;
4+
use serde_json::json;
5+
use std::io::BufReader;
6+
use std::os::unix::net::UnixStream;
7+
use std::path::{Path, PathBuf};
8+
use std::process::Command;
9+
use std::thread;
10+
use std::time::Duration;
11+
12+
fn wait_for_socket(path: &Path) {
13+
for _ in 0..50 {
14+
if path.exists() {
15+
return;
16+
}
17+
thread::sleep(Duration::from_millis(100));
18+
}
19+
// println!("{path:?}");
20+
panic!("socket not created");
21+
}
22+
23+
#[test]
24+
fn test_backend_dap_server() {
25+
let bin = env!("CARGO_BIN_EXE_db-backend");
26+
let pid = std::process::id() as usize;
27+
let trace_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("trace");
28+
29+
let socket_path = dap_server::socket_path_for(std::process::id() as usize);
30+
let mut child = Command::new(bin).arg("dap-server").arg(&socket_path).spawn().unwrap();
31+
wait_for_socket(&socket_path);
32+
33+
let stream = UnixStream::connect(&socket_path).unwrap();
34+
let mut reader = BufReader::new(stream.try_clone().unwrap());
35+
let mut writer = stream;
36+
37+
let mut client = DapClient::default();
38+
let init = client.request("initialize", json!({}));
39+
dap::write_message(&mut writer, &init).unwrap();
40+
let launch_args = LaunchRequestArguments {
41+
program: Some("main".to_string()),
42+
trace_folder: Some(trace_dir),
43+
trace_file: None,
44+
pid: Some(pid as u64),
45+
cwd: None,
46+
no_debug: None,
47+
restart: None,
48+
name: None,
49+
request: None,
50+
typ: None,
51+
session_id: None,
52+
};
53+
let launch = client.launch(launch_args).unwrap();
54+
dap::write_message(&mut writer, &launch).unwrap();
55+
56+
let msg1 = dap::from_reader(&mut reader).unwrap();
57+
match msg1 {
58+
DapMessage::Response(r) => {
59+
assert_eq!(r.command, "initialize");
60+
println!("{:?}", r.body);
61+
// assert!(r.body["supportsLoadedSourcesRequest"].as_bool().unwrap());
62+
assert!(r.body["supportsStepBack"].as_bool().unwrap());
63+
assert!(r.body["supportsConfigurationDoneRequest"].as_bool().unwrap());
64+
assert!(r.body["supportsDisassembleRequest"].as_bool().unwrap());
65+
assert!(r.body["supportsLogPoints"].as_bool().unwrap());
66+
assert!(r.body["supportsRestartRequest"].as_bool().unwrap());
67+
}
68+
_ => panic!(),
69+
}
70+
let msg2 = dap::from_reader(&mut reader).unwrap();
71+
match msg2 {
72+
DapMessage::Event(e) => assert_eq!(e.event, "initialized"),
73+
_ => panic!(),
74+
}
75+
let conf_done = client.request("configurationDone", json!({}));
76+
dap::write_message(&mut writer, &conf_done).unwrap();
77+
let msg3 = dap::from_reader(&mut reader).unwrap();
78+
match msg3 {
79+
DapMessage::Response(r) => assert_eq!(r.command, "launch"),
80+
_ => panic!(),
81+
}
82+
let msg4 = dap::from_reader(&mut reader).unwrap();
83+
match msg4 {
84+
DapMessage::Response(r) => assert_eq!(r.command, "configurationDone"),
85+
_ => panic!(),
86+
}
87+
88+
let msg5 = dap::from_reader(&mut reader).unwrap();
89+
match msg5 {
90+
DapMessage::Event(e) => {
91+
assert_eq!(e.event, "stopped");
92+
assert_eq!(e.body["reason"], "entry");
93+
}
94+
_ => panic!("expected a stopped event, but got {:?}", msg5),
95+
}
96+
97+
let msg_complete_move = dap::from_reader(&mut reader).unwrap();
98+
match msg_complete_move {
99+
DapMessage::Event(e) => {
100+
assert_eq!(e.event, "ct/complete-move");
101+
}
102+
_ => panic!("expected a complete move events, but got {:?}", msg_complete_move),
103+
}
104+
105+
let threads_request = client.request("threads", json!({}));
106+
dap::write_message(&mut writer, &threads_request).unwrap();
107+
let msg_threads = dap::from_reader(&mut reader).unwrap();
108+
match msg_threads {
109+
DapMessage::Response(r) => {
110+
assert_eq!(r.command, "threads");
111+
assert_eq!(r.body["threads"][0]["id"], 1);
112+
}
113+
_ => panic!(
114+
"expected a Response DapMessage after a threads request, but got {:?}",
115+
msg_threads
116+
),
117+
}
118+
119+
let stack_trace_request = client.request(
120+
"stackTrace",
121+
serde_json::to_value(StackTraceArguments {
122+
thread_id: 1,
123+
format: None,
124+
levels: None,
125+
start_frame: None,
126+
})
127+
.unwrap(),
128+
);
129+
dap::write_message(&mut writer, &stack_trace_request).unwrap();
130+
let msg_stack_trace = dap::from_reader(&mut reader).unwrap();
131+
match msg_stack_trace {
132+
DapMessage::Response(r) => assert_eq!(r.command, "stackTrace"), // TODO: test stackFrames / totalFrames ?
133+
_ => panic!(),
134+
}
135+
136+
drop(writer);
137+
drop(reader);
138+
let _ = child.wait().unwrap();
139+
}
140+
141+
#[test]
142+
fn test_dap_initialization_sequence() {
143+
let bin = env!("CARGO_BIN_EXE_db-backend");
144+
let pid = std::process::id() as usize;
145+
146+
let socket_path = dap_server::socket_path_for(pid);
147+
let mut child = Command::new(bin).arg("dap-server").arg(&socket_path).spawn().unwrap();
148+
wait_for_socket(&socket_path);
149+
150+
let stream = UnixStream::connect(&socket_path).unwrap();
151+
let mut reader = BufReader::new(stream.try_clone().unwrap());
152+
let mut writer = stream;
153+
154+
let mut client = DapClient::default();
155+
let init = client.request("initialize", json!({}));
156+
dap::write_message(&mut writer, &init).unwrap();
157+
158+
let msg1 = dap::from_reader(&mut reader).unwrap();
159+
match msg1 {
160+
DapMessage::Response(r) => {
161+
assert_eq!(r.command, "initialize");
162+
assert!(r.body["supportsStepBack"].as_bool().unwrap());
163+
assert!(r.body["supportsConfigurationDoneRequest"].as_bool().unwrap());
164+
assert!(r.body["supportsDisassembleRequest"].as_bool().unwrap());
165+
assert!(r.body["supportsLogPoints"].as_bool().unwrap());
166+
assert!(r.body["supportsRestartRequest"].as_bool().unwrap());
167+
}
168+
_ => panic!("expected an initialize response, but got {:?}", msg1),
169+
}
170+
171+
let msg2 = dap::from_reader(&mut reader).unwrap();
172+
match msg2 {
173+
DapMessage::Event(e) => assert_eq!(e.event, "initialized"),
174+
_ => panic!("expected an initialized event, but got {:?}", msg2),
175+
}
176+
177+
drop(writer);
178+
drop(reader);
179+
let _ = child.wait().unwrap();
180+
}

src/db-backend/transport_spec.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# DB-Backend Transport Specification
2+
3+
## 1. Introduction
4+
5+
This document specifies the transport mechanism used by the `db-backend` component for communication with external clients, primarily debuggers or development environments. The transport layer facilitates the exchange of messages conforming to the Debug Adapter Protocol (DAP).
6+
7+
## 2. Protocol Overview
8+
9+
The `db-backend` utilizes the Debug Adapter Protocol (DAP) as its primary communication protocol. DAP defines a generic protocol for debuggers and development environments to communicate with debug adapters. This specification focuses on how these DAP messages are transmitted over a communication channel.
10+
11+
## 3. Message Structure
12+
13+
DAP messages are exchanged using a simple, length-prefixed JSON format. Each message consists of two parts:
14+
15+
1. **Header**: A set of HTTP-like headers, terminated by a `\r\n\r\n` sequence. The most crucial header is `Content-Length`, which indicates the size of the following JSON payload in bytes.
16+
```/dev/null/example.txt#L1-2
17+
Content-Length: 123
18+
Content-Type: application/json
19+
```
20+
The `Content-Type` header is optional but recommended. If present, its value must be `application/json`.
21+
22+
2. **Content**: The actual DAP message, which is a JSON object encoded in UTF-8. The size of this content must exactly match the `Content-Length` specified in the header.
23+
24+
Example of a complete message:
25+
```/dev/null/example.txt#L1-5
26+
Content-Length: 72
27+
Content-Type: application/json
28+
29+
{"seq":1, "type":"request", "command":"initialize", "arguments":{"adapterID":"db"}}
30+
```
31+
32+
## 4. Transport Layer
33+
34+
The `db-backend` primarily uses **Standard I/O (stdin/stdout)** for its transport layer.
35+
36+
* **Input (stdin)**: The `db-backend` reads incoming DAP messages from its standard input stream.
37+
* **Output (stdout)**: The `db-backend` writes outgoing DAP messages to its standard output stream.
38+
39+
Each message (header + content) is transmitted as a contiguous block of bytes. There should be no additional delimiters or framing between messages beyond the `\r\n\r\n` separator between the header and the content.
40+
41+
## 5. Error Handling
42+
43+
### 5.1. Malformed Messages
44+
45+
If the `db-backend` receives a message that does not conform to the specified header and content format (e.g., missing `Content-Length`, invalid `Content-Length`, or non-JSON content), it should:
46+
47+
* Attempt to log the error internally (if a logging mechanism is available).
48+
* Discard the malformed message.
49+
* Continue processing subsequent messages, if possible.
50+
* It should **not** send an error response over the DAP channel for transport-layer parsing errors, as the message might be too corrupted to respond to meaningfully.
51+
52+
### 5.2. Protocol Errors (DAP Level)
53+
54+
Errors within the DAP message content (e.g., unknown command, invalid arguments for a command) should be handled according to the DAP specification, typically by sending a `response` message with the `success` field set to `false` and an appropriate `message` and `body.error` field.
55+
56+
### 5.3. Transport Failures
57+
58+
If the standard I/O streams are closed unexpectedly or encounter read/write errors, the `db-backend` should terminate gracefully, logging the nature of the transport failure.

0 commit comments

Comments
 (0)