Merged
test(drivers): synchronize asynchronous lifecycle fixtures #1334
test(drivers): synchronize asynchronous lifecycle fixtures #1334
Merged
liuxingsenAdd on 25 days ago
Total: 11 files changed+443-222
@@ -210,7 +210,7 @@ MC_REFLECT(drivers::pcie_nic_card::hisi::hi1822_fc::test::test_chip_object, ((m_
210 210 
211using namespace drivers::pcie_nic_card::hisi::hi1822_fc::test;211using namespace drivers::pcie_nic_card::hisi::hi1822_fc::test;
212 212 
213-TEST_F(Hi1822FcCardTest, DISABLED_Start_WithPorts)213+TEST_F(Hi1822FcCardTest, Start_WithPorts)
214{214{
215 static int port_counter = 0;215 static int port_counter = 0;
216 port_counter++;216 port_counter++;
@@ -231,24 +231,33 @@ TEST_F(Hi1822FcCardTest, DISABLED_Start_WithPorts)
231 port2->set_position(port2_position.c_str());231 port2->set_position(port2_position.c_str());
232 port2->set_parent(m_card.get());232 port2->set_parent(m_card.get());
233 233 
234- auto csr1 = create_test_csr();234+ auto csr1 = create_test_csr();
235- csr1["PortId"] = port_counter;235+ csr1["bmc.dev.NetworkPort"] = mc::dict{{"PortId", uint8_t{0}}};
236- auto connector = create_test_connector();236+ auto connector = create_test_connector();
237 port1->init(csr1, connector);237 port1->init(csr1, connector);
238 238 
239- auto csr2 = create_test_csr();239+ auto csr2 = create_test_csr();
240- csr2["PortId"] = port_counter + 1;240+ csr2["bmc.dev.NetworkPort"] = mc::dict{{"PortId", uint8_t{1}}};
241 port2->init(csr2, connector);241 port2->init(csr2, connector);
242 242 
243 m_test_service->register_object(port1);243 m_test_service->register_object(port1);
244 m_test_service->register_object(port2);244 m_test_service->register_object(port2);
245 245 
246- m_card->init_network_ports();246+ auto optical1 = mc::make_shared<dev::hi1822_fc_optical_module>();
247+ optical1->set_object_name("OpticalTransceiver_1");
248+ optical1->set_parent(port1.get());
249+ auto optical2 = mc::make_shared<dev::hi1822_fc_optical_module>();
250+ optical2->set_object_name("OpticalTransceiver_2");
251+ optical2->set_parent(port2.get());
247 252 
248 auto card_csr = create_test_csr();253 auto card_csr = create_test_csr();
249 m_card->init(card_csr, connector);254 m_card->init(card_csr, connector);
250 bool result = m_card->start();255 bool result = m_card->start();
251 EXPECT_TRUE(result);256 EXPECT_TRUE(result);
257+ EXPECT_EQ(port1->m_optical_module, optical1.get());
258+ EXPECT_EQ(port2->m_optical_module, optical2.get());
259+ EXPECT_EQ(optical1->m_optical_module.get_port_id(), 0);
260+ EXPECT_EQ(optical2->m_optical_module.get_port_id(), 1);
252 261 
253 // 等待 start() 中的异步操作完成,包括端口启动和 IMU 协议初始化262 // 等待 start() 中的异步操作完成,包括端口启动和 IMU 协议初始化
254 std::this_thread::sleep_for(std::chrono::milliseconds(30));263 std::this_thread::sleep_for(std::chrono::milliseconds(30));
@@ -685,7 +685,7 @@ TEST_F(network_adapter_fault_status_test, check_netCard_Health_by_smbus_empty_re
685 m_fault_status->stop_smbus_update_task();685 m_fault_status->stop_smbus_update_task();
686}686}
687 687 
688-TEST_F(network_adapter_fault_status_test, DISABLED_updata_fault_code_by_smbus_success)688+TEST_F(network_adapter_fault_status_test, updata_fault_code_by_smbus_success)
689{689{
690 std::string health_response;690 std::string health_response;
691 uint8_t health = 1;691 uint8_t health = 1;
@@ -703,18 +703,26 @@ TEST_F(network_adapter_fault_status_test, DISABLED_updata_fault_code_by_smbus_su
703 m_fault_status->start_smbus_update_task(m_smbus, mc::milliseconds(100));703 m_fault_status->start_smbus_update_task(m_smbus, mc::milliseconds(100));
704 m_fault_status->check_netCard_Health_by_smbus();704 m_fault_status->check_netCard_Health_by_smbus();
705 m_fault_status->updata_fault_code_by_smbus();705 m_fault_status->updata_fault_code_by_smbus();
706- // 方法没有返回值,主要验证不崩溃706+ EXPECT_EQ(m_fault_status->FaultCode.get_value().as<std::string>(), "4097, 4098");
707 707 
708 // 显式停止定时器,避免在 TearDown 时定时器仍在运行708 // 显式停止定时器,避免在 TearDown 时定时器仍在运行
709 m_fault_status->stop_smbus_update_task();709 m_fault_status->stop_smbus_update_task();
710}710}
711 711 
712-TEST_F(network_adapter_fault_status_test, DISABLED_updata_fault_code_by_smbus_empty_response)712+TEST_F(network_adapter_fault_status_test, updata_fault_code_by_smbus_empty_response)
713{713{
714- m_smbus->set_send_request_result("");714+ auto oem = mc::make_shared<dev::NetworkAdapter_Oem>();
715- m_fault_status->start_smbus_update_task(m_smbus, mc::milliseconds(100));715+ oem->SmBiosStatus.set_value(uint8_t{0});
716- 716+ m_fault_status->set_network_adapter_oem(oem.get());
717+ m_fault_status->FaultCode.set_value("stale fault");
718+ m_smbus->set_send_request_result_for_opcode(0x01, std::string(1, '\1'));
719+ m_smbus->set_send_request_result_for_opcode(0x02, "");
720+ m_fault_status->start_smbus_update_task(m_smbus, mc::milliseconds(60000));
721+ m_fault_status->check_netCard_Health_by_smbus();
717 m_fault_status->updata_fault_code_by_smbus();722 m_fault_status->updata_fault_code_by_smbus();
723+ EXPECT_EQ(m_fault_status->FaultCode.get_value().as<std::string>(), "N/A");
724+ m_fault_status->stop_smbus_update_task();
725+ m_fault_status->set_network_adapter_oem(nullptr);
718}726}
719 727 
720TEST_F(network_adapter_fault_status_test, start_smbus_update_task_with_null_pointer)728TEST_F(network_adapter_fault_status_test, start_smbus_update_task_with_null_pointer)
@@ -17,6 +17,7 @@
17#include <functional>17#include <functional>
18#include <future>18#include <future>
19#include <memory>19#include <memory>
20+#include <mutex>
20#include <string>21#include <string>
21#include <thread>22#include <thread>
22#include <vector>23#include <vector>
@@ -2149,7 +2150,7 @@ TEST_F(network_port_link_info_test, update_link_status_by_smbus_success)
2149 m_link_info->stop_smbus_update_task();2150 m_link_info->stop_smbus_update_task();
2150}2151}
2151 2152 
2152-TEST_F(network_port_link_info_test, DISABLED_update_link_status_by_smbus_port_0_up)2153+TEST_F(network_port_link_info_test, update_link_status_by_smbus_port_0_up)
2153{2154{
2154 auto mock_smbus_obj = mc::make_shared<mock_smbus>();2155 auto mock_smbus_obj = mc::make_shared<mock_smbus>();
2155 std::string response_data;2156 std::string response_data;
@@ -2177,7 +2178,7 @@ TEST_F(network_port_link_info_test, update_link_status_by_smbus_empty_response)
2177 m_link_info->stop_smbus_update_task();2178 m_link_info->stop_smbus_update_task();
2178}2179}
2179 2180 
2180-TEST_F(network_port_link_info_test, DISABLED_update_link_status_by_smbus_invalid_status_value)2181+TEST_F(network_port_link_info_test, update_link_status_by_smbus_invalid_status_value)
2181{2182{
2182 auto mock_smbus_obj = mc::make_shared<mock_smbus>();2183 auto mock_smbus_obj = mc::make_shared<mock_smbus>();
2183 std::string response_data;2184 std::string response_data;
@@ -2192,14 +2193,14 @@ TEST_F(network_port_link_info_test, DISABLED_update_link_status_by_smbus_invalid
2192 m_link_info->stop_smbus_update_task();2193 m_link_info->stop_smbus_update_task();
2193}2194}
2194 2195 
2195-TEST_F(network_port_link_info_test, DISABLED_update_link_status_by_smbus_not_initialized)2196+TEST_F(network_port_link_info_test, update_link_status_by_smbus_not_initialized)
2196{2197{
2197 // 不设置 smbus_obj2198 // 不设置 smbus_obj
2198 bool result = m_link_info->update_link_status_by_smbus(1);2199 bool result = m_link_info->update_link_status_by_smbus(1);
2199 EXPECT_FALSE(result);2200 EXPECT_FALSE(result);
2200}2201}
2201 2202 
2202-TEST_F(network_port_link_info_test, DISABLED_start_smbus_update_task_nullptr)2203+TEST_F(network_port_link_info_test, start_smbus_update_task_nullptr)
2203{2204{
2204 // 传入 nullptr 应该不会崩溃2205 // 传入 nullptr 应该不会崩溃
2205 m_link_info->start_smbus_update_task(nullptr, mc::milliseconds(1000), 1);2206 m_link_info->start_smbus_update_task(nullptr, mc::milliseconds(1000), 1);
@@ -2263,21 +2264,27 @@ TEST_F(network_port_link_info_test, ncsi_timer_callback_updates_status)
2263}2264}
2264 2265 
2265// 测试定时器回调 - SMBus2266// 测试定时器回调 - SMBus
2266-TEST_F(network_port_link_info_test, DISABLED_smbus_timer_callback_updates_status)2267+TEST_F(network_port_link_info_test, smbus_timer_callback_updates_status)
2267{2268{
2268 auto mock_smbus_obj = mc::make_shared<mock_smbus>();2269 auto mock_smbus_obj = mc::make_shared<mock_smbus>();
2269 std::string response_data;2270 std::string response_data;
2270 response_data += static_cast<char>(0x01); // port_id=0: UP2271 response_data += static_cast<char>(0x01); // port_id=0: UP
2271 mock_smbus_obj->set_send_request_result_for_opcode(0x0403, response_data);2272 mock_smbus_obj->set_send_request_result_for_opcode(0x0403, response_data);
2272 2273 
2273- m_link_info->start_smbus_update_task(mock_smbus_obj, mc::milliseconds(100), 0);2274+ auto requested = std::make_shared<std::promise<void>>();
2274- 2275+ auto ready = requested->get_future();
2275- // 直接调用一次 update_link_status_by_smbus(同步),确保状态更新且 send_request 被触发2276+ auto once = std::make_shared<std::once_flag>();
2276- EXPECT_TRUE(m_link_info->update_link_status_by_smbus(0));2277+ mock_smbus_obj->set_send_request_callback([requested, once]() {
2277- EXPECT_EQ(m_link_info->LinkStatus.get_value().as<uint8_t>(), 1);2278+ std::call_once(*once, [requested]() {
2278- 2279+ requested->set_value();
2279- // 停止定时器,确保测试结束后不会有回调执行2280+ });
2281+ });
2282+ m_link_info->LinkStatus.set_value(uint8_t{0});
2283+ m_link_info->start_smbus_update_task(mock_smbus_obj, mc::milliseconds(10), 0);
2284+ EXPECT_EQ(ready.wait_for(std::chrono::seconds(2)), std::future_status::ready);
O
Oopenubmc-ci-bot25 days ago

⚠️ 警告:读取 LinkStatus 可能与回调竞争

ready 信号在 send_request 被调用时触发,但 LinkStatus.set_value() 是在同一回调内收到响应后才执行(update_link_status_by_smbus)。也就是说“等待 ready”不代表属性已更新。在 ready 触发后主线程调用 stop_smbus_update_task() 与 EXPECT_EQ(LinkStatus, 1) 之间存在一个竞争窗口,可能读到仍未更新的旧值(0)而触发偶发失败。

修复建议:改为在“属性已更新”的信号上等待(例如对 LinkStatus 的变化回调托平 promise),而不是仅等 send_request 被调用。

likedislike
liuxingsen
25 days ago Comments:
2285+ // Join the actual callback before reading the property it updates.
2280 m_link_info->stop_smbus_update_task();2286 m_link_info->stop_smbus_update_task();
2287+ EXPECT_EQ(m_link_info->LinkStatus.get_value().as<uint8_t>(), 1);
2281}2288}
2282 2289 
2283// ========== PLDM 相关测试 ==========2290// ========== PLDM 相关测试 ==========
@@ -2488,7 +2495,7 @@ TEST_F(network_port_link_info_test, pldm_timer_callback_updates_status)
2488// ========== 边界场景测试 ==========2495// ========== 边界场景测试 ==========
2489 2496 
2490// 测试 update_link_status_by_smbus - port_id 超出范围 - 源码已注释2497// 测试 update_link_status_by_smbus - port_id 超出范围 - 源码已注释
2491-TEST_F(network_port_link_info_test, DISABLED_update_link_status_by_smbus_port_id_out_of_range)2498+TEST_F(network_port_link_info_test, update_link_status_by_smbus_port_id_out_of_range)
2492{2499{
2493 auto mock_smbus_obj = mc::make_shared<mock_smbus>();2500 auto mock_smbus_obj = mc::make_shared<mock_smbus>();
2494 std::string response_data;2501 std::string response_data;
@@ -1153,7 +1153,7 @@ TEST_F(optical_module_power_test, update_info_by_ncsi_failure)
1153 1153 
1154// ========== OpticalModule update_presence_info_by_smbus 测试 ==========1154// ========== OpticalModule update_presence_info_by_smbus 测试 ==========
1155 1155 
1156-TEST_F(optical_module_test, DISABLED_update_presence_info_by_smbus_presence)1156+TEST_F(optical_module_test, update_presence_info_by_smbus_presence)
1157{1157{
1158 auto mock_smbus_obj = mc::make_shared<mock_smbus>();1158 auto mock_smbus_obj = mc::make_shared<mock_smbus>();
1159 std::string response_data;1159 std::string response_data;
@@ -1698,44 +1698,49 @@ TEST_F(optical_module_test, set_network_adapter_oem)
1698 EXPECT_TRUE(true);1698 EXPECT_TRUE(true);
1699}1699}
1700 1700 
1701-TEST_F(optical_module_test, DISABLED_set_optical_module_cooling)1701+TEST_F(optical_module_test, set_optical_module_cooling)
1702{1702{
1703 // 目标:验证set_optical_module_cooling方法1703 // 目标:验证set_optical_module_cooling方法
1704 auto cooling = mc::make_shared<dev::OpticalModule_TemperatureCelsius>();1704 auto cooling = mc::make_shared<dev::OpticalModule_TemperatureCelsius>();
1705 m_optical_module->set_optical_module_cooling(cooling.get());1705 m_optical_module->set_optical_module_cooling(cooling.get());
1706- EXPECT_TRUE(true);1706+ EXPECT_EQ(m_optical_module->m_optical_module_temperature_celsius, cooling.get());
1707+ m_optical_module->set_optical_module_cooling(nullptr);
1707}1708}
1708 1709 
1709-TEST_F(optical_module_test, DISABLED_set_optical_module_voltage)1710+TEST_F(optical_module_test, set_optical_module_voltage)
1710{1711{
1711 // 目标:验证set_optical_module_voltage方法1712 // 目标:验证set_optical_module_voltage方法
1712 auto voltage = mc::make_shared<dev::OpticalModule_Voltage>();1713 auto voltage = mc::make_shared<dev::OpticalModule_Voltage>();
1713 m_optical_module->set_optical_module_voltage(voltage.get());1714 m_optical_module->set_optical_module_voltage(voltage.get());
1714- EXPECT_TRUE(true);1715+ EXPECT_EQ(m_optical_module->m_optical_module_voltage, voltage.get());
1716+ m_optical_module->set_optical_module_voltage(nullptr);
1715}1717}
1716 1718 
1717-TEST_F(optical_module_test, DISABLED_set_optical_module_power)1719+TEST_F(optical_module_test, set_optical_module_power)
1718{1720{
1719 // 目标:验证set_optical_module_power方法1721 // 目标:验证set_optical_module_power方法
1720 auto power = mc::make_shared<dev::OpticalModule_Power>();1722 auto power = mc::make_shared<dev::OpticalModule_Power>();
1721 m_optical_module->set_optical_module_power(power.get());1723 m_optical_module->set_optical_module_power(power.get());
1722- EXPECT_TRUE(true);1724+ EXPECT_EQ(m_optical_module->m_optical_module_power, power.get());
1725+ m_optical_module->set_optical_module_power(nullptr);
1723}1726}
1724 1727 
1725-TEST_F(optical_module_test, DISABLED_set_optical_module_current)1728+TEST_F(optical_module_test, set_optical_module_current)
1726{1729{
1727 // 目标:验证set_optical_module_current方法1730 // 目标:验证set_optical_module_current方法
1728 auto current = mc::make_shared<dev::OpticalModule_Current>();1731 auto current = mc::make_shared<dev::OpticalModule_Current>();
1729 m_optical_module->set_optical_module_current(current.get());1732 m_optical_module->set_optical_module_current(current.get());
1730- EXPECT_TRUE(true);1733+ EXPECT_EQ(m_optical_module->m_optical_module_current, current.get());
1734+ m_optical_module->set_optical_module_current(nullptr);
1731}1735}
1732 1736 
1733-TEST_F(optical_module_test, DISABLED_set_optical_module_status)1737+TEST_F(optical_module_test, set_optical_module_status)
1734{1738{
1735 // 目标:验证set_optical_module_status方法1739 // 目标:验证set_optical_module_status方法
1736 auto status = mc::make_shared<dev::OpticalModule_Status>();1740 auto status = mc::make_shared<dev::OpticalModule_Status>();
1737 m_optical_module->set_optical_module_status(status.get());1741 m_optical_module->set_optical_module_status(status.get());
1738- EXPECT_TRUE(true);1742+ EXPECT_EQ(m_optical_module->m_optical_module_status, status.get());
1743+ m_optical_module->set_optical_module_status(nullptr);
1739}1744}
1740 1745 
1741// ========== 边界值测试 ==========1746// ========== 边界值测试 ==========
@@ -342,6 +342,9 @@ TEST_F(MucseInterfaceTest, PCIeDeviceSmbusTaskValidatesDependencies)
342 });342 });
343 EXPECT_NO_THROW(device->start_smbus_update_task(smbus, mc::milliseconds(60000)));343 EXPECT_NO_THROW(device->start_smbus_update_task(smbus, mc::milliseconds(60000)));
344 EXPECT_NO_THROW(device->stop_smbus_update_task());344 EXPECT_NO_THROW(device->stop_smbus_update_task());
345+ 
346+ oem.reset();
347+ EXPECT_NO_THROW(device.reset());
345}348}
346 349 
347TEST_F(MucseInterfaceTest, PCIeDeviceSmbusReadsHandleFailuresAndEmptyFields)350TEST_F(MucseInterfaceTest, PCIeDeviceSmbusReadsHandleFailuresAndEmptyFields)
@@ -183,6 +183,47 @@ TEST_F(N10AbiTest, PortDelegatesNcsiAndSmbusTasksToChildren)
183 EXPECT_NO_THROW(port->stop_smbus_update_task());183 EXPECT_NO_THROW(port->stop_smbus_update_task());
184}184}
185 185 
186+TEST_F(N10AbiTest, PortNcsiTaskCancellationDoesNotOutliveParent)
187+{
188+ auto mctp_object = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_NCSI, "");
189+ auto ncsi = std::make_shared<dev::ncsi_over_mctp_mucse>(mctp_object);
190+ 
191+ for (int iteration = 0; iteration < 128; ++iteration) {
192+ auto port = mc::make_shared<dev::n10_port>();
193+ auto module = mc::make_shared<dev::n10_optical_module>();
194+ port->set_service(&m_test_service);
195+ module->set_service(&m_test_service);
196+ port->set_name("NicPort_cancel");
197+ module->set_name("OpticalTransceiver_cancel");
198+ module->set_parent(port.get());
199+ ASSERT_TRUE(port->init_optical_module());
200+ 
201+ ASSERT_TRUE(port->start_ncsi_update_task(ncsi, mc::milliseconds(100000)));
202+ ASSERT_TRUE(port->start_ncsi_update_task(ncsi, mc::milliseconds(100000)));
203+ port->stop_ncsi_update_task();
204+ }
205+}
206+ 
207+TEST_F(N10AbiTest, PortSmbusTaskCancellationDoesNotOutliveParent)
208+{
209+ auto bus = mc::make_shared<dev::smbus>(nullptr);
210+ 
211+ for (int iteration = 0; iteration < 128; ++iteration) {
212+ auto port = mc::make_shared<dev::n10_port>();
213+ auto module = mc::make_shared<dev::n10_optical_module>();
214+ port->set_service(&m_test_service);
215+ module->set_service(&m_test_service);
216+ port->set_name("NicPort_cancel");
217+ module->set_name("OpticalTransceiver_cancel");
218+ module->set_parent(port.get());
219+ ASSERT_TRUE(port->init_optical_module());
220+ 
221+ ASSERT_TRUE(port->start_smbus_update_task(bus, mc::milliseconds(100000)));
222+ ASSERT_TRUE(port->start_smbus_update_task(bus, mc::milliseconds(100000)));
223+ port->stop_smbus_update_task();
224+ }
225+}
226+ 
186class N10PortIdTest : public ::testing::TestWithParam<uint8_t> {};227class N10PortIdTest : public ::testing::TestWithParam<uint8_t> {};
187 228 
188TEST_P(N10PortIdTest, PortAcceptsPortIdBoundaryValues)229TEST_P(N10PortIdTest, PortAcceptsPortIdBoundaryValues)
@@ -183,6 +183,47 @@ TEST_F(N20AbiTest, PortDelegatesNcsiAndSmbusTasksToChildren)
183 EXPECT_NO_THROW(port->stop_smbus_update_task());183 EXPECT_NO_THROW(port->stop_smbus_update_task());
184}184}
185 185 
186+TEST_F(N20AbiTest, PortNcsiTaskCancellationDoesNotOutliveParent)
187+{
188+ auto mctp_object = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE::MCTP_MESSAGE_TYPE_NCSI, "");
189+ auto ncsi = std::make_shared<dev::ncsi_over_mctp_mucse>(mctp_object);
190+ 
191+ for (int iteration = 0; iteration < 128; ++iteration) {
192+ auto port = mc::make_shared<dev::n20_port>();
193+ auto module = mc::make_shared<dev::n20_optical_module>();
194+ port->set_service(&m_test_service);
195+ module->set_service(&m_test_service);
196+ port->set_name("NicPort_cancel");
197+ module->set_name("OpticalTransceiver_cancel");
198+ module->set_parent(port.get());
199+ ASSERT_TRUE(port->init_optical_module());
200+ 
201+ ASSERT_TRUE(port->start_ncsi_update_task(ncsi, mc::milliseconds(100000)));
202+ ASSERT_TRUE(port->start_ncsi_update_task(ncsi, mc::milliseconds(100000)));
203+ port->stop_ncsi_update_task();
204+ }
205+}
206+ 
207+TEST_F(N20AbiTest, PortSmbusTaskCancellationDoesNotOutliveParent)
208+{
209+ auto bus = mc::make_shared<dev::smbus>(nullptr);
210+ 
211+ for (int iteration = 0; iteration < 128; ++iteration) {
212+ auto port = mc::make_shared<dev::n20_port>();
213+ auto module = mc::make_shared<dev::n20_optical_module>();
214+ port->set_service(&m_test_service);
215+ module->set_service(&m_test_service);
216+ port->set_name("NicPort_cancel");
217+ module->set_name("OpticalTransceiver_cancel");
218+ module->set_parent(port.get());
219+ ASSERT_TRUE(port->init_optical_module());
220+ 
221+ ASSERT_TRUE(port->start_smbus_update_task(bus, mc::milliseconds(100000)));
222+ ASSERT_TRUE(port->start_smbus_update_task(bus, mc::milliseconds(100000)));
223+ port->stop_smbus_update_task();
224+ }
225+}
226+ 
186class N20PortIdTest : public ::testing::TestWithParam<uint8_t> {};227class N20PortIdTest : public ::testing::TestWithParam<uint8_t> {};
187 228 
188TEST_P(N20PortIdTest, PortAcceptsPortIdBoundaryValues)229TEST_P(N20PortIdTest, PortAcceptsPortIdBoundaryValues)
@@ -11,9 +11,11 @@
11 */11 */
12 12 
13#include <algorithm>13#include <algorithm>
14+#include <atomic>
14#include <chrono>15#include <chrono>
15#include <cstring>16#include <cstring>
16#include <functional>17#include <functional>
18+#include <future>
17#include <mc/common.h>19#include <mc/common.h>
18#include <mc/engine.h>20#include <mc/engine.h>
19#include <mctp/mctp.h>21#include <mctp/mctp.h>
@@ -495,7 +497,19 @@ TEST_F(NVMeNVMeMITest, GetTelemetryData_ReturnTaskId)
495TEST_F(NVMeNVMeMITest, GetTelemetryData_AlreadyRunning)497TEST_F(NVMeNVMeMITest, GetTelemetryData_AlreadyRunning)
496{498{
497 // 目标:遥测采集已在运行(标志位为 true)时再次调用返回 0499 // 目标:遥测采集已在运行(标志位为 true)时再次调用返回 0
498- dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {500+ auto request_entered = std::make_shared<std::promise<void>>();
501+ auto request_entered_future = request_entered->get_future();
502+ auto release_request = std::make_shared<std::promise<void>>();
503+ auto release_request_future = release_request->get_future().share();
504+ auto request_entered_signaled = std::make_shared<std::atomic<bool>>(false);
505+ dev::set_mctp_request_handler([request_entered, release_request_future, request_entered_signaled](
506+ const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
507+ if (!request_entered_signaled->exchange(true)) {
508+ request_entered->set_value();
509+ }
510+ if (release_request_future.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
511+ return false;
512+ }
499 rsp.resize(1 + 15 + 512);513 rsp.resize(1 + 15 + 512);
500 rsp[0] = NVME_MI_PROTOCOL_STATUS_SUCCESS;514 rsp[0] = NVME_MI_PROTOCOL_STATUS_SUCCESS;
501 std::fill(rsp.begin() + 16, rsp.end(), 0x00);515 std::fill(rsp.begin() + 16, rsp.end(), 0x00);
@@ -510,6 +524,7 @@ TEST_F(NVMeNVMeMITest, GetTelemetryData_AlreadyRunning)
510 auto owner = mc::make_shared<DeraNvmeMiTestOwner>();524 auto owner = mc::make_shared<DeraNvmeMiTestOwner>();
511 owner->set_service(app_svc.get());525 owner->set_service(app_svc.get());
512 m_nvme_mi->set_owner(owner.get());526 m_nvme_mi->set_owner(owner.get());
527+ m_nvme_mi->bind_executor(mc::runtime::get_runtime_context().create_work_strand());
513 528 
514 auto [mctp_obj, nvme_mi_obj] = CreateMockNvmeMi();529 auto [mctp_obj, nvme_mi_obj] = CreateMockNvmeMi();
515 m_nvme_mi->set_nvme_mi_obj(nvme_mi_obj);530 m_nvme_mi->set_nvme_mi_obj(nvme_mi_obj);
@@ -518,14 +533,21 @@ TEST_F(NVMeNVMeMITest, GetTelemetryData_AlreadyRunning)
518 oem->DriveObjectPath.set_value("/bmc/Storage/Drives/Drive_0_000000");533 oem->DriveObjectPath.set_value("/bmc/Storage/Drives/Drive_0_000000");
519 m_nvme_mi->set_nvme_oem(oem.get());534 m_nvme_mi->set_nvme_oem(oem.get());
520 535 
521- // 第一次调用会设置 m_telemetry_running,异步任务尚未完成536+ // Block the first request inside the asynchronous collector so the second
537+ // call deterministically observes m_telemetry_running == true.
522 auto first = m_nvme_mi->GetTelemetryData("Host");538 auto first = m_nvme_mi->GetTelemetryData("Host");
523 EXPECT_GT(first, 0u);539 EXPECT_GT(first, 0u);
540+ EXPECT_EQ(request_entered_future.wait_for(std::chrono::seconds(5)), std::future_status::ready);
524 541 
525- // 立即再次调用应命中 running 标志返回 0
526 EXPECT_EQ(m_nvme_mi->GetTelemetryData("Host"), 0u);542 EXPECT_EQ(m_nvme_mi->GetTelemetryData("Host"), 0u);
527 543 
528- std::this_thread::sleep_for(std::chrono::milliseconds(200));544+ release_request->set_value();
545+ auto collector_finished = std::make_shared<std::promise<void>>();
546+ auto collector_finished_future = collector_finished->get_future();
547+ m_nvme_mi->executor().post([collector_finished]() {
548+ collector_finished->set_value();
549+ });
550+ EXPECT_EQ(collector_finished_future.wait_for(std::chrono::seconds(5)), std::future_status::ready);
529 m_nvme_mi->set_owner(nullptr);551 m_nvme_mi->set_owner(nullptr);
530 app_svc->stop();552 app_svc->stop();
531}553}
@@ -20,6 +20,7 @@
20#include <test_utilities/test_base.h>20#include <test_utilities/test_base.h>
21#include <gtest/gtest.h>21#include <gtest/gtest.h>
22 22 
23+#include "../../../test_utils/device_lifecycle.h"
23#include "interface/storage/bbu.h"24#include "interface/storage/bbu.h"
24#include "interface/storage/bbu/status.h"25#include "interface/storage/bbu/status.h"
25#include "interface/storage/controller.h"26#include "interface/storage/controller.h"
@@ -62,6 +63,7 @@
62#include "interface/storage/volume/settings.h"63#include "interface/storage/volume/settings.h"
63#include "interface/storage/volume/status.h"64#include "interface/storage/volume/status.h"
64#include "mc/app/task/task_mgmt.h"65#include "mc/app/task/task_mgmt.h"
66+#include "mc/app/task/task_object.h"
65#include "smlib/sml_library.h"67#include "smlib/sml_library.h"
66#include "smlib/sml_public.h"68#include "smlib/sml_public.h"
67#include "smlib/sml_public_ctrl.h"69#include "smlib/sml_public_ctrl.h"
@@ -1523,6 +1525,8 @@ gint32 MockLdOperation(guint32 ctrlId, guint16 targetId, guint8 operation, gpoin
1523}1525}
1524 1526 
1525// 失败路径 mock:返回非零错误码1527// 失败路径 mock:返回非零错误码
1528+constexpr gint32 kMockLdOperationFailure = -1;
1529+ 
1526gint32 MockLdOperation_Fail(guint32 ctrlId, guint16 targetId, guint8 operation, gpointer param, guint32 paramLength)1530gint32 MockLdOperation_Fail(guint32 ctrlId, guint16 targetId, guint8 operation, gpointer param, guint32 paramLength)
1527{1531{
1528 (void)ctrlId;1532 (void)ctrlId;
@@ -1530,7 +1534,7 @@ gint32 MockLdOperation_Fail(guint32 ctrlId, guint16 targetId, guint8 operation,
1530 (void)operation;1534 (void)operation;
1531 (void)param;1535 (void)param;
1532 (void)paramLength;1536 (void)paramLength;
1533- return -1; // 失败1537+ return kMockLdOperationFailure;
1534}1538}
1535 1539 
1536// 需重启生效路径 mock:返回SML_ERR_REBOOT_REQUIRED(命令执行成功但需OS重启生效)1540// 需重启生效路径 mock:返回SML_ERR_REBOOT_REQUIRED(命令执行成功但需OS重启生效)
@@ -2876,8 +2880,16 @@ TEST_F(StorageDriveSettingsTest, SetHotspare_Global)
2876 return 0;2880 return 0;
2877 });2881 });
2878 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);2882 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);
2883+ bool refreshed = false;
2884+ m_drive_settings->SetOnPropertyChanged([&] {
2885+ SML_PD_INFO_S info{};
2886+ info.pdinfo.hot_spare = PD_HOT_SPARE_GLOBAL;
2887+ m_drive_settings->UpdateSettings(info);
2888+ refreshed = true;
2889+ });
2879 uint32_t ret = m_drive_settings->SetHotspare(PD_HOT_SPARE_GLOBAL, 5);2890 uint32_t ret = m_drive_settings->SetHotspare(PD_HOT_SPARE_GLOBAL, 5);
2880 EXPECT_EQ(ret, 0u);2891 EXPECT_EQ(ret, 0u);
2892+ EXPECT_TRUE(refreshed);
2881 EXPECT_EQ(m_drive_settings->Hotspare.get_value(), PD_HOT_SPARE_GLOBAL);2893 EXPECT_EQ(m_drive_settings->Hotspare.get_value(), PD_HOT_SPARE_GLOBAL);
2882}2894}
2883 2895 
@@ -2892,8 +2904,16 @@ TEST_F(StorageDriveSettingsTest, SetHotspare_Dedicated)
2892 return 0;2904 return 0;
2893 });2905 });
2894 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);2906 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);
2907+ bool refreshed = false;
2908+ m_drive_settings->SetOnPropertyChanged([&] {
2909+ SML_PD_INFO_S info{};
2910+ info.pdinfo.hot_spare = PD_HOT_SPARE_DEDICATED;
2911+ m_drive_settings->UpdateSettings(info);
2912+ refreshed = true;
2913+ });
2895 uint32_t ret = m_drive_settings->SetHotspare(PD_HOT_SPARE_DEDICATED, 5);2914 uint32_t ret = m_drive_settings->SetHotspare(PD_HOT_SPARE_DEDICATED, 5);
2896 EXPECT_EQ(ret, 0u);2915 EXPECT_EQ(ret, 0u);
2916+ EXPECT_TRUE(refreshed);
2897 EXPECT_EQ(m_drive_settings->Hotspare.get_value(), PD_HOT_SPARE_DEDICATED);2917 EXPECT_EQ(m_drive_settings->Hotspare.get_value(), PD_HOT_SPARE_DEDICATED);
2898}2918}
2899 2919 
@@ -2923,9 +2943,17 @@ TEST_F(StorageDriveSettingsTest, SetHotspare_Default)
2923 return 0;2943 return 0;
2924 });2944 });
2925 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);2945 m_drive_settings->SetSmlLibrary(&mockLib, 0, 1, 100);
2946+ bool refreshed = false;
2947+ m_drive_settings->SetOnPropertyChanged([&] {
2948+ SML_PD_INFO_S info{};
2949+ info.pdinfo.hot_spare = PD_HOT_SPARE_NONE;
2950+ m_drive_settings->UpdateSettings(info);
2951+ refreshed = true;
2952+ });
2926 uint32_t ret = m_drive_settings->SetHotspare(99, 5);2953 uint32_t ret = m_drive_settings->SetHotspare(99, 5);
2927 EXPECT_EQ(ret, 0u);2954 EXPECT_EQ(ret, 0u);
2928- EXPECT_EQ(m_drive_settings->Hotspare.get_value(), 99u);2955+ EXPECT_TRUE(refreshed);
2956+ EXPECT_EQ(m_drive_settings->Hotspare.get_value(), PD_HOT_SPARE_NONE);
2929}2957}
2930 2958 
2931TEST_F(StorageDriveSettingsTest, SetHotspare_Failure)2959TEST_F(StorageDriveSettingsTest, SetHotspare_Failure)
@@ -3326,11 +3354,33 @@ TEST_F(StorageVolumeSettingsTest, SetSSDCachingEnable_WithMock)
3326 // 测试设置 SSD 缓存启用(成功路径)- 使用独立 mock 对象3354 // 测试设置 SSD 缓存启用(成功路径)- 使用独立 mock 对象
3327 mc::shared_ptr<Storage_Volume_Settings> settings = mc::make_shared<Storage_Volume_Settings>();3355 mc::shared_ptr<Storage_Volume_Settings> settings = mc::make_shared<Storage_Volume_Settings>();
3328 SmlLibrary mockLib{};3356 SmlLibrary mockLib{};
3329- mock_inject_ldOperation(mockLib, MockLdOperation);3357+ mock_inject_ldOperation(
3358+ mockLib, [](guint32 ctrlId, guint16 targetId, guint8 operation, gpointer data, guint32 size) -> gint32 {
3359+ EXPECT_EQ(ctrlId, 1u);
3360+ EXPECT_EQ(targetId, 100u);
3361+ EXPECT_EQ(operation, LD_OPERATION_SET_SSCD_CACHING_ENABLE);
3362+ EXPECT_EQ(size, sizeof(SML_LD_SET_SSCD_CACHING_ENABLE_S));
3363+ const auto& request = *static_cast<SML_LD_SET_SSCD_CACHING_ENABLE_S*>(data);
3364+ EXPECT_EQ(request.enable, 1);
3365+ EXPECT_EQ(request.sscd_count, 1u);
3366+ EXPECT_EQ(request.sscd_target_ids[0], 7u);
3367+ return 0;
3368+ });
3330 settings->SetSmlLibrary(&mockLib, 0, 1, 100);3369 settings->SetSmlLibrary(&mockLib, 0, 1, 100);
3370+ settings->SetSscdCachingContextProvider([] {
3371+ return Storage_Volume_Settings::SscdCachingContext{true, false, {7}};
3372+ });
3373+ 
3374+ settings->updateSsdCachingEnable(false);
3375+ bool refreshed = false;
3376+ settings->SetOnLdChangedCallback([&](const LdChangeInfo&) {
3377+ settings->updateSsdCachingEnable(true);
3378+ refreshed = true;
3379+ });
3331 3380 
3332 uint32_t ret = settings->SetSSDCachingEnable(true);3381 uint32_t ret = settings->SetSSDCachingEnable(true);
3333 EXPECT_EQ(ret, 0u);3382 EXPECT_EQ(ret, 0u);
3383+ EXPECT_TRUE(refreshed);
3334 EXPECT_EQ(settings->SSDCachingEnable.get_value(), true);3384 EXPECT_EQ(settings->SSDCachingEnable.get_value(), true);
3335}3385}
3336 3386 
@@ -3339,11 +3389,33 @@ TEST_F(StorageVolumeSettingsTest, SetSSDCachingEnable_Disable_WithMock)
3339 // 测试设置 SSD 缓存禁用(成功路径)3389 // 测试设置 SSD 缓存禁用(成功路径)
3340 mc::shared_ptr<Storage_Volume_Settings> settings = mc::make_shared<Storage_Volume_Settings>();3390 mc::shared_ptr<Storage_Volume_Settings> settings = mc::make_shared<Storage_Volume_Settings>();
3341 SmlLibrary mockLib{};3391 SmlLibrary mockLib{};
3342- mock_inject_ldOperation(mockLib, MockLdOperation);3392+ mock_inject_ldOperation(
3393+ mockLib, [](guint32 ctrlId, guint16 targetId, guint8 operation, gpointer data, guint32 size) -> gint32 {
3394+ EXPECT_EQ(ctrlId, 1u);
3395+ EXPECT_EQ(targetId, 100u);
3396+ EXPECT_EQ(operation, LD_OPERATION_SET_SSCD_CACHING_ENABLE);
3397+ EXPECT_EQ(size, sizeof(SML_LD_SET_SSCD_CACHING_ENABLE_S));
3398+ const auto& request = *static_cast<SML_LD_SET_SSCD_CACHING_ENABLE_S*>(data);
3399+ EXPECT_EQ(request.enable, 0);
3400+ EXPECT_EQ(request.sscd_count, 1u);
3401+ EXPECT_EQ(request.sscd_target_ids[0], 7u);
3402+ return 0;
3403+ });
3343 settings->SetSmlLibrary(&mockLib, 0, 1, 100);3404 settings->SetSmlLibrary(&mockLib, 0, 1, 100);
3405+ settings->SetSscdCachingContextProvider([] {
3406+ return Storage_Volume_Settings::SscdCachingContext{true, false, {7}};
3407+ });
3408+ 
3409+ settings->updateSsdCachingEnable(true);
3410+ bool refreshed = false;
3411+ settings->SetOnLdChangedCallback([&](const LdChangeInfo&) {
3412+ settings->updateSsdCachingEnable(false);
3413+ refreshed = true;
3414+ });
3344 3415 
3345 uint32_t ret = settings->SetSSDCachingEnable(false);3416 uint32_t ret = settings->SetSSDCachingEnable(false);
3346 EXPECT_EQ(ret, 0u);3417 EXPECT_EQ(ret, 0u);
3418+ EXPECT_TRUE(refreshed);
3347 EXPECT_EQ(settings->SSDCachingEnable.get_value(), false);3419 EXPECT_EQ(settings->SSDCachingEnable.get_value(), false);
3348}3420}
3349 3421 
@@ -3439,9 +3511,12 @@ TEST_F(StorageVolumeSettingsTest, SetSSDCachingEnable_Failure)
3439 SmlLibrary mockLib{};3511 SmlLibrary mockLib{};
3440 mock_inject_ldOperation(mockLib, MockLdOperation_Fail);3512 mock_inject_ldOperation(mockLib, MockLdOperation_Fail);
3441 settings->SetSmlLibrary(&mockLib, 0, 1, 100);3513 settings->SetSmlLibrary(&mockLib, 0, 1, 100);
3514+ settings->SetSscdCachingContextProvider([] {
3515+ return Storage_Volume_Settings::SscdCachingContext{true, false, {7}};
3516+ });
3442 3517 
3443- uint32_t ret = settings->SetSSDCachingEnable(true);3518+ const int32_t ret = settings->SetSSDCachingEnable(true);
3444- EXPECT_NE(ret, 0u);3519+ EXPECT_EQ(ret, kMockLdOperationFailure);
3445}3520}
3446 3521 
3447TEST_F(StorageVolumeSettingsTest, SetBootPriority_Failure)3522TEST_F(StorageVolumeSettingsTest, SetBootPriority_Failure)
@@ -3528,6 +3603,9 @@ TEST_F(StorageVolumeSettingsTest, SetSSDCachingEnable_WithCallback)
3528 SmlLibrary mockLib{};3603 SmlLibrary mockLib{};
3529 mock_inject_ldOperation(mockLib, MockLdOperation);3604 mock_inject_ldOperation(mockLib, MockLdOperation);
3530 settings->SetSmlLibrary(&mockLib, 0, 1, 100);3605 settings->SetSmlLibrary(&mockLib, 0, 1, 100);
3606+ settings->SetSscdCachingContextProvider([] {
3607+ return Storage_Volume_Settings::SscdCachingContext{true, false, {7}};
3608+ });
3531 3609 
3532 bool callbackCalled = false;3610 bool callbackCalled = false;
3533 settings->SetOnLdChangedCallback([&callbackCalled](const LdChangeInfo&) {3611 settings->SetOnLdChangedCallback([&callbackCalled](const LdChangeInfo&) {
@@ -4371,7 +4449,7 @@ public:
4371 4449 
4372 void SetUp() override4450 void SetUp() override
4373 {4451 {
4374- m_settings = mc::make_shared<Storage_Controller_Settings>();4452+ m_settings = make_iface<Storage_Controller_Settings>();
4375 m_settings->SetSmlLibrary(&m_mockSmlLib, 0, 1);4453 m_settings->SetSmlLibrary(&m_mockSmlLib, 0, 1);
4376 m_settings->SetObjectPath("/bmc/dev/Storage/Controller/0");4454 m_settings->SetObjectPath("/bmc/dev/Storage/Controller/0");
4377 // 关键链路:mc::app::service 不是 abstract_object,无法直接 set_owner。4455 // 关键链路:mc::app::service 不是 abstract_object,无法直接 set_owner。
@@ -4387,6 +4465,7 @@ public:
4387 mock_inject_ldOperation(m_mockSmlLib, MockLdOperation_Capture);4465 mock_inject_ldOperation(m_mockSmlLib, MockLdOperation_Capture);
4388 mock_inject_getLdInfo(m_mockSmlLib, MockGetLdInfo);4466 mock_inject_getLdInfo(m_mockSmlLib, MockGetLdInfo);
4389 mock_inject_getLdPdList(m_mockSmlLib, MockGetLdPdList);4467 mock_inject_getLdPdList(m_mockSmlLib, MockGetLdPdList);
4468+ mock_inject_getPdInfo(m_mockSmlLib, MockGetPdInfo);
4390 g_mock_ctrlOperation_ret = 0;4469 g_mock_ctrlOperation_ret = 0;
4391 g_mock_ctrlOperation_last_op = 0xFF;4470 g_mock_ctrlOperation_last_op = 0xFF;
4392 g_mock_ctrlOperation_last_data = nullptr;4471 g_mock_ctrlOperation_last_data = nullptr;
@@ -4407,8 +4486,7 @@ public:
4407 4486 
4408 void TearDown() override4487 void TearDown() override
4409 {4488 {
4410- // 等待 post 回调完成4489+ drain_executor(*m_settings);
4411- std::this_thread::sleep_for(std::chrono::milliseconds(100));
4412 m_settings.reset();4490 m_settings.reset();
4413 m_owner.reset();4491 m_owner.reset();
4414 }4492 }
@@ -4694,11 +4772,22 @@ TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_CapacityOverflow_Full
4694 4772 
4695TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_DriveIdNotFound)4773TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_DriveIdNotFound)
4696{4774{
4697- g_mock_configOperation_ret = 0;4775+ g_mock_getPdInfo_ret = SML_ERR_NULL_DATA;
4776+ m_settings->SetOnLdChangedCallback([this](const LdChangeInfo&) {
4777+ m_callback_called = true;
4778+ });
4698 4779 
4699 uint32_t ret = m_settings->CreateCommonLD({1, 2}, 1, 1, "TestLD", 100, 0, 64, 0, 0, 0, 0, 0, 0, 0);4780 uint32_t ret = m_settings->CreateCommonLD({1, 2}, 1, 1, "TestLD", 100, 0, 64, 0, 0, 0, 0, 0, 0, 0);
4700 4781 
4701- EXPECT_EQ(ret, (uint32_t)SML_ERR_NULL_DATA);4782+ // Drive lookup now happens in the posted operation; the method returns a task ID.
4783+ ASSERT_TRUE(dev::test::wait_executor_idle(*m_settings));
4784+ auto* task = m_test_service->task_mgmt().get_task_obj(ret);
4785+ ASSERT_NE(task, nullptr);
4786+ EXPECT_EQ(task->task_interface.m_state.get_value(), "Exception");
4787+ EXPECT_EQ(task->task_interface.m_status.get_value(), "Error");
4788+ EXPECT_EQ(task->task_interface.m_message_args.get_value(), std::vector<mc::string>{"0x10c0"});
4789+ EXPECT_EQ(g_mock_configOperation_last_op, 0xFF);
4790+ EXPECT_FALSE(m_callback_called);
4702}4791}
4703 4792 
4704TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_LongName)4793TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_LongName)
@@ -4954,11 +5043,22 @@ TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_CapacityOverflow_F
4954 5043 
4955TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_DriveIdNotFound)5044TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_DriveIdNotFound)
4956{5045{
4957- g_mock_configOperation_ret = 0;5046+ g_mock_getPdInfo_ret = SML_ERR_NULL_DATA;
5047+ m_settings->SetOnLdChangedCallback([this](const LdChangeInfo&) {
5048+ m_callback_called = true;
5049+ });
4958 5050 
4959 uint32_t ret = m_settings->CreateCachecadeLD({1, 2}, 1, "TestCachecade", 0, 0, 100, 0, 0, 64);5051 uint32_t ret = m_settings->CreateCachecadeLD({1, 2}, 1, "TestCachecade", 0, 0, 100, 0, 0, 64);
4960 5052 
4961- EXPECT_EQ(ret, (uint32_t)SML_ERR_NULL_DATA);5053+ // Drive lookup now happens in the posted operation; the method returns a task ID.
5054+ ASSERT_TRUE(dev::test::wait_executor_idle(*m_settings));
5055+ auto* task = m_test_service->task_mgmt().get_task_obj(ret);
5056+ ASSERT_NE(task, nullptr);
5057+ EXPECT_EQ(task->task_interface.m_state.get_value(), "Exception");
5058+ EXPECT_EQ(task->task_interface.m_status.get_value(), "Error");
5059+ EXPECT_EQ(task->task_interface.m_message_args.get_value(), std::vector<mc::string>{"0x10c0"});
5060+ EXPECT_EQ(g_mock_configOperation_last_op, 0xFF);
5061+ EXPECT_FALSE(m_callback_called);
4962}5062}
4963 5063 
4964TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_LongName)5064TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_LongName)
@@ -5116,10 +5216,7 @@ TEST_F(StorageControllerSettingsEngineTest, CreateCommonLD_OperationInProgress)
5116{5216{
5117 g_mock_configOperation_ret = 0;5217 g_mock_configOperation_ret = 0;
5118 5218 
5119- m_settings->SetGetDriveInfoCallback([](uint16_t) -> std::optional<DrivePdInfo> {5219+ mock_inject_getPdInfo(m_mockSmlLib, MockGetPdInfo);
5120- DrivePdInfo info;
5121- return info;
5122- });
5123 5220 
5124 // 把第一次投递的异步"钉"在 mock 内,保证第二次调用时操作仍在进行中(确定性命中冲突)5221 // 把第一次投递的异步"钉"在 mock 内,保证第二次调用时操作仍在进行中(确定性命中冲突)
5125 {5222 {
@@ -5162,10 +5259,7 @@ TEST_F(StorageControllerSettingsEngineTest, CreateCachecadeLD_OperationInProgres
5162{5259{
5163 g_mock_configOperation_ret = 0;5260 g_mock_configOperation_ret = 0;
5164 5261 
5165- m_settings->SetGetDriveInfoCallback([](uint16_t) -> std::optional<DrivePdInfo> {5262+ mock_inject_getPdInfo(m_mockSmlLib, MockGetPdInfo);
5166- DrivePdInfo info;
5167- return info;
5168- });
5169 5263 
5170 // 把第一次投递的异步"钉"在 mock 内,保证第二次调用时操作仍在进行中(确定性命中冲突)5264 // 把第一次投递的异步"钉"在 mock 内,保证第二次调用时操作仍在进行中(确定性命中冲突)
5171 {5265 {
@@ -13,7 +13,9 @@
13#include <atomic>13#include <atomic>
14#include <chrono>14#include <chrono>
15#include <future>15#include <future>
16+#include <mc/engine/errors/std_errors.h>
16#include <mc/engine/property/ref_object.h>17#include <mc/engine/property/ref_object.h>
18+#include <mc/error_engine.h>
17#include <mc/log.h>19#include <mc/log.h>
18#include <mc/runtime/runtime_strand.h>20#include <mc/runtime/runtime_strand.h>
19#include <memory>21#include <memory>
@@ -23,7 +25,13 @@
23#include <vector>25#include <vector>
24#include <gtest/gtest.h>26#include <gtest/gtest.h>
25 27 
28+#include <mc/app/object.h>
29+#include <mc/debounce/base.h>
30+#include <mc/timer.h>
31+ 
32+#define private public
26#include "../../../drivers/scanner/scanner/scanner.h"33#include "../../../drivers/scanner/scanner/scanner.h"
34+#undef private
27#include "../test_utils/root_object.h"35#include "../test_utils/root_object.h"
28 36 
29using namespace dev;37using namespace dev;
@@ -571,6 +579,33 @@ TEST_F(ScannerTest, test_init_missing_fields)
571 EXPECT_FALSE(result);579 EXPECT_FALSE(result);
572}580}
573 581 
582+TEST_F(ScannerTest, stopped_timer_can_outlive_embedded_interface)
583+{
584+ m_scanner->i_scanner.Period.set_value(uint32_t{60000});
585+ m_scanner->i_scanner.start_scanner_task();
586+ auto timer = m_scanner->i_scanner.m_scanner_timer;
587+ ASSERT_NE(timer, nullptr);
588+ ASSERT_TRUE(m_scanner->stop());
589+ EXPECT_FALSE(timer->is_active());
590+ EXPECT_EQ(timer->get_parent(), nullptr);
591+ m_test_service->unregister_object(std::string(m_scanner->get_object_path()));
592+ m_scanner.reset();
593+ timer.reset();
594+}
595+ 
596+TEST_F(ScannerTest, replaced_timer_is_stopped_and_detached)
597+{
598+ auto& scanner = m_scanner->i_scanner;
599+ scanner.Period.set_value(uint32_t{60000});
600+ scanner.start_scanner_task();
601+ auto timer = scanner.m_scanner_timer;
602+ ASSERT_NE(timer, nullptr);
603+ scanner.start_scanner_task();
604+ EXPECT_NE(timer, scanner.m_scanner_timer);
605+ EXPECT_FALSE(timer->is_active());
606+ EXPECT_EQ(timer->get_parent(), nullptr);
607+}
608+ 
574TEST_F(ScannerTest, test_start_stop_scanner)609TEST_F(ScannerTest, test_start_stop_scanner)
575{610{
576 auto csr = create_test_csr();611 auto csr = create_test_csr();
@@ -1404,72 +1439,30 @@ TEST_F(ScannerTest, test_set_value_zero)
1404 1439 
1405TEST_F(ScannerTest, test_success_debounce_count)1440TEST_F(ScannerTest, test_success_debounce_count)
1406{1441{
1407- auto csr = create_test_csr();1442+ auto csr = create_test_csr();
1408- auto connector = create_test_connector();1443+ ASSERT_TRUE(m_scanner->init(csr, create_test_connector()));
1444+ auto& scanner = m_scanner->i_scanner;
1445+ scanner.FailureDebounceCount.set_value(2);
1446+ scanner.SuccessDebounceCount.set_value(3);
1447+ scanner.Value.set_value(uint64_t{42});
1409 1448 
1410- bool init_result = m_scanner->init(csr, connector);1449+ // Drive completed read results directly so each debounce boundary is observable.
1411- ASSERT_TRUE(init_result);1450+ scanner.handle_result_failure("read failed");
1451+ EXPECT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_PRE_FAILURE);
1452+ scanner.handle_result_failure("read failed");
1453+ ASSERT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_FAILURE);
1412 1454 
1413- // 设置失败防抖计数和成功防抖计数1455+ for (uint64_t value : {100u, 101u}) {
1414- m_scanner->i_scanner.FailureDebounceCount.set_value(2);1456+ scanner.handle_result_success(value);
1415- m_scanner->i_scanner.SuccessDebounceCount.set_value(3);1457+ EXPECT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_FAILURE);
1416- 1458+ EXPECT_EQ(scanner.Value.get_value().as<uint64_t>(), 42u);
1417- set_chip_reference();1459+ EXPECT_TRUE(scanner.dump().error.has_value());
1418- 
1419- // 先触发失败状态
1420- mc::mutable_dict failure_csr;
1421- failure_csr["Chip"] = "#/" + std::string(m_exception_chip->get_object_name())
1422- .substr(0, std::string(m_exception_chip->get_object_name()).find("_"));
1423- failure_csr["Offset"] = static_cast<uint32_t>(0xDEAD);
1424- failure_csr["Size"] = static_cast<uint8_t>(4);
1425- failure_csr["Mask"] = static_cast<uint8_t>(0xFF);
1426- failure_csr["Type"] = static_cast<uint8_t>(1);
1427- failure_csr["Period"] = static_cast<uint32_t>(10);
1428- 
1429- // 先停止当前的扫描任务(init时启动的),并等待停止完成
1430- m_scanner->stop();
1431- std::this_thread::sleep_for(std::chrono::milliseconds(20));
1432- 
1433- // 设置新的Period和FailureDebounceCount
1434- m_scanner->i_scanner.Period.set_value(20);
1435- 
1436- // 临时切换到异常chip来触发失败
1437- std::string exception_chip_ref = "#/" + std::string(m_exception_chip->get_object_name())
1438- .substr(0, std::string(m_exception_chip->get_object_name()).find("_"));
1439- mc::variant exception_ref_obj{exception_chip_ref};
1440- from_variant(exception_ref_obj, m_scanner->i_scanner.Chip);
1441- 
1442- // 启动扫描任务(这会启动定时器)
1443- bool start_result = m_scanner->start();
1444- ASSERT_TRUE(start_result);
1445- 
1446- // 等待失败发生 - 等待足够的时间让扫描任务执行并触发失败
1447- // FailureDebounceCount为2,Period为20ms,需要至少2次失败 = 40ms + 余量
1448- // 但由于异常chip会立即抛出异常,每次扫描都会失败
1449- // 第一次失败:failure_count=1, status=SCANNER_PRE_FAILURE(2)
1450- // 第二次失败:failure_count=2, status=SCANNER_FAILURE(1)
1451- std::this_thread::sleep_for(std::chrono::milliseconds(50));
1452- uint8_t failure_status = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
1453- // 验证状态变为失败或预失败(取决于FailureDebounceCount)
1454- // 如果状态不是预期的失败状态,跳过测试
1455- if (failure_status != SCANNER_FAILURE && failure_status != SCANNER_PRE_FAILURE) {
1456- GTEST_SKIP() << "Scanner status is not in failure state, got " << static_cast<int>(failure_status);
1457 }1460 }
1458- EXPECT_TRUE(failure_status == SCANNER_FAILURE || failure_status == SCANNER_PRE_FAILURE);1461+ scanner.handle_result_success(102);
1459- 1462+ EXPECT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_OK);
1460- // 切换回正常chip1463+ EXPECT_EQ(scanner.Value.get_value().as<uint64_t>(), 102u);
1461- set_chip_reference();1464+ EXPECT_EQ(scanner.dump().success_count, 3u);
1462- 1465+ EXPECT_FALSE(scanner.dump().error.has_value());
1463- // 等待成功计数累积 - 需要足够的时间让成功计数达到SuccessDebounceCount(3)
1464- // Period为20ms,成功计数需要3次,所以需要至少 20ms * 3 = 60ms,加上余量
1465- std::this_thread::sleep_for(std::chrono::milliseconds(80));
1466- 
1467- // 验证状态,成功计数应该足够后状态变为OK
1468- uint8_t final_status = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
1469- // 成功计数达到后,状态应该恢复为OK
1470- EXPECT_EQ(final_status, SCANNER_OK);
1471- 
1472- m_scanner->stop();
1473}1466}
1474 1467 
1475// ========================= Scanner异常处理分支测试 =========================1468// ========================= Scanner异常处理分支测试 =========================
@@ -1586,30 +1579,18 @@ TEST_F(ScannerTest, test_set_value_exception_handling)
1586 EXPECT_EQ(actual_value, 0x12345678ULL);1579 EXPECT_EQ(actual_value, 0x12345678ULL);
1587}1580}
1588 1581 
1589-// 测试 Scanner::init() 异常处理 - Offset字段为null1582+// 缺省 Offset 保留数值属性的零默认值
1590-TEST_F(ScannerTest, test_init_exception_offset_null)1583+TEST_F(ScannerTest, test_init_missing_offset_uses_zero_default)
1591{1584{
1592 mc::mutable_dict csr;1585 mc::mutable_dict csr;
1593- csr["Chip"] = "#/" + m_chip_name.substr(0, m_chip_name.find("_"));1586+ csr["Chip"] = "#/" + m_chip_name.substr(0, m_chip_name.find("_"));
1594- // Offset字段缺失,from_variant后Offset应该是null
1595- // 注意:如果from_variant没有设置Offset,get_value()可能返回默认值而不是null
1596- // 因此这个测试可能不会失败,取决于from_variant的实现
1597 csr["Size"] = static_cast<uint8_t>(4);1587 csr["Size"] = static_cast<uint8_t>(4);
1598 csr["Period"] = static_cast<uint32_t>(10);1588 csr["Period"] = static_cast<uint32_t>(10);
1599 1589 
1600- auto connector = create_test_connector();1590+ // Offset is a typed uint32_t property: omission preserves its valid zero default.
1601- bool result = m_scanner->init(csr, connector);1591+ ASSERT_EQ(m_scanner->i_scanner.Offset.get_value().as<uint32_t>(), 0u);
1602- // 如果Offset字段缺失,init应该失败,但如果from_variant设置了默认值,则可能成功1592+ EXPECT_TRUE(m_scanner->init(csr, create_test_connector()));
1603- // 根据实际行为调整断言1593+ EXPECT_EQ(m_scanner->i_scanner.Offset.get_value().as<uint32_t>(), 0u);
1604- // 如果Offset缺失导致get_value()返回null,则应该失败
1605- // 否则,这个测试用例可能需要移除或修改
1606- if (result) {
1607- // 如果init成功了,说明Offset有默认值,这不是我们要测试的场景
1608- // 这个测试用例在这种情况下无法测试null检查
1609- GTEST_SKIP() << "Offset field has default value, cannot test null check";
1610- } else {
1611- EXPECT_FALSE(result);
1612- }
1613}1594}
1614 1595 
1615// 测试 IScanner::update_scanner_task() 异常处理 - BitIORead调用失败1596// 测试 IScanner::update_scanner_task() 异常处理 - BitIORead调用失败
@@ -1711,40 +1692,20 @@ TEST_F(ScannerTest, test_stop_scanner_task_exception_handling)
1711// 测试 IScanner::init_debounce_config() 异常处理 - Debounce引用为null1692// 测试 IScanner::init_debounce_config() 异常处理 - Debounce引用为null
1712TEST_F(ScannerTest, test_init_debounce_config_exception_null_ref)1693TEST_F(ScannerTest, test_init_debounce_config_exception_null_ref)
1713{1694{
1714- auto csr = create_test_csr();1695+ auto csr = create_test_csr();
1715- auto connector = create_test_connector();1696+ ASSERT_TRUE(m_scanner->init(csr, create_test_connector()));
1697+ auto& scanner = m_scanner->i_scanner;
1698+ scanner.FailureDebounceCount.set_value(1);
1699+ scanner.Debounce.set_property_type(mc::engine::p_type::ref_object);
1700+ scanner.Debounce.set_ref_object_cache(std::make_unique<mc::variant>());
1701+ ASSERT_EQ(scanner.Debounce.get_value().as<mc::engine::ref_object*>(), nullptr);
1716 1702 
1717- bool init_result = m_scanner->init(csr, connector);1703+ EXPECT_FALSE(scanner.init_debounce_config());
1718- ASSERT_TRUE(init_result);1704+ EXPECT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_FAILURE);
1719- 1705+ const auto info = scanner.dump();
1720- set_chip_reference();1706+ EXPECT_EQ(info.failure_count, 1u);
1721- 1707+ ASSERT_TRUE(info.error.has_value());
1722- // 设置Debounce为无效引用(空字符串)1708+ EXPECT_EQ(*info.error, "init_debounce_config failed: Debounce object reference is invalid");
1723- // 注意:如果Debounce属性类型不是ref_object,可能不会触发debounce初始化
1724- // 需要确保Debounce属性类型是ref_object才能测试这个场景
1725- m_scanner->i_scanner.Debounce.set_value(std::string(""));
1726- m_scanner->i_scanner.FailureDebounceCount.set_value(1);
1727- m_scanner->i_scanner.Period.set_value(10);
1728- m_scanner->i_scanner.ScanEnabled.set_value(1);
1729- 
1730- // 检查Debounce属性类型,如果不是ref_object,则跳过此测试
1731- uint32_t debounce_type = m_scanner->i_scanner.Debounce.get_property_type_value();
1732- if (debounce_type != static_cast<uint32_t>(mc::engine::p_type::ref_object)) {
1733- GTEST_SKIP() << "Debounce property is not ref_object type, cannot test "
1734- "null ref check";
1735- }
1736- 
1737- // 等待扫描任务执行(需要足够的时间让任务执行至少2次以触发失败)
1738- // Period为10ms,FailureDebounceCount为1,需要至少1次失败 = 10ms + 余量
1739- // debounce初始化失败会导致每次扫描都失败,所以failure_count会快速增加
1740- std::this_thread::sleep_for(std::chrono::milliseconds(30));
1741- 
1742- // 验证状态变为失败或预失败(由于debounce初始化失败)
1743- uint8_t status = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
1744- EXPECT_TRUE(status == SCANNER_FAILURE || status == SCANNER_PRE_FAILURE)
1745- << "Expected failure status (1 or 2), but got " << static_cast<int>(status);
1746- 
1747- m_scanner->stop();
1748}1709}
1749 1710 
1750// 测试 update_scanner_task 整体异常捕获1711// 测试 update_scanner_task 整体异常捕获
@@ -1845,43 +1806,25 @@ TEST_F(ScannerTest, test_stop_scanner_task_nominal_value_null)
1845// 测试 init_debounce_config - 异常处理路径(获取 Debounce 对象引用失败)1806// 测试 init_debounce_config - 异常处理路径(获取 Debounce 对象引用失败)
1846TEST_F(ScannerTest, test_init_debounce_config_exception_get_ref)1807TEST_F(ScannerTest, test_init_debounce_config_exception_get_ref)
1847{1808{
1848- auto csr = create_test_csr();1809+ const auto error_name = mc::engine::errors::internal_error.name;
1849- auto connector = create_test_connector();1810+ ASSERT_TRUE(mc::error_engine::get_instance().is_registered(error_name));
1850 1811 
1851- bool init_result = m_scanner->init(csr, connector);1812+ auto csr = create_test_csr();
1852- ASSERT_TRUE(init_result);1813+ ASSERT_TRUE(m_scanner->init(csr, create_test_connector()));
1814+ auto& scanner = m_scanner->i_scanner;
1815+ scanner.FailureDebounceCount.set_value(1);
1816+ install_ref_object(scanner.Debounce, m_mock_chip);
1817+ ASSERT_NE(scanner.Debounce.get_value().as<mc::engine::ref_object*>(), nullptr);
1818+ scanner.Debounce.set_error(error_name);
1819+ EXPECT_THROW(scanner.Debounce.get_value(), mc::error_exception);
1853 1820 
1854- set_chip_reference();1821+ EXPECT_FALSE(scanner.init_debounce_config());
1855- 1822+ EXPECT_EQ(scanner.Status.get_value().as<uint8_t>(), SCANNER_FAILURE);
1856- // 检查 Debounce 属性类型1823+ const auto info = scanner.dump();
1857- uint32_t debounce_type = m_scanner->i_scanner.Debounce.get_property_type_value();1824+ EXPECT_EQ(info.failure_count, 1u);
1858- if (debounce_type != static_cast<uint32_t>(mc::engine::p_type::ref_object)) {1825+ ASSERT_TRUE(info.error.has_value());
1859- GTEST_SKIP() << "Debounce property is not ref_object type, cannot test "1826+ EXPECT_NE(info.error->find("Unable to get Debounce object reference"), std::string::npos);
1860- "exception path";1827+ scanner.Debounce.clear_error();
1861- }
1862- 
1863- // 设置一个无效的 debounce 引用(可能导致异常)
1864- // 尝试设置一个不存在的对象引用
1865- m_scanner->i_scanner.Debounce.set_value(std::string("#/NonExistentDebounceObject"));
1866- m_scanner->i_scanner.FailureDebounceCount.set_value(1);
1867- m_scanner->i_scanner.Period.set_value(10);
1868- m_scanner->i_scanner.ScanEnabled.set_value(1);
1869- 
1870- // 启动 scanner
1871- bool start_result = m_scanner->start();
1872- ASSERT_TRUE(start_result);
1873- 
1874- // 等待扫描任务执行(init_debounce_config 会在 update_scanner_task 中被调用)
1875- std::this_thread::sleep_for(std::chrono::milliseconds(50));
1876- 
1877- // 验证状态变为失败或预失败(由于 debounce 初始化失败)
1878- uint8_t status = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
1879- EXPECT_TRUE(status == SCANNER_FAILURE || status == SCANNER_PRE_FAILURE || status == SCANNER_OK ||
1880- status == SCANNER_STOPPED)
1881- << "Status: " << static_cast<int>(status);
1882- 
1883- m_scanner->stop();
1884- std::this_thread::sleep_for(std::chrono::milliseconds(20));
1885}1828}
1886 1829 
1887// 测试 handle_result_success - 成功计数不足时跳过的情况1830// 测试 handle_result_success - 成功计数不足时跳过的情况
@@ -271,48 +271,96 @@ TEST_F(smbus_test, send_and_receive_request_in_frames_EmptyData)
271 EXPECT_FALSE(result.first);271 EXPECT_FALSE(result.first);
272}272}
273 273 
274-TEST_F(smbus_test, DISABLED_send_and_receive_request_in_frames_WithData)274+TEST_F(smbus_test, send_and_receive_request_in_frames_WithData)
275{275{
276 // 目标:验证 send_and_receive_request_in_frames 方法可以处理有数据的情况276 // 目标:验证 send_and_receive_request_in_frames 方法可以处理有数据的情况
277+ constexpr uint32_t receive_buffer_len = 64;
278+ constexpr uint16_t opcode = 0x0404;
279+ constexpr uint32_t data_written = 2;
280+ constexpr uint32_t frame_len = 10;
281+ constexpr int32_t expect_data_len = 0; // Pure write: reserved bytes stay zero.
282+ 
283+ const std::string payload = "test_data_longer_than_max_frame";
284+ 
277 std::vector<uint8_t> response_data = {0x01, 0x02};285 std::vector<uint8_t> response_data = {0x01, 0x02};
286+ std::vector<uint8_t> sent;
278 std::function<std::vector<uint8_t>(const std::vector<uint8_t>&, uint32_t)> func =287 std::function<std::vector<uint8_t>(const std::vector<uint8_t>&, uint32_t)> func =
279- [response_data](const std::vector<uint8_t>&, uint32_t) -> std::vector<uint8_t> {288+ [&sent, response_data, receive_buffer_len](const std::vector<uint8_t>& request,
289+ uint32_t length) -> std::vector<uint8_t> {
290+ sent = request;
291+ EXPECT_EQ(length, receive_buffer_len);
280 return response_data;292 return response_data;
281 };293 };
282 294 
283 m_smbus->register_WriteRead_method(func);295 m_smbus->register_WriteRead_method(func);
284 296 
285 // 初始化 smbus297 // 初始化 smbus
286- mc::mutable_dict params = {{"BufferLen", 64}, {"McuAddress", 0xD4}};298+ mc::mutable_dict params = {{"BufferLen", receive_buffer_len}, {"McuAddress", 0xD4}};
287 m_smbus->init(params);299 m_smbus->init(params);
288 300 
289 mc::mutable_dict track_request = {301 mc::mutable_dict track_request = {
290- {"opcode", 0x0404}, {"data_written", 2}, {"data", "test_data_longer_than_max_frame"}, {"max_frame_len", 10}};302+ {"opcode", opcode}, {"data_written", data_written}, {"data", payload},
303+ {"len", frame_len}, {"expect_data_len", expect_data_len}, {"max_frame_len", frame_len}};
291 auto result = m_smbus->send_and_receive_request_in_frames(track_request);304 auto result = m_smbus->send_and_receive_request_in_frames(track_request);
292- // 注册了 WriteRead 函数,expect success305+ // The transport response must be preserved, including a short reply.
293 EXPECT_TRUE(result.first);306 EXPECT_TRUE(result.first);
307+ EXPECT_EQ(result.second, response_data);
308+ // Independent little-endian wire bytes; do not use the production packer.
309+ const std::vector<uint8_t> header{0, 0, // reserved: pure write
310+ 0x04, 0x04, // opcode 0x0404
311+ 2, 0, 0, 0, // data_written / offset
312+ 10, 0, 0, 0}; // frame_len
313+ ASSERT_EQ(header.size(), header_len);
314+ // construct_request_data appends CRC32 after the payload, without padding.
315+ ASSERT_EQ(sent.size(), header.size() + payload.size() + crc32_len);
316+ EXPECT_EQ(std::vector<uint8_t>(sent.begin(), sent.begin() + header.size()), header);
317+ EXPECT_EQ(std::string(sent.begin() + header.size(), sent.end() - crc32_len), payload);
294}318}
295 319 
296-TEST_F(smbus_test, DISABLED_send_and_receive_request_in_frames_PartialData)320+TEST_F(smbus_test, send_and_receive_request_in_frames_PartialData)
297{321{
298 // 目标:验证 send_and_receive_request_in_frames 方法可以处理部分数据322 // 目标:验证 send_and_receive_request_in_frames 方法可以处理部分数据
323+ constexpr uint32_t receive_buffer_len = 64;
324+ constexpr uint16_t opcode = 0x0404;
325+ constexpr uint32_t data_written = 1;
326+ constexpr uint32_t frame_len = 10;
327+ constexpr int32_t expect_data_len = 0; // Pure write: reserved bytes stay zero.
328+ 
329+ const std::string payload = "test_data_longer_than_max_frame";
330+ 
299 std::vector<uint8_t> response_data = {0x01, 0x02};331 std::vector<uint8_t> response_data = {0x01, 0x02};
332+ std::vector<uint8_t> sent;
300 std::function<std::vector<uint8_t>(const std::vector<uint8_t>&, uint32_t)> func =333 std::function<std::vector<uint8_t>(const std::vector<uint8_t>&, uint32_t)> func =
301- [response_data](const std::vector<uint8_t>&, uint32_t) -> std::vector<uint8_t> {334+ [&sent, response_data, receive_buffer_len](const std::vector<uint8_t>& request,
335+ uint32_t length) -> std::vector<uint8_t> {
336+ sent = request;
337+ EXPECT_EQ(length, receive_buffer_len);
302 return response_data;338 return response_data;
303 };339 };
304 340 
305 m_smbus->register_WriteRead_method(func);341 m_smbus->register_WriteRead_method(func);
306 342 
307 // 初始化 smbus343 // 初始化 smbus
308- mc::mutable_dict params = {{"BufferLen", 64}, {"McuAddress", 0xD4}};344+ mc::mutable_dict params = {{"BufferLen", receive_buffer_len}, {"McuAddress", 0xD4}};
309 m_smbus->init(params);345 m_smbus->init(params);
310 346 
311 mc::mutable_dict track_request = {347 mc::mutable_dict track_request = {
312- {"opcode", 0x0404}, {"data_written", 1}, {"data", "test_data_longer_than_max_frame"}, {"max_frame_len", 10}};348+ {"opcode", opcode}, {"data_written", data_written}, {"data", payload},
349+ {"len", frame_len}, {"expect_data_len", expect_data_len}, {"max_frame_len", frame_len}};
313 auto result = m_smbus->send_and_receive_request_in_frames(track_request);350 auto result = m_smbus->send_and_receive_request_in_frames(track_request);
314- // 注册了 WriteRead 函数,expect success351+ // The transport response must be preserved, including a short reply.
315 EXPECT_TRUE(result.first);352 EXPECT_TRUE(result.first);
353+ EXPECT_EQ(result.second, response_data);
354+ // Independent little-endian wire bytes; do not use the production packer.
355+ const std::vector<uint8_t> header{0, 0, // reserved: pure write
356+ 0x04, 0x04, // opcode 0x0404
357+ 1, 0, 0, 0, // data_written / offset
358+ 10, 0, 0, 0}; // frame_len
359+ ASSERT_EQ(header.size(), header_len);
360+ // construct_request_data appends CRC32 after the payload, without padding.
361+ ASSERT_EQ(sent.size(), header.size() + payload.size() + crc32_len);
362+ EXPECT_EQ(std::vector<uint8_t>(sent.begin(), sent.begin() + header.size()), header);
363+ EXPECT_EQ(std::string(sent.begin() + header.size(), sent.end() - crc32_len), payload);
316}364}
317 365 
318TEST_F(smbus_test, unpack_response_data_ExpectDataLenZero)366TEST_F(smbus_test, unpack_response_data_ExpectDataLenZero)