From 8ffe19cd5f8c083bbf034333e54960694a48775f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=8D=9A=E6=96=87?= <1323515022@qq.com> Date: Fri, 30 Oct 2026 12:11:28 +0800 Subject: [PATCH] =?UTF-8?q?test(agent-core):=20v3=20=E5=88=86=E5=8F=91?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=8D=95=E6=B5=8B=E2=80=94=E2=80=94=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=90=8E=E7=AB=AF=E4=B8=8D=20panic=E3=80=81=E6=88=AA?= =?UTF-8?q?=E6=96=AD=E5=A5=91=E7=BA=A6=E3=80=81=E7=A9=BA=E8=BE=93=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新功能必须带测试,agent-core 此前无测试模块,v3 新增的 text 默认实现与 截断分支正好落在盲区里: - 后端不实现 text() 时(Linux/X11 的真实形态)必须被正常调用且不 panic; - 超 MAX_TEXT_UNITS 必须以截断后的长度到注入侧,未超限原样; - 空 Text 仍会下发到后端——被控端不假设对端守规矩,线格式输入按不可信处理。 --- agent-core/src/lib.rs | 128 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/agent-core/src/lib.rs b/agent-core/src/lib.rs index 7279aaf..f30de67 100644 --- a/agent-core/src/lib.rs +++ b/agent-core/src/lib.rs @@ -509,3 +509,131 @@ where println!("[agent] session closed"); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + /// 记录调用轨迹的注入后端:验证分发与截断,不碰任何真实输入。 + #[derive(Default)] + struct MockInjector { + calls: Mutex>, + } + + impl MockInjector { + fn take(&self) -> Vec { + let mut g = self.calls.lock().unwrap(); + std::mem::take(&mut g) + } + } + + impl InputInjector for MockInjector { + fn mouse_move(&self, x: u16, y: u16) { + self.calls.lock().unwrap().push(format!("move {x},{y}")); + } + fn mouse_button(&self, b: MouseButton, down: bool) { + self.calls.lock().unwrap().push(format!("btn {b:?} {down}")); + } + fn wheel(&self, dy: i32) { + self.calls.lock().unwrap().push(format!("wheel {dy}")); + } + fn key(&self, scancode: u16, extended: bool, down: bool) { + self.calls + .lock() + .unwrap() + .push(format!("key {scancode:#x} ext={extended} {down}")); + } + // 刻意**不**实现 text():让默认实现进入测试面,验证「后端没做文本注入 + // 时也必须被 dispatch_input 正常调用、且不 panic」——Linux 端正是这个形态。 + } + + /// 实现了 text() 的后端,用来验证截断后的长度真的传给了注入侧。 + struct TextCapableInjector { + received: Mutex>, + } + + impl InputInjector for TextCapableInjector { + fn mouse_move(&self, _x: u16, _y: u16) {} + fn mouse_button(&self, _b: MouseButton, _down: bool) {} + fn wheel(&self, _dy: i32) {} + fn key(&self, _scancode: u16, _extended: bool, _down: bool) {} + fn text(&self, units: &[u16]) { + self.received.lock().unwrap().push(units.len()); + } + } + + #[test] + fn dispatch_routes_each_event_kind() { + let inj = MockInjector::default(); + dispatch_input(&inj, InputEvent::MouseMove { x: 1, y: 2 }); + dispatch_input(&inj, InputEvent::MouseDown(MouseButton::Right)); + dispatch_input(&inj, InputEvent::Wheel { dy: -120 }); + dispatch_input( + &inj, + InputEvent::KeyDown { + scancode: 0x48, + extended: true, + }, + ); + let calls = inj.take(); + assert_eq!( + calls, + vec![ + "move 1,2", + "btn Right true", + "wheel -120", + "key 0x48 ext=true true", + ] + ); + } + + /// v3 关键回归:`Text` 必须被分发,且**后端没有实现 text() 时不能 panic**。 + /// 这是 Linux/X11 后端的真实形态——用户对着 Linux 被控端打字会走到这里。 + #[test] + fn text_event_reaches_default_backend_without_panic() { + let inj = MockInjector::default(); + dispatch_input( + &inj, + InputEvent::Text { + units: vec![0x4F60, 0x597D], + }, + ); + // 默认实现只记日志,不产生调用记录;此处断言"没炸"即为通过。 + assert!(inj.take().is_empty()); + } + + /// 截断契约:超过 `MAX_TEXT_UNITS` 的输入必须以截断后的长度到达注入侧。 + /// 不截断会让长粘贴把远端注入队列刷爆;截断不告知则是静默丢数据。 + #[test] + fn text_is_clamped_to_protocol_cap() { + let inj = TextCapableInjector { + received: Mutex::new(vec![]), + }; + dispatch_input( + &inj, + InputEvent::Text { + units: vec![0x61; common::MAX_TEXT_UNITS + 500], + }, + ); + dispatch_input( + &inj, + InputEvent::Text { + units: vec![0x62; 10], + }, + ); + let got = inj.received.lock().unwrap().clone(); + assert_eq!(got, vec![common::MAX_TEXT_UNITS, 10], "超限截断、未超限原样"); + } + + /// 空 Text 仍会调用后端(是否忽略由后端决定):主控侧 `send_text` 已拦掉空输入, + /// 但被控端不能假设对端守规矩——线格式输入一律按不可信处理。 + #[test] + fn empty_text_still_dispatches() { + let inj = TextCapableInjector { + received: Mutex::new(vec![]), + }; + dispatch_input(&inj, InputEvent::Text { units: vec![] }); + assert_eq!(inj.received.lock().unwrap().clone(), vec![0]); + } +}