/** * test_snapshot.c — 传感快照日志 gcc 隔离单测 * * 编译: gcc -I../BLE/OnlyUpdateApp_Peripheral/APP/include \ * -o test_snapshot test_snapshot.c * 运行: ./test_snapshot * * Mock: W25Q32 NOR 行为 (写=AND, 擦=0xFF), mstick 固定值, * g_offlog_part (事件区 512KB), offlog_evt (审计记录 mock) * 注意: 不 include offlog.c — 两个 .c 的 static 变量 (如 _wr_off) 在 * 同一翻译单元会符号冲突 (踩过: snap_clear 审计写乱了快照写指针) * 覆盖: 全新初始化 / 中断安全(enqueue 不落盘) / flush 落盘 / 打包格式 * 环形回绕 / 掉电恢复 / 扇区切换 / 暂存满丢新 / 清空审计 / 分区表 */ #include #include #include #include "../BLE/OnlyUpdateApp_Peripheral/APP/include/offlog.h" /* ============ mock W25Q32 (4MB, NOR 语义) ============ */ static uint8_t flash[4 * 1024 * 1024]; void SPI_Flash_Erase_Sector(uint32_t sec) { memset(flash + sec * 4096, 0xFF, 4096); } void SPI_Flash_Read(uint8_t *buf, uint32_t addr, uint16_t size) { memcpy(buf, flash + addr, size); } void SPI_Flash_Write_NoCheck(uint8_t *buf, uint32_t addr, uint16_t size) { uint16_t i; for (i = 0; i < size; i++) flash[addr + i] &= buf[i]; /* NOR: 只能 1→0 */ } uint8_t SPI_Flash_ReadJEDEC_ID(void) { return 0x16; } /* mock W25Q32 */ void SPI_Flash_Write(uint8_t *buf, uint32_t addr, uint16_t size) { /* 模拟 storage.c 行为: 目标区域非全 FF 则擦整扇区再写 */ uint16_t i; int need_erase = 0; for (i = 0; i < size; i++) { if (flash[addr + i] != 0xFF) { need_erase = 1; break; } } if (need_erase) SPI_Flash_Erase_Sector(addr / 4096); SPI_Flash_Write_NoCheck(buf, addr, size); } /* ============ mock 时间 ============ */ static uint32_t mock_ms = 0; uint32_t mstick(void) { return mock_ms; } /* ============ mock offlog 依赖 (不 include offlog.c, 避免 static 冲突) ============ */ OfflogPart g_offlog_part = { OFFLOG_CHIP_W25Q32, /* W25Q32 */ 0x80000UL, /* 事件区 512KB */ 127, 16256, }; static uint8_t g_audit_type; static uint8_t g_audit_payload[4]; static uint8_t g_audit_len; void offlog_evt(uint8_t type, const uint8_t *payload, uint8_t len) { g_audit_type = type; g_audit_len = len; if (payload && len > 0 && len <= 4) memcpy(g_audit_payload, payload, len); } /* ch32v20x 库类型 (单测环境无 SDK) */ typedef unsigned char u8; /* ============ 被测模块 (直接包含实现) ============ */ #include "../BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c" /* ============ 断言 ============ */ static int failures = 0; #define CHECK(cond) do { \ if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); failures++; } \ } while (0) static void reset_flash(void) { memset(flash, 0xFF, sizeof(flash)); mock_ms = 0; } /* 构造一帧传感报告: 4 线圈, 各线圈填可辨识值 */ static void make_report(LUP_SensorReport *sr, uint32_t base_freq, int32_t base_var) { uint8_t i; memset(sr, 0, sizeof(*sr)); sr->sens_type = 0x0C; sr->coil_count = 4; for (i = 0; i < 4; i++) { sr->coils[i].freq_level = 2; /* 中低 66nF */ sr->coils[i].direction = 1; sr->coils[i].freq_type = 0; sr->coils[i].sensitivity = 9; sr->coils[i].condition = 5; sr->coils[i].loop_state = 1; sr->coils[i].car_state = (i & 1); /* 0/1 交替 */ sr->coils[i].misc_type = 0; sr->coils[i].freq = base_freq + i * 1000; sr->coils[i].variation = base_var + i * 100; sr->coils[i].misc.passtime_ms = 0x10000000 + i; } } /* 测试1: 全新初始化 → enqueue 不落盘(中断安全), flush 落盘可读回 */ static void test_fresh_init_and_flush(void) { LUP_SensorReport sr; SnapRec r; uint32_t i; reset_flash(); snap_init(); CHECK(snap_enabled() == 1); CHECK(snap_count() == 0); make_report(&sr, 0x123456, 1000); mock_ms = 12345; /* 中断路径: 只 enqueue, 不 flush → flash 不得有任何快照数据 */ snap_enqueue(&sr); CHECK(snap_count() == 0); /* 未落盘 */ for (i = 0; i < 64; i++) { CHECK(flash[SNAP_DATA_BASE + i] == 0xFF); /* 数据区仍全 FF */ } /* 主循环: flush → 落盘 1 条 */ snap_flush(); CHECK(snap_count() == 1); CHECK(snap_read_idx(0, &r) == 0); CHECK(r.magic == SNAP_MAGIC); CHECK(r.len == 48); /* 4 线圈 × 12B */ CHECK(r.seq == 1); CHECK(r.boot_seq == 1); CHECK(r.ts_ms == 12345); CHECK(snap_seq_last() == 1); printf("PASS test_fresh_init_and_flush\n"); } /* 测试2: 打包格式与 0xC0 线上线圈单元 12B 一致 */ static void test_pack_format(void) { LUP_SensorReport sr; SnapRec r; reset_flash(); snap_init(); make_report(&sr, 0x123456, -12345); /* 负变化量 */ /* 覆盖值: freq_level=2 direction=1 freq_type=0 sens=9 → o[0]=0xA9 condition=5 loop=1 car=0 misc=3 → o[1]=0x5B freq=0x123456 → 56 34 12 variation=-12345 → 0xFFFFCFC7 截 24bit → C7 CF FF misc=0x10000000 → 00 00 00 10 */ sr.coils[0].freq = 0x123456; sr.coils[0].variation = -12345; sr.coils[0].misc_type = 3; sr.coils[0].misc.relay_count = 0x10000000; snap_enqueue(&sr); snap_flush(); snap_read_idx(0, &r); CHECK(r.coils[0] == 0xA9); CHECK(r.coils[1] == 0x5B); CHECK(r.coils[2] == 0x56 && r.coils[3] == 0x34 && r.coils[4] == 0x12); CHECK(r.coils[5] == 0xC7 && r.coils[6] == 0xCF && r.coils[7] == 0xFF); CHECK(r.coils[8] == 0x00 && r.coils[9] == 0x00 && r.coils[10] == 0x00 && r.coils[11] == 0x10); /* 线圈 2: car_state=1 → o[1] bit2=1 → 0x5F (其他字段相同) */ CHECK((r.coils[12 + 1] & 0x04) == 0x04); CHECK((r.coils[0 + 1] & 0x04) == 0x00); printf("PASS test_pack_format\n"); } /* 测试3: 环形回绕 — 写满 max+5 条, count 封顶, 序号单调 */ static void test_ring_wrap(void) { LUP_SensorReport sr; SnapRec r; uint32_t i, n = SNAP_MAX_RECORDS + 5; reset_flash(); snap_init(); make_report(&sr, 0x100, 100); for (i = 0; i < n; i++) { snap_enqueue(&sr); snap_flush(); } CHECK(snap_count() == SNAP_MAX_RECORDS - SNAP_REC_PER_SECTOR + 5); CHECK(snap_read_idx(snap_count() - 1, &r) == 0); CHECK(r.seq == n); CHECK(snap_seq_last() == n); CHECK(snap_read_idx(snap_count(), &r) == -1); /* 越界 */ printf("PASS test_ring_wrap (count %lu, max %lu)\n", (unsigned long)snap_count(), (unsigned long)SNAP_MAX_RECORDS); } /* 测试4: 掉电恢复 — 写 10 条后重新 init, boot_seq 递增, seq 连续 */ static void test_power_loss_recovery(void) { LUP_SensorReport sr; SnapRec r; uint32_t i; reset_flash(); snap_init(); make_report(&sr, 0x200, 200); for (i = 0; i < 10; i++) { snap_enqueue(&sr); snap_flush(); } CHECK(snap_count() == 10); snap_init(); /* 模拟掉电重启 */ CHECK(snap_boot_seq() == 2); CHECK(snap_count() == 10); snap_enqueue(&sr); snap_flush(); CHECK(snap_count() == 11); CHECK(snap_read_idx(10, &r) == 0); CHECK(r.seq == 11); CHECK(r.boot_seq == 2); printf("PASS test_power_loss_recovery\n"); } /* 测试5: 扇区切换 — 写满 64 条后头扇区更新, 数据完整 */ static void test_sector_switch(void) { LUP_SensorReport sr; SnapRec r; SnapHead h; uint32_t i; reset_flash(); snap_init(); make_report(&sr, 0x300, 300); for (i = 0; i < 64; i++) { snap_enqueue(&sr); snap_flush(); } snap_enqueue(&sr); snap_flush(); /* 第 65 条触发扇区切换 */ CHECK(snap_count() == 65); SPI_Flash_Read((uint8_t *)&h, g_snap_part.area_base, sizeof(h)); CHECK(h.magic[0] == SNAP_HEAD_MAGIC0); CHECK(h.wr_sector == 2); CHECK(h.wr_off == SNAP_DATA_BASE + 4096); CHECK(snap_read_idx(63, &r) == 0); CHECK(r.seq == 64); CHECK(snap_read_idx(64, &r) == 0); CHECK(r.seq == 65); printf("PASS test_sector_switch\n"); } /* 测试6: RAM 暂存满丢新 — 8 深, 第 9 条丢, flush 只落 8 条 */ static void test_ram_overflow_drop(void) { LUP_SensorReport sr; SnapRec r; uint32_t i; reset_flash(); snap_init(); make_report(&sr, 0x400, 400); for (i = 0; i < SNAP_RAM_DEPTH + 3; i++) { /* 11 条, 深 8 */ snap_enqueue(&sr); /* 不 flush, 模拟中断风暴 */ } CHECK(snap_count() == 0); /* 全部还在 RAM */ snap_flush(); CHECK(snap_count() == 8); /* 只落 8 条, 丢 3 条 */ CHECK(snap_read_idx(0, &r) == 0); CHECK(r.seq == 1); /* 丢的是最新(尾部) */ CHECK(snap_read_idx(7, &r) == 0); CHECK(r.seq == 8); printf("PASS test_ram_overflow_drop\n"); } /* 测试7: 清空 — 擦数据区 + 事件流审计 (payload[0]=2=快照流) */ static void test_clear_audit(void) { LUP_SensorReport sr; SnapRec r; uint32_t i; reset_flash(); g_audit_type = 0; g_audit_len = 0; memset(g_audit_payload, 0, sizeof(g_audit_payload)); snap_init(); make_report(&sr, 0x500, 500); for (i = 0; i < 5; i++) { snap_enqueue(&sr); snap_flush(); } CHECK(snap_count() == 5); snap_clear(); CHECK(snap_count() == 0); CHECK(snap_seq_last() == 5); /* seq 不重置 */ CHECK(snap_read_idx(0, &r) == -1); /* 数据区已空 */ /* 审计: offlog_evt 被调用, type=LOG_CLEAR, payload[0]=2 */ CHECK(g_audit_type == OFFLOG_EVT_LOG_CLEAR); CHECK(g_audit_len == 1); CHECK(g_audit_payload[0] == 2); printf("PASS test_clear_audit\n"); } /* 测试8: 运行时分区 — W25Q32 快照区 0x110000, 3008KB, 48064 条 */ static void test_part_table(void) { reset_flash(); snap_init(); CHECK(g_snap_part.area_base == 0x110000UL); CHECK(g_snap_part.area_size == 0x2F0000UL); /* 3008KB */ CHECK(g_snap_part.data_sectors == 751); CHECK(g_snap_part.max_records == 751 * 64); CHECK(SNAP_DATA_BASE == 0x111000UL); /* base + 4KB 头扇区 */ printf("PASS test_part_table\n"); } /* 测试9: 懒擦 — init 只擦当前写扇区, 其余扇区由环形写切扇区时覆盖 (修复: 原 init 擦全部 751 扇区 ~34s → 主循环阻塞超 IWDG 4s → 不断复位) */ static void test_lazy_erase(void) { LUP_SensorReport sr; SnapRec r; uint32_t i; reset_flash(); /* 在数据扇区 2 预埋旧数据 (模拟 Flash 残留) */ flash[SNAP_DATA_BASE + 4096] = 0xAA; flash[SNAP_DATA_BASE + 4096 + 1] = 0x55; snap_init(); /* 懒擦: 只擦扇区 1, 扇区 2 保持旧数据 */ CHECK(flash[SNAP_DATA_BASE] == 0xFF); /* 扇区1 已擦 */ CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 扇区2 未动 */ CHECK(flash[SNAP_DATA_BASE + 4096 + 1] == 0x55); make_report(&sr, 0x600, 600); for (i = 0; i < 64; i++) { /* 写满扇区 1 */ snap_enqueue(&sr); snap_flush(); } CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 仍在扇区 1 内, 扇区2 未动 */ snap_enqueue(&sr); /* 第 65 条切扇区 */ snap_flush(); CHECK(flash[SNAP_DATA_BASE + 4096] != 0xAA); /* 扇区2 已擦并写入新数据 */ CHECK(snap_read_idx(64, &r) == 0); CHECK(r.seq == 65); CHECK(snap_count() == 65); printf("PASS test_lazy_erase\n"); } int main(void) { test_fresh_init_and_flush(); test_pack_format(); test_ring_wrap(); test_power_loss_recovery(); test_sector_switch(); test_ram_overflow_drop(); test_clear_audit(); test_part_table(); test_lazy_erase(); if (failures) { printf("\n%d FAILURE(S)\n", failures); return 1; } printf("\nALL PASS\n"); return 0; }