1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#![cfg(feature = "dap")]
// DEBUG-002: DAP Server CLI Handler Tests
// Sprint 74 - RED Phase
//
// Tests for `pmat debug serve` command implementation
// These tests drive the creation of async DAP server infrastructure
use pmat::services::dap::DapServer;
use tokio::net::TcpListener;
use tokio::time::{timeout, Duration};
// RED Test 1: Handler function exists and reports itself unimplemented
//
// This used to spawn `handle_debug_serve` and comment "Server should be running
// now" — it never was; the handler bailed immediately and the task's Err was
// discarded, so the test passed on a handler that binds no socket at all. The
// handler now exits the process with DEBUG_UNIMPLEMENTED_EXIT_CODE, so awaiting
// it here would take the test binary down. Pin the signature and the honest
// diagnostic instead.
#[test]
fn test_debug_serve_handler_exists() {
// Building the future type-checks the signature without running it.
let _future = pmat::cli::handlers::debug_handlers::handle_debug_serve(
15678,
"127.0.0.1".to_string(),
None,
);
let mut buf = Vec::new();
pmat::cli::handlers::debug_handlers::write_debug_unimplemented_message(
&mut buf,
"serve",
"DEBUG-002",
"host=127.0.0.1 port=15678 record_dir=None",
)
.expect("write");
let s = String::from_utf8(buf).expect("utf8");
assert!(s.contains("DEBUG-002"), "got: {s}");
assert!(s.contains("not implemented"), "got: {s}");
assert_eq!(
pmat::cli::handlers::debug_handlers::DEBUG_UNIMPLEMENTED_EXIT_CODE,
2,
"unimplemented must exit 2 (misuse), not 1"
);
}
// RED Test 2: DAP server can start on specified port
#[tokio::test]
async fn test_dap_server_starts_on_port() {
// This test drives the creation of DapServer::run() method
// The server should bind to a port and be ready to accept connections
let server = DapServer::new();
let port = 15679;
// Spawn server in background task
let server_handle =
tokio::spawn(async move { server.run(port, "127.0.0.1".to_string()).await });
// Give server time to start
tokio::time::sleep(Duration::from_millis(100)).await;
// Verify port is listening by attempting to connect
let connect_result = timeout(
Duration::from_millis(500),
TcpListener::bind(format!("127.0.0.1:{}", port)),
)
.await;
// Port should be in use (connection attempt fails with "already in use")
assert!(
connect_result.is_err() || connect_result.unwrap().is_err(),
"Server should be listening on port {}",
port
);
// Clean up
server_handle.abort();
}
// RED Test 3: Server returns error when port is already in use
#[tokio::test]
async fn test_server_handles_port_in_use() {
// This test ensures proper error handling for port conflicts
let port = 15680;
// Bind the port manually to simulate "already in use"
let _listener = TcpListener::bind(format!("127.0.0.1:{}", port))
.await
.expect("Failed to bind test port");
// Try to start DAP server on same port
let server = DapServer::new();
let result = server.run(port, "127.0.0.1".to_string()).await;
// Should return an error (not panic)
assert!(result.is_err(), "Should fail when port is already in use");
let error_msg = result.unwrap_err().to_string();
assert!(
error_msg.contains("address already in use")
|| error_msg.contains("in use")
|| error_msg.contains("bind"),
"Error should indicate port/address conflict: {}",
error_msg
);
}
// RED Test 4: Handler can be stopped gracefully
#[tokio::test]
async fn test_server_graceful_shutdown() {
// This test ensures the server can be stopped cleanly
// Important for CLI use case where user hits Ctrl+C
let server = DapServer::new();
let port = 15681;
// Start server
let server_handle =
tokio::spawn(async move { server.run(port, "127.0.0.1".to_string()).await });
// Give server time to start
tokio::time::sleep(Duration::from_millis(100)).await;
// Stop server by aborting the task
server_handle.abort();
// Wait a bit for cleanup
tokio::time::sleep(Duration::from_millis(50)).await;
// Verify port is released by successfully binding to it
let bind_result = TcpListener::bind(format!("127.0.0.1:{}", port)).await;
assert!(
bind_result.is_ok(),
"Port should be released after server shutdown"
);
}