fixes on dev-tools
This commit is contained in:
parent
47a2f9971d
commit
6b9858d481
@ -105,9 +105,9 @@ impl PrometeuRunner {
|
||||
}
|
||||
|
||||
fn check_debug_commands(&mut self) {
|
||||
if self.debug_waiting_for_start {
|
||||
if let Some(listener) = &self.debug_listener {
|
||||
if let Ok((stream, _addr)) = listener.accept() {
|
||||
if let Some(listener) = &self.debug_listener {
|
||||
if let Ok((stream, _addr)) = listener.accept() {
|
||||
if self.debug_stream.is_none() {
|
||||
println!("[Debugger] Connection received!");
|
||||
stream.set_nonblocking(true).expect("Cannot set non-blocking on stream");
|
||||
|
||||
@ -125,6 +125,8 @@ impl PrometeuRunner {
|
||||
},
|
||||
};
|
||||
self.send_debug_response(handshake);
|
||||
} else {
|
||||
println!("[Debugger] Connection refused: already connected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,17 +137,25 @@ impl PrometeuRunner {
|
||||
Ok(0) => {
|
||||
println!("[Debugger] Connection closed by remote.");
|
||||
self.debug_stream = None;
|
||||
self.firmware.os.paused = false;
|
||||
self.debug_waiting_for_start = false;
|
||||
}
|
||||
Ok(n) => {
|
||||
let data = &buf[..n];
|
||||
// Processar múltiplos comandos se houver \n
|
||||
let msg = String::from_utf8_lossy(data);
|
||||
|
||||
self.debug_stream = Some(stream); // Coloca de volta antes de processar comandos
|
||||
|
||||
for line in msg.lines() {
|
||||
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(line) {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(trimmed) {
|
||||
self.handle_debug_command(cmd);
|
||||
}
|
||||
}
|
||||
self.debug_stream = Some(stream);
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
self.debug_stream = Some(stream);
|
||||
@ -153,6 +163,8 @@ impl PrometeuRunner {
|
||||
Err(e) => {
|
||||
eprintln!("[Debugger] Connection error: {}", e);
|
||||
self.debug_stream = None;
|
||||
self.firmware.os.paused = false;
|
||||
self.debug_waiting_for_start = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,7 +181,6 @@ impl PrometeuRunner {
|
||||
if self.debug_waiting_for_start {
|
||||
println!("[Debugger] Starting execution...");
|
||||
self.debug_waiting_for_start = false;
|
||||
self.debug_listener = None;
|
||||
}
|
||||
self.firmware.os.paused = false;
|
||||
}
|
||||
@ -190,9 +201,9 @@ impl PrometeuRunner {
|
||||
}
|
||||
DebugCommand::GetState => {
|
||||
let stack_top = self.firmware.vm.operand_stack.iter()
|
||||
.rev().take(10).filter_map(|v| v.as_integer()).collect();
|
||||
.rev().take(10).cloned().collect();
|
||||
|
||||
let resp = DebugResponse::State {
|
||||
let resp = DebugResponse::GetState {
|
||||
pc: self.firmware.vm.pc,
|
||||
stack_top,
|
||||
frame_index: self.firmware.os.logical_frame_index,
|
||||
@ -441,7 +452,6 @@ impl ApplicationHandler for PrometeuRunner {
|
||||
|
||||
if is_down && code == KeyCode::KeyD && self.debug_waiting_for_start {
|
||||
self.debug_waiting_for_start = false;
|
||||
self.debug_listener = None; // Fecha o socket
|
||||
println!("[Debugger] Execution started!");
|
||||
}
|
||||
|
||||
@ -703,7 +713,7 @@ mod tests {
|
||||
// Processa o comando recebido
|
||||
runner.check_debug_commands();
|
||||
assert!(!runner.debug_waiting_for_start, "Execução deve ter iniciado após comando start");
|
||||
assert!(runner.debug_listener.is_none(), "Listener deve ter sido fechado");
|
||||
assert!(runner.debug_listener.is_some(), "Listener deve permanecer aberto para reconexões");
|
||||
}
|
||||
|
||||
// Agora que o stream saiu do escopo do teste, o runner deve detectar o fechamento no próximo check
|
||||
@ -711,4 +721,150 @@ mod tests {
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.debug_stream.is_none(), "Stream deve ter sido fechado após o cliente desconectar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_reconnection() {
|
||||
let mut runner = PrometeuRunner::new(None, None);
|
||||
let port = 9998;
|
||||
runner.set_boot_target(BootTarget::Cartridge {
|
||||
path: "dummy.bin".to_string(),
|
||||
debug: true,
|
||||
debug_port: port,
|
||||
});
|
||||
|
||||
// 1. Conecta e inicia
|
||||
{
|
||||
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 1");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.debug_stream.is_some());
|
||||
|
||||
stream.write_all(b"{\"type\":\"start\"}\n").expect("Deve escrever start");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(!runner.debug_waiting_for_start);
|
||||
// Atualmente o listener é fechado aqui.
|
||||
}
|
||||
|
||||
// 2. Desconecta (limpa o stream no runner)
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.debug_stream.is_none());
|
||||
|
||||
// 3. Tenta reconectar - DEVE FALHAR atualmente, mas queremos que FUNCIONE
|
||||
let stream2 = TcpStream::connect(format!("127.0.0.1:{}", port));
|
||||
assert!(stream2.is_ok(), "Deve aceitar nova conexão mesmo após o start");
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.debug_stream.is_some(), "Stream deve ter sido aceito na reconexão");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_refuse_second_connection() {
|
||||
let mut runner = PrometeuRunner::new(None, None);
|
||||
let port = 9997;
|
||||
runner.set_boot_target(BootTarget::Cartridge {
|
||||
path: "dummy.bin".to_string(),
|
||||
debug: true,
|
||||
debug_port: port,
|
||||
});
|
||||
|
||||
// 1. Primeira conexão
|
||||
let mut _stream1 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 1");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.debug_stream.is_some());
|
||||
|
||||
// 2. Segunda conexão
|
||||
let mut stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 2 (SO aceita)");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands(); // Deve aceitar e fechar stream2
|
||||
|
||||
// Verifica se stream2 foi fechado pelo servidor
|
||||
let mut buf = [0u8; 10];
|
||||
stream2.set_read_timeout(Some(std::time::Duration::from_millis(100))).unwrap();
|
||||
let res = stream2.read(&mut buf);
|
||||
assert!(matches!(res, Ok(0)) || res.is_err(), "Segunda conexão deve ser fechada pelo servidor");
|
||||
|
||||
assert!(runner.debug_stream.is_some(), "Primeira conexão deve continuar ativa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_state_returns_response() {
|
||||
let mut runner = PrometeuRunner::new(None, None);
|
||||
let port = 9996;
|
||||
runner.set_boot_target(BootTarget::Cartridge {
|
||||
path: "dummy.bin".to_string(),
|
||||
debug: true,
|
||||
debug_port: port,
|
||||
});
|
||||
|
||||
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar");
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
runner.check_debug_commands();
|
||||
|
||||
// Limpa o buffer do handshake
|
||||
let mut buf = [0u8; 2048];
|
||||
let n = stream.read(&mut buf).expect("Deve ler handshake");
|
||||
assert!(n > 0);
|
||||
|
||||
// Envia getState
|
||||
stream.write_all(b"{\"type\":\"getState\"}\n").expect("Deve escrever getState");
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
runner.check_debug_commands();
|
||||
|
||||
// Verifica se recebeu resposta
|
||||
stream.set_read_timeout(Some(std::time::Duration::from_millis(500))).unwrap();
|
||||
let mut full_resp = Vec::new();
|
||||
let mut temp_buf = [0u8; 1024];
|
||||
loop {
|
||||
match stream.read(&mut temp_buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
full_resp.extend_from_slice(&temp_buf[..n]);
|
||||
if full_resp.contains(&b'\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
assert!(!full_resp.is_empty(), "Resposta deve ter conteúdo");
|
||||
|
||||
let resp: serde_json::Value = serde_json::from_slice(&full_resp).expect("Resposta deve ser JSON válido");
|
||||
assert_eq!(resp["type"], "getState");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debug_resume_on_disconnect() {
|
||||
let mut runner = PrometeuRunner::new(None, None);
|
||||
let port = 9995;
|
||||
runner.set_boot_target(BootTarget::Cartridge {
|
||||
path: "dummy.bin".to_string(),
|
||||
debug: true,
|
||||
debug_port: port,
|
||||
});
|
||||
|
||||
// 1. Conecta e pausa
|
||||
{
|
||||
let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
|
||||
stream.write_all(b"{\"type\":\"pause\"}\n").expect("Deve escrever pause");
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
assert!(runner.firmware.os.paused, "VM deve estar pausada");
|
||||
}
|
||||
|
||||
// 2. Desconecta (stream sai de escopo)
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
runner.check_debug_commands();
|
||||
|
||||
// 3. Verifica se despausou
|
||||
assert!(!runner.firmware.os.paused, "VM deve ter despausado após desconexão");
|
||||
assert!(!runner.debug_waiting_for_start, "VM deve ter saído do estado waiting_for_start");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::model::AppMode;
|
||||
|
||||
use crate::virtual_machine::Value;
|
||||
|
||||
pub const DEVTOOLS_PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@ -37,10 +39,10 @@ pub enum DebugResponse {
|
||||
runtime_version: String,
|
||||
cartridge: HandshakeCartridge,
|
||||
},
|
||||
#[serde(rename = "state")]
|
||||
State {
|
||||
#[serde(rename = "getState")]
|
||||
GetState {
|
||||
pc: usize,
|
||||
stack_top: Vec<i64>,
|
||||
stack_top: Vec<Value>,
|
||||
frame_index: u64,
|
||||
app_id: u32,
|
||||
},
|
||||
@ -87,3 +89,26 @@ pub enum DebugEvent {
|
||||
frame_index: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::virtual_machine::Value;
|
||||
|
||||
#[test]
|
||||
fn test_get_state_serialization() {
|
||||
let resp = DebugResponse::GetState {
|
||||
pc: 42,
|
||||
stack_top: vec![Value::Integer(10), Value::String("test".into()), Value::Boolean(true)],
|
||||
frame_index: 5,
|
||||
app_id: 1,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&resp).unwrap();
|
||||
assert!(json.contains("\"type\":\"getState\""));
|
||||
assert!(json.contains("\"pc\":42"));
|
||||
assert!(json.contains("\"stack_top\":[10,\"test\",true]"));
|
||||
assert!(json.contains("\"frame_index\":5"));
|
||||
assert!(json.contains("\"app_id\":1"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
#[derive(Debug, Clone)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
|
||||
@ -11,50 +11,50 @@
|
||||
},
|
||||
"requests": [
|
||||
{
|
||||
"name": "pause",
|
||||
"type": "pause",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "resume",
|
||||
"type": "resume",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "step",
|
||||
"type": "step",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "stepFrame",
|
||||
"type": "stepFrame",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"name": "getState",
|
||||
"type": "getState",
|
||||
"params": [],
|
||||
"response": ["pc", "stack_top", "frame_index", "app_id"]
|
||||
},
|
||||
{
|
||||
"name": "setBreakpoint",
|
||||
"type": "setBreakpoint",
|
||||
"params": ["pc"]
|
||||
},
|
||||
{
|
||||
"name": "clearBreakpoint",
|
||||
"type": "clearBreakpoint",
|
||||
"params": ["pc"]
|
||||
}
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"name": "breakpointHit",
|
||||
"event": "breakpointHit",
|
||||
"fields": ["pc", "frame_index"]
|
||||
},
|
||||
{
|
||||
"name": "log",
|
||||
"event": "log",
|
||||
"fields": ["level", "source", "msg"]
|
||||
},
|
||||
{
|
||||
"name": "telemetry",
|
||||
"event": "telemetry",
|
||||
"fields": ["frame_index", "vm_steps", "syscalls", "cycles"]
|
||||
},
|
||||
{
|
||||
"name": "cert",
|
||||
"event": "cert",
|
||||
"fields": ["rule", "used", "limit", "frame_index"]
|
||||
}
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user