From 6b9858d4810049cadd8f71ab3d8b45a4b1471cc4 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Sun, 18 Jan 2026 07:46:04 +0000 Subject: [PATCH] fixes on dev-tools --- crates/host-desktop/src/prometeu_runner.rs | 176 +++++++++++++++++- crates/prometeu-core/src/debugger_protocol.rs | 31 ++- .../src/virtual_machine/value.rs | 5 +- devtools-protocol/protocol.json | 22 +-- 4 files changed, 209 insertions(+), 25 deletions(-) diff --git a/crates/host-desktop/src/prometeu_runner.rs b/crates/host-desktop/src/prometeu_runner.rs index c5cda79f..468eb3ea 100644 --- a/crates/host-desktop/src/prometeu_runner.rs +++ b/crates/host-desktop/src/prometeu_runner.rs @@ -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::(line) { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + if let Ok(cmd) = serde_json::from_str::(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"); + } } diff --git a/crates/prometeu-core/src/debugger_protocol.rs b/crates/prometeu-core/src/debugger_protocol.rs index 49a8b5ea..0eac55dc 100644 --- a/crates/prometeu-core/src/debugger_protocol.rs +++ b/crates/prometeu-core/src/debugger_protocol.rs @@ -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, + stack_top: Vec, 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")); + } +} diff --git a/crates/prometeu-core/src/virtual_machine/value.rs b/crates/prometeu-core/src/virtual_machine/value.rs index 5d23e591..1e89b535 100644 --- a/crates/prometeu-core/src/virtual_machine/value.rs +++ b/crates/prometeu-core/src/virtual_machine/value.rs @@ -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), diff --git a/devtools-protocol/protocol.json b/devtools-protocol/protocol.json index 7c79bdd3..f22366c1 100644 --- a/devtools-protocol/protocol.json +++ b/devtools-protocol/protocol.json @@ -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"] } ]