use libc::{F_GETFL, F_SETFL, O_NONBLOCK, fcntl};
use serde_json::json;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::unix::io::AsRawFd;
use std::process::{Child, Command, Stdio};
use std::thread;
use std::time::{Duration, Instant};
fn start_bs_stdio_dap(debugee: &str) -> anyhow::Result<StdioDAP> {
let mut child = Command::new(
std::env::var("CARGO_BIN_EXE_bs").unwrap_or_else(|_| "./target/debug/bs".to_string()),
)
.arg("--dap-local")
.arg(debugee)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
let stdin = child
.stdin
.take()
.ok_or_else(|| anyhow::anyhow!("no stdin"))?;
let stdout = child
.stdout
.take()
.ok_or_else(|| anyhow::anyhow!("no stdout"))?;
let fd = stdout.as_raw_fd();
unsafe {
let flags = fcntl(fd, F_GETFL);
if flags >= 0 {
let _ = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
}
Ok(StdioDAP {
child,
stdin: Box::new(stdin),
reader: BufReader::new(stdout),
})
}
pub struct StdioDAP {
child: Child,
stdin: Box<dyn Write + Send>,
reader: BufReader<std::process::ChildStdout>,
}
impl StdioDAP {
fn read_message(&mut self) -> anyhow::Result<serde_json::Value> {
const MESSAGE_TIMEOUT: Duration = Duration::from_secs(15);
let deadline = Instant::now() + MESSAGE_TIMEOUT;
let mut content_length: Option<usize> = None;
loop {
let mut line = String::new();
let read_n = loop {
match self.reader.read_line(&mut line) {
Ok(n) => break n,
Err(err)
if err.kind() == std::io::ErrorKind::WouldBlock
|| err.kind() == std::io::ErrorKind::TimedOut =>
{
if Instant::now() > deadline {
return Err(anyhow::anyhow!("Timed out waiting for DAP header"));
}
thread::sleep(Duration::from_millis(10));
continue;
}
Err(err) => return Err(err.into()),
}
};
if read_n == 0 {
return Err(anyhow::anyhow!("connection closed"));
}
let line_trimmed = line.trim_end_matches(['\r', '\n']);
if line_trimmed.is_empty() {
if content_length.is_some() {
break;
} else {
continue;
}
}
let lower = line_trimmed.to_ascii_lowercase();
if let Some(v) = lower.strip_prefix("content-length:") {
content_length = Some(v.trim().parse()?);
}
}
let len = content_length.ok_or_else(|| anyhow::anyhow!("missing content-length"))?;
let mut buf = vec![0u8; len];
let mut offset = 0usize;
while offset < buf.len() {
match self.reader.read(&mut buf[offset..]) {
Ok(0) => return Err(anyhow::anyhow!("DAP connection closed")),
Ok(n) => {
offset += n;
}
Err(err)
if err.kind() == std::io::ErrorKind::WouldBlock
|| err.kind() == std::io::ErrorKind::TimedOut =>
{
if Instant::now() > deadline {
return Err(anyhow::anyhow!("Timed out waiting for DAP body"));
}
thread::sleep(Duration::from_millis(10));
continue;
}
Err(err) => return Err(err.into()),
}
}
let v = serde_json::from_slice(&buf)?;
Ok(v)
}
fn write_message(&mut self, msg: &serde_json::Value) -> anyhow::Result<()> {
let payload = serde_json::to_vec(msg)?;
write!(self.stdin, "Content-Length: {}\r\n\r\n", payload.len())?;
self.stdin.write_all(&payload)?;
self.stdin.flush()?;
Ok(())
}
fn send_request(
&mut self,
seq: i64,
command: &str,
arguments: serde_json::Value,
) -> anyhow::Result<()> {
self.write_message(&json!({
"seq": seq,
"type": "request",
"command": command,
"arguments": arguments
}))
}
}
#[test]
fn test_stdio_dap_initialize() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
"pathFormat": "path",
"linesStartAt1": true,
"columnsStartAt1": true,
"supportsVariableType": true,
"supportsVariablePaging": true,
"supportsRunInTerminalRequest": false,
"supportsMemoryReferences": true,
}),
)?;
let response = dap.read_message()?;
assert_eq!(response["type"], "response");
assert_eq!(response["command"], "initialize");
assert!(response["success"].as_bool().unwrap_or(false));
let event = dap.read_message()?;
assert_eq!(event["type"], "event");
assert_eq!(event["event"], "initialized");
Ok(())
}
#[test]
fn test_stdio_dap_launch() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
"pathFormat": "path",
"linesStartAt1": true,
"columnsStartAt1": true,
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": hello_world,
"stopOnEntry": true,
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
Ok(())
}
#[test]
fn test_stdio_dap_capabilities() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let init_response = dap.read_message()?;
let _event = dap.read_message()?;
let body = &init_response["body"];
assert!(
body["supportsConfigurationDoneRequest"]
.as_bool()
.unwrap_or(false)
);
assert!(body["supportsSetVariable"].as_bool().unwrap_or(false));
assert!(
body["supportsBreakpointLocationsRequest"]
.as_bool()
.unwrap_or(false)
);
Ok(())
}
#[test]
fn test_stdio_dap_threads() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": hello_world,
"stopOnEntry": true,
}),
)?;
let mut response = None;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
response = Some(msg);
break;
}
}
if response.is_none() {
return Ok(());
}
dap.send_request(3, "threads", json!({}))?;
let mut response_found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "threads" {
assert!(msg["body"]["threads"].is_array());
response_found = true;
break;
}
}
if !response_found {
return Ok(());
}
Ok(())
}
#[test]
fn test_stdio_dap_set_breakpoints() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
dap.send_request(
3,
"setBreakpoints",
json!({
"source": { "path": &hello_world },
"breakpoints": [{ "line": 5 }],
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "setBreakpoints" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
Ok(())
}
#[test]
fn test_stdio_dap_continue() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"continue",
json!({
"threadId": 1,
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "continue" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
Ok(())
}
#[test]
fn test_stdio_dap_next() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"next",
json!({
"threadId": 1,
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "next" {
found = true;
break;
}
}
assert!(found, "next response not received");
Ok(())
}
#[test]
fn test_stdio_dap_stack_trace() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"stackTrace",
json!({
"threadId": 1,
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "stackTrace" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
assert!(response["body"]["stackFrames"].is_array());
Ok(())
}
#[test]
fn test_stdio_dap_scopes() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"scopes",
json!({
"frameId": 0,
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "scopes" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
Ok(())
}
#[test]
fn test_stdio_dap_variables() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"variables",
json!({
"variablesReference": 0,
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "variables" {
break msg;
}
};
assert_eq!(response["type"], "response");
assert_eq!(response["command"], "variables");
Ok(())
}
#[test]
fn test_stdio_dap_evaluate() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"evaluate",
json!({
"expression": "1+1",
"frameId": 0,
"context": "watch"
}),
)?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "evaluate" {
break msg;
}
};
assert_eq!(response["type"], "response");
assert_eq!(response["command"], "evaluate");
Ok(())
}
#[test]
fn test_stdio_dap_step_in() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"stepIn",
json!({
"threadId": 1,
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "stepIn" {
found = true;
break;
}
}
assert!(found, "stepIn response not received");
Ok(())
}
#[test]
fn test_stdio_dap_step_out() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"stepOut",
json!({
"threadId": 1,
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "stepOut" {
found = true;
break;
}
}
assert!(found, "stepOut response not received");
Ok(())
}
#[test]
fn test_stdio_dap_configuration_done() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(3, "configurationDone", json!({}))?;
let response = loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "configurationDone" {
break msg;
}
};
assert!(response["success"].as_bool().unwrap_or(false));
Ok(())
}
#[test]
fn test_stdio_dap_set_function_breakpoints() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"setFunctionBreakpoints",
json!({
"breakpoints": [{ "name": "main" }],
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "setFunctionBreakpoints" {
found = true;
break;
}
}
assert!(found, "setFunctionBreakpoints response not received");
Ok(())
}
#[test]
fn test_stdio_dap_set_exception_breakpoints() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"setExceptionBreakpoints",
json!({
"filters": ["all"],
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "setExceptionBreakpoints" {
found = true;
break;
}
}
assert!(found, "setExceptionBreakpoints response not received");
Ok(())
}
#[test]
fn test_stdio_dap_pause() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"pause",
json!({
"threadId": 1,
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "pause" {
found = true;
break;
}
}
assert!(found, "pause response not received");
Ok(())
}
#[test]
fn test_stdio_dap_disconnect() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(3, "disconnect", json!({}))?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "disconnect" {
found = true;
break;
}
}
assert!(found, "disconnect response not received");
Ok(())
}
#[test]
fn test_stdio_dap_source() -> anyhow::Result<()> {
let hello_world = std::env::var("CARGO_BIN_EXE_hello_world")
.unwrap_or_else(|_| "./examples/target/debug/hello_world".to_string());
let mut dap = start_bs_stdio_dap(&hello_world)?;
dap.send_request(
1,
"initialize",
json!({
"clientID": "test",
"clientName": "test-client",
"adapterID": "bs-dap",
}),
)?;
let _init_response = dap.read_message()?;
let _init_event = dap.read_message()?;
dap.send_request(
2,
"launch",
json!({
"request": "launch",
"program": &hello_world,
"stopOnEntry": true,
}),
)?;
loop {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "launch" {
break;
}
}
dap.send_request(
3,
"source",
json!({
"source": { "path": &hello_world },
}),
)?;
let mut found = false;
for _ in 0..50 {
let msg = dap.read_message()?;
if msg["type"] == "response" && msg["command"] == "source" {
found = true;
break;
}
}
assert!(found, "source response not received");
Ok(())
}