From 1827310dca9699c7a39717a3659bfc161030ecd1 Mon Sep 17 00:00:00 2001 From: showen Date: Fri, 13 Mar 2026 06:48:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20DevicePlugin=20Task4=20=E2=80=94=207?= =?UTF-8?q?=E4=B8=AA=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95(MockBackend+?= =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96)=20=E6=80=BB=E8=AE=A173=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .showen/TEAM_CHAT.md | 44 ++++++++ souls/li-siqi.md | 17 +++ src/core/message.rs | 2 +- src/plugins/device/mod.rs | 3 + src/plugins/device/tests.rs | 218 ++++++++++++++++++++++++++++++++++++ 5 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 src/plugins/device/tests.rs diff --git a/.showen/TEAM_CHAT.md b/.showen/TEAM_CHAT.md index 4b9d18c..95dd2c6 100644 --- a/.showen/TEAM_CHAT.md +++ b/.showen/TEAM_CHAT.md @@ -2267,3 +2267,47 @@ Task 1 已完成,可以进入 Task 2(DevicePlugin 骨架与 Backend trait) Task 3 已完成,可以进入 Task 4(集成测试与文档)。 + +--- + +## 2026-03-13 李思琪 — Task 4 完成汇报 + +### 任务概述 +DevicePlugin 阶段一 Task 4:集成测试 ✅ + +### 完成内容 +1. **创建 src/plugins/device/tests.rs**,包含 7 个测试用例: + - `test_device_command_serialization` — 测试 12 个 DeviceCommand 变体的 JSON 序列化/反序列化往返 + - `test_device_response_serialization` — 测试 6 个 DeviceResponse 变体的 JSON 序列化/反序列化往返 + - `test_device_event_serialization` — 测试 6 个 DeviceEvent 变体的 JSON 序列化/反序列化往返 + - `test_mock_backend_capabilities` — 验证 MockBackend 返回正确能力列表(Display + Backlight) + - `test_mock_backend_get_display_info` — 验证 GetDisplayInfo 返回 1280x800 RGB888 + - `test_mock_backend_set_sleep_inhibit` — 验证 SetSleepInhibit 返回 Ok + - `test_mock_backend_unsupported_command` — 验证未实现命令返回 Error("not supported") + +2. **实现 MockBackend**: + - 实现 DeviceBackend trait,不依赖真实硬件 + - name() 返回 "mock" + - capabilities() 返回 vec![Display, Backlight] + - handle_command() 处理 GetDisplayInfo、SetSleepInhibit、SetBacklight,其他返回 Error + +3. **修复编译问题**: + - 发现 DeviceCapability 缺少 PartialEq trait(测试中需要 contains() 比较) + - 在 src/core/message.rs:138 添加 PartialEq 派生 + +4. **在 src/plugins/device/mod.rs 添加 `#[cfg(test)] mod tests;`** + +### 验收结果 +- ✅ `cargo check --workspace --all-targets` 零 warning +- ✅ `cargo test --workspace` 全部通过(73 个测试,新增 7 个) +- ✅ 测试覆盖所有核心消息类型的序列化 +- ✅ MockBackend 实现完整,可用于测试 + +### 技术要点 +- 序列化测试采用往返验证(serialize → deserialize → serialize),确保 JSON 稳定性 +- MockBackend 返回预定义数据,避免依赖 /sys/class/graphics 等硬件路径 +- 测试用例覆盖了所有 DeviceCommand/DeviceResponse/DeviceEvent 变体 + +### 下一步 +Task 4 已完成,DevicePlugin 阶段一基础框架全部就绪。等待 PM 刘建国向 CEO 汇报。 + diff --git a/souls/li-siqi.md b/souls/li-siqi.md index 3f9f189..6b039fe 100644 --- a/souls/li-siqi.md +++ b/souls/li-siqi.md @@ -48,3 +48,20 @@ - 精通 Message 序列化链路和 serde 陷阱 - 熟悉 service_manager 广播机制 - 理解 Arc 与序列化的冲突场景 +- 掌握 DevicePlugin 测试框架和 MockBackend 模式 + +## 个人经验 (2026-03-13 Task 4) +- 完成 DevicePlugin 阶段一 Task 4:集成测试 +- 创建 src/plugins/device/tests.rs,包含 7 个测试用例: + - test_device_command_serialization — 测试 12 个 DeviceCommand 变体的 JSON 往返 + - test_device_response_serialization — 测试 6 个 DeviceResponse 变体的 JSON 往返 + - test_device_event_serialization — 测试 6 个 DeviceEvent 变体的 JSON 往返 + - test_mock_backend_capabilities — 验证 MockBackend 返回 Display + Backlight + - test_mock_backend_get_display_info — 验证返回 1280x800 RGB888 + - test_mock_backend_set_sleep_inhibit — 验证返回 Ok + - test_mock_backend_unsupported_command — 验证未实现命令返回 Error("not supported") +- 实现 MockBackend (impl DeviceBackend),不依赖硬件,用于测试 +- 发现 DeviceCapability 缺少 PartialEq,补充后通过编译 +- cargo check --workspace --all-targets 零 warning +- cargo test --workspace 全部通过(73 个测试,新增 7 个) +- 测试覆盖了所有核心消息类型的序列化和 MockBackend 的基本行为 diff --git a/src/core/message.rs b/src/core/message.rs index 03497d9..040f5be 100644 --- a/src/core/message.rs +++ b/src/core/message.rs @@ -135,7 +135,7 @@ pub enum TouchAction { } /// 设备能力 -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum DeviceCapability { /// 显示屏 Display, diff --git a/src/plugins/device/mod.rs b/src/plugins/device/mod.rs index d9b129d..972f0ea 100644 --- a/src/plugins/device/mod.rs +++ b/src/plugins/device/mod.rs @@ -151,3 +151,6 @@ impl Plugin for DevicePlugin { Ok(()) } } + +#[cfg(test)] +mod tests; diff --git a/src/plugins/device/tests.rs b/src/plugins/device/tests.rs new file mode 100644 index 0000000..0100415 --- /dev/null +++ b/src/plugins/device/tests.rs @@ -0,0 +1,218 @@ +//! DevicePlugin 集成测试 +//! +//! 测试 DeviceCommand/DeviceResponse/DeviceEvent 的序列化以及 MockBackend 的行为。 + +use crate::core::message::{ + DeviceCapability, DeviceCommand, DeviceEvent, DeviceResponse, PixelFormat, SensorType, + TouchAction, +}; +use crate::plugins::device::backend::DeviceBackend; +use anyhow::Result; + +/// MockBackend — 用于测试的模拟设备后端 +/// +/// 不依赖真实硬件,返回预定义的响应数据。 +struct MockBackend { + initialized: bool, +} + +impl MockBackend { + fn new() -> Self { + Self { initialized: false } + } +} + +impl DeviceBackend for MockBackend { + fn name(&self) -> &str { + "mock" + } + + fn init(&mut self, _config: &serde_json::Value) -> Result<()> { + self.initialized = true; + Ok(()) + } + + fn capabilities(&self) -> Vec { + vec![DeviceCapability::Display, DeviceCapability::Backlight] + } + + fn handle_command(&mut self, cmd: DeviceCommand) -> Result { + match cmd { + DeviceCommand::GetDisplayInfo => Ok(DeviceResponse::DisplayInfo { + width: 1280, + height: 800, + format: PixelFormat::RGB888, + }), + DeviceCommand::SetSleepInhibit(_) => Ok(DeviceResponse::Ok), + DeviceCommand::SetBacklight(_) => Ok(DeviceResponse::Ok), + _ => Ok(DeviceResponse::Error("not supported".to_string())), + } + } + + fn shutdown(&mut self) -> Result<()> { + self.initialized = false; + Ok(()) + } +} + +// ── 序列化测试 ── + +#[test] +fn test_device_command_serialization() { + let commands = vec![ + DeviceCommand::GetDisplayInfo, + DeviceCommand::SetBrightness(75), + DeviceCommand::SetBacklight(true), + DeviceCommand::WriteFramebuffer { + data: vec![0xFF, 0x00, 0xFF], + format: PixelFormat::RGB888, + }, + DeviceCommand::SetSleepInhibit(true), + DeviceCommand::GetBatteryLevel, + DeviceCommand::SetVolume(50), + DeviceCommand::PlayAudio { + path: "/audio/test.wav".to_string(), + }, + DeviceCommand::GetTouchEvents, + DeviceCommand::GetButtonState, + DeviceCommand::GetSensorData(SensorType::Temperature), + DeviceCommand::CustomCommand { + subsystem: "gpio".to_string(), + payload: serde_json::json!({"pin": 17, "value": 1}), + }, + ]; + + for cmd in commands { + let json = serde_json::to_string(&cmd).expect("DeviceCommand should serialize"); + let decoded: DeviceCommand = + serde_json::from_str(&json).expect("DeviceCommand should deserialize"); + let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again"); + assert_eq!(json, json2, "DeviceCommand round trip should be stable"); + } +} + +#[test] +fn test_device_response_serialization() { + let responses = vec![ + DeviceResponse::DisplayInfo { + width: 1920, + height: 1080, + format: PixelFormat::RGBA8888, + }, + DeviceResponse::SensorData { + sensor: SensorType::Temperature, + value: 23.5, + }, + DeviceResponse::BatteryLevel(85), + DeviceResponse::Ok, + DeviceResponse::Error("device not found".to_string()), + DeviceResponse::Custom(serde_json::json!({"status": "ready"})), + ]; + + for resp in responses { + let json = serde_json::to_string(&resp).expect("DeviceResponse should serialize"); + let decoded: DeviceResponse = + serde_json::from_str(&json).expect("DeviceResponse should deserialize"); + let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again"); + assert_eq!(json, json2, "DeviceResponse round trip should be stable"); + } +} + +#[test] +fn test_device_event_serialization() { + let events = vec![ + DeviceEvent::TouchEvent { + x: 100, + y: 200, + action: TouchAction::Down, + }, + DeviceEvent::ButtonEvent { + button: 1, + pressed: true, + }, + DeviceEvent::BatteryLow(15), + DeviceEvent::DisplayConnected, + DeviceEvent::DisplayDisconnected, + DeviceEvent::SensorAlert { + sensor: SensorType::Temperature, + value: 85.0, + }, + ]; + + for event in events { + let json = serde_json::to_string(&event).expect("DeviceEvent should serialize"); + let decoded: DeviceEvent = + serde_json::from_str(&json).expect("DeviceEvent should deserialize"); + let json2 = serde_json::to_string(&decoded).expect("decoded should serialize again"); + assert_eq!(json, json2, "DeviceEvent round trip should be stable"); + } +} + +// ── MockBackend 行为测试 ── + +#[test] +fn test_mock_backend_capabilities() { + let backend = MockBackend::new(); + let caps = backend.capabilities(); + assert_eq!(caps.len(), 2); + assert!(caps.contains(&DeviceCapability::Display)); + assert!(caps.contains(&DeviceCapability::Backlight)); +} + +#[test] +fn test_mock_backend_get_display_info() { + let mut backend = MockBackend::new(); + backend + .init(&serde_json::json!({})) + .expect("init should succeed"); + + let response = backend + .handle_command(DeviceCommand::GetDisplayInfo) + .expect("GetDisplayInfo should succeed"); + + match response { + DeviceResponse::DisplayInfo { + width, + height, + format, + } => { + assert_eq!(width, 1280); + assert_eq!(height, 800); + assert!(matches!(format, PixelFormat::RGB888)); + } + _ => panic!("expected DisplayInfo response"), + } +} + +#[test] +fn test_mock_backend_set_sleep_inhibit() { + let mut backend = MockBackend::new(); + backend + .init(&serde_json::json!({})) + .expect("init should succeed"); + + let response = backend + .handle_command(DeviceCommand::SetSleepInhibit(true)) + .expect("SetSleepInhibit should succeed"); + + assert!(matches!(response, DeviceResponse::Ok)); +} + +#[test] +fn test_mock_backend_unsupported_command() { + let mut backend = MockBackend::new(); + backend + .init(&serde_json::json!({})) + .expect("init should succeed"); + + let response = backend + .handle_command(DeviceCommand::GetBatteryLevel) + .expect("unsupported command should return Error response"); + + match response { + DeviceResponse::Error(msg) => { + assert_eq!(msg, "not supported"); + } + _ => panic!("expected Error response for unsupported command"), + } +}