Skip to main content

fission_shell_desktop/
test_control.rs

1use fission_test_driver::{TestCommand, TestResponse, TextItem, SemanticNode};
2use std::io::{Read, Write};
3use std::net::{TcpListener, TcpStream};
4use std::sync::mpsc;
5
6pub type CommandSender = mpsc::Sender<(TestCommand, ResponseSender)>;
7pub type CommandReceiver = mpsc::Receiver<(TestCommand, ResponseSender)>;
8pub type ResponseSender = mpsc::Sender<TestResponse>;
9
10pub fn create_channel() -> (CommandSender, CommandReceiver) {
11    mpsc::channel()
12}
13
14pub fn spawn_server(port: u16, cmd_tx: CommandSender) -> std::thread::JoinHandle<()> {
15    std::thread::spawn(move || {
16        let listener = TcpListener::bind(format!("127.0.0.1:{}", port))
17            .unwrap_or_else(|e| panic!("failed to bind test control port {}: {}", port, e));
18        eprintln!("[fission-test-control] listening on port {}", port);
19
20        for stream in listener.incoming() {
21            match stream {
22                Ok(stream) => handle_connection(stream, &cmd_tx),
23                Err(e) => eprintln!("[fission-test-control] accept error: {}", e),
24            }
25        }
26    })
27}
28
29fn handle_connection(mut stream: TcpStream, cmd_tx: &CommandSender) {
30    let mut buf = Vec::new();
31    let mut tmp = [0u8; 4096];
32
33    // Read HTTP request
34    loop {
35        match stream.read(&mut tmp) {
36            Ok(0) => return,
37            Ok(n) => {
38                buf.extend_from_slice(&tmp[..n]);
39                // Check for end of HTTP headers
40                if buf.windows(4).any(|w| w == b"\r\n\r\n") {
41                    break;
42                }
43            }
44            Err(_) => return,
45        }
46    }
47
48    let request = String::from_utf8_lossy(&buf);
49
50    // Parse request line
51    let first_line = request.lines().next().unwrap_or("");
52    let parts: Vec<&str> = first_line.split_whitespace().collect();
53    let method = parts.first().copied().unwrap_or("");
54    let path = parts.get(1).copied().unwrap_or("");
55
56    // Health check
57    if path == "/health" {
58        send_http_response(&mut stream, 200, r#"{"status":"ok"}"#);
59        return;
60    }
61
62    if method != "POST" || path != "/cmd" {
63        send_http_response(&mut stream, 404, r#"{"status":"Error","message":"not found"}"#);
64        return;
65    }
66
67    // Extract content-length and body
68    let content_length = request
69        .lines()
70        .find(|l| l.to_lowercase().starts_with("content-length:"))
71        .and_then(|l| l.split(':').nth(1))
72        .and_then(|v| v.trim().parse::<usize>().ok())
73        .unwrap_or(0);
74
75    // Find body start
76    let header_end = buf
77        .windows(4)
78        .position(|w| w == b"\r\n\r\n")
79        .map(|p| p + 4)
80        .unwrap_or(buf.len());
81
82    let mut body = buf[header_end..].to_vec();
83
84    // Read remaining body if needed
85    while body.len() < content_length {
86        match stream.read(&mut tmp) {
87            Ok(0) => break,
88            Ok(n) => body.extend_from_slice(&tmp[..n]),
89            Err(_) => break,
90        }
91    }
92
93    let body_str = String::from_utf8_lossy(&body);
94
95    // Parse command
96    let cmd: TestCommand = match serde_json::from_str(&body_str) {
97        Ok(c) => c,
98        Err(e) => {
99            let resp = TestResponse::Error {
100                message: format!("parse error: {}", e),
101            };
102            send_http_response(&mut stream, 400, &serde_json::to_string(&resp).unwrap());
103            return;
104        }
105    };
106
107    // Send to event loop and wait for response
108    let (resp_tx, resp_rx) = mpsc::channel();
109    if cmd_tx.send((cmd, resp_tx)).is_err() {
110        send_http_response(
111            &mut stream,
112            500,
113            r#"{"status":"Error","message":"event loop disconnected"}"#,
114        );
115        return;
116    }
117
118    // Wait for response (with timeout)
119    match resp_rx.recv_timeout(std::time::Duration::from_secs(30)) {
120        Ok(response) => {
121            send_http_response(&mut stream, 200, &serde_json::to_string(&response).unwrap());
122        }
123        Err(_) => {
124            send_http_response(
125                &mut stream,
126                504,
127                r#"{"status":"Error","message":"timeout waiting for response"}"#,
128            );
129        }
130    }
131}
132
133fn send_http_response(stream: &mut TcpStream, status: u16, body: &str) {
134    let status_text = match status {
135        200 => "OK",
136        400 => "Bad Request",
137        404 => "Not Found",
138        500 => "Internal Server Error",
139        504 => "Gateway Timeout",
140        _ => "Unknown",
141    };
142    let response = format!(
143        "HTTP/1.1 {} {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
144        status, status_text, body.len(), body
145    );
146    let _ = stream.write_all(response.as_bytes());
147}