- offlog.h: OfflogPart 运行时分区表 + g_offlog_part; AREA_BASE 改 0x090000 (参数64KB+OTA512KB); 容量宏展开为运行时值(主体零改动) - offlog.c: offlog_part_detect() 读 JEDEC ID → 事件区容量 W25Q32=512KB/16256条, Q64=1MB/32640, Q128=2MB/65408, Q256=4MB/130944 - storage.c/h: SPI_Flash_ReadJEDEC_ID() (0x9F, 校验 EF 40) - 旧固件事件区 0x010000 自动废弃(新地址无 magic → 重建), 预期行为 - 测试: mock JEDEC=0x16 + g_offlog_part, 15 例全过 - 文档: BLE/MQTT/TCP 协议 capacity 8064→动态, 交互示例, devlog
313 lines
9.6 KiB
C
313 lines
9.6 KiB
C
/**
|
|
* test_offlog.c — offlog 环形事件日志 gcc 隔离单测
|
|
*
|
|
* 编译: gcc -I../BLE/OnlyUpdateApp_Peripheral/APP/include \
|
|
* -o test_offlog test_offlog.c
|
|
* 运行: ./test_offlog
|
|
*
|
|
* Mock: W25Q32 NOR 行为 (写=AND, 擦=0xFF), mstick/dev_time_now 固定值
|
|
* 覆盖: 全新初始化 / 环形回绕 / 掉电恢复 / 扇区切换 / 清空审计
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdint.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 时间 (已同步: >16亿) ============ */
|
|
static uint32_t mock_ms = 0;
|
|
uint32_t mstick(void) { return mock_ms; }
|
|
uint32_t dev_time_now(void) { return 1784768575UL; }
|
|
|
|
/* ch32v20x 库类型 (单测环境无 SDK) */
|
|
typedef unsigned char u8;
|
|
|
|
/* ============ 被测模块 (直接包含实现) ============ */
|
|
#include "../BLE/OnlyUpdateApp_Peripheral/APP/offlog.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;
|
|
}
|
|
|
|
/* 测试1: 全新初始化 → BOOT 事件可读回 */
|
|
static void test_fresh_init(void)
|
|
{
|
|
OfflogEvt e;
|
|
uint8_t payload[4] = {0x18, 0, 0, 0}; /* 假复位原因 (大端序写入 payload) */
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
CHECK(offlog_boot_seq() == 1);
|
|
CHECK(offlog_count() == 0); /* init 不写事件 */
|
|
|
|
offlog_boot(0x18000000); /* bit27+bit24 之类 */
|
|
CHECK(offlog_count() == 1);
|
|
CHECK(offlog_read_idx(0, &e) == 0);
|
|
CHECK(e.magic == OFFLOG_EVT_MAGIC);
|
|
CHECK(e.type == OFFLOG_EVT_BOOT);
|
|
CHECK(e.seq == 1);
|
|
CHECK(e.boot_seq == 1);
|
|
CHECK(e.flags & OFFLOG_UNIX_VALID_FLAG);
|
|
CHECK(e.len == 4);
|
|
CHECK(memcmp(e.payload, payload, 4) == 0);
|
|
CHECK(offlog_read_idx(1, &e) == -1); /* 越界 */
|
|
printf("PASS test_fresh_init\n");
|
|
}
|
|
|
|
/* 测试2: 环形回绕 — 写满 8064+5 条, count 封顶, 序号单调 */
|
|
static void test_ring_wrap(void)
|
|
{
|
|
OfflogEvt e;
|
|
uint32_t i, n = OFFLOG_MAX_RECORDS + 5;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
for (i = 0; i < n; i++) {
|
|
offlog_evt(OFFLOG_EVT_IOT_RECONN, NULL, 0);
|
|
}
|
|
CHECK(offlog_count() == OFFLOG_MAX_RECORDS - OFFLOG_EVT_PER_SECTOR + 5); /* 8064-128+5 */
|
|
/* 最新一条是最后写入的, seq 应等于 n (从 1 开始) */
|
|
CHECK(offlog_read_idx(offlog_count() - 1, &e) == 0);
|
|
CHECK(e.seq == n);
|
|
/* 逻辑首: 回绕时整扇区(128条)被擦 + 写回5条 → 最旧 = 129 */
|
|
CHECK(offlog_read_idx(0, &e) == 0);
|
|
CHECK(e.seq == n - OFFLOG_MAX_RECORDS + OFFLOG_EVT_PER_SECTOR - 4);
|
|
printf("PASS test_ring_wrap (seq %lu..%lu, count %u)\n",
|
|
(unsigned long)(n - OFFLOG_MAX_RECORDS + OFFLOG_EVT_PER_SECTOR - 4),
|
|
(unsigned long)n, offlog_count());
|
|
}
|
|
|
|
/* 测试3: 掉电恢复 — 写 10 条后重新 init, boot_seq 递增, seq 连续 */
|
|
static void test_power_loss_recovery(void)
|
|
{
|
|
OfflogEvt e;
|
|
uint32_t i;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
for (i = 0; i < 10; i++) {
|
|
offlog_evt(OFFLOG_EVT_COIL, NULL, 0);
|
|
}
|
|
CHECK(offlog_count() == 10);
|
|
|
|
/* 模拟掉电重启 (flash 保留, RAM 全失) */
|
|
offlog_init();
|
|
CHECK(offlog_boot_seq() == 2);
|
|
CHECK(offlog_count() == 10); /* 上电恢复出之前 10 条 */
|
|
|
|
offlog_evt(OFFLOG_EVT_IOT_CONNECT, NULL, 0);
|
|
CHECK(offlog_count() == 11);
|
|
CHECK(offlog_read_idx(10, &e) == 0);
|
|
CHECK(e.type == OFFLOG_EVT_IOT_CONNECT);
|
|
CHECK(e.seq == 11); /* seq 跨 boot 连续 */
|
|
CHECK(e.boot_seq == 2);
|
|
printf("PASS test_power_loss_recovery\n");
|
|
}
|
|
|
|
/* 测试4: 扇区切换 — 写满 128 条后头扇区更新, 数据完整 */
|
|
static void test_sector_switch(void)
|
|
{
|
|
OfflogEvt e;
|
|
OfflogHead h;
|
|
uint32_t i;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
for (i = 0; i < 128; i++) {
|
|
offlog_evt(OFFLOG_EVT_BOOT, NULL, 0);
|
|
}
|
|
/* 第 129 条触发扇区切换 */
|
|
offlog_evt(OFFLOG_EVT_TIME_ANCHOR, NULL, 0);
|
|
CHECK(offlog_count() == 129);
|
|
|
|
SPI_Flash_Read((uint8_t *)&h, OFFLOG_AREA_BASE, sizeof(h));
|
|
CHECK(h.magic[0] == OFFLOG_HEAD_MAGIC0);
|
|
CHECK(h.wr_sector == 2);
|
|
/* 头在扇区切换时刷新: wr_off = 新扇区起点 (尚未写入第 129 条) */
|
|
CHECK(h.wr_off == OFFLOG_DATA_BASE + 4096);
|
|
|
|
CHECK(offlog_read_idx(127, &e) == 0);
|
|
CHECK(e.type == OFFLOG_EVT_BOOT);
|
|
CHECK(e.seq == 128);
|
|
CHECK(offlog_read_idx(128, &e) == 0);
|
|
CHECK(e.type == OFFLOG_EVT_TIME_ANCHOR);
|
|
CHECK(e.seq == 129);
|
|
printf("PASS test_sector_switch\n");
|
|
}
|
|
|
|
/* 测试5: 清空 — 擦数据区 + LOG_CLEAR 审计 */
|
|
static void test_clear(void)
|
|
{
|
|
OfflogEvt e;
|
|
uint32_t i;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
for (i = 0; i < 5; i++) {
|
|
offlog_evt(OFFLOG_EVT_IOT_READY, NULL, 0);
|
|
}
|
|
offlog_clear();
|
|
CHECK(offlog_count() == 1); /* 只剩审计 */
|
|
CHECK(offlog_read_idx(0, &e) == 0);
|
|
CHECK(e.type == OFFLOG_EVT_LOG_CLEAR);
|
|
CHECK(e.seq == 6); /* seq 不重置 */
|
|
printf("PASS test_clear\n");
|
|
}
|
|
|
|
/* 测试6: 掉电恢复跨扇区 — 写到扇区 2 中间掉电 */
|
|
static void test_power_loss_mid_sector(void)
|
|
{
|
|
OfflogEvt e;
|
|
uint32_t i;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
for (i = 0; i < 130; i++) { /* 已跨入扇区 2 */
|
|
offlog_evt(OFFLOG_EVT_COIL, NULL, 0);
|
|
}
|
|
CHECK(offlog_count() == 130);
|
|
|
|
offlog_init(); /* 掉电重启 */
|
|
CHECK(offlog_boot_seq() == 2);
|
|
CHECK(offlog_count() == 130);
|
|
|
|
offlog_evt(OFFLOG_EVT_IOT_CONNECT, NULL, 0);
|
|
CHECK(offlog_count() == 131);
|
|
CHECK(offlog_read_idx(130, &e) == 0);
|
|
CHECK(e.seq == 131);
|
|
printf("PASS test_power_loss_mid_sector\n");
|
|
}
|
|
|
|
/* 测试7: TIME_ANCHOR 严格使用平台下发 unix_ts (不走 dev_time_now) */
|
|
static void test_time_anchor_exact(void)
|
|
{
|
|
OfflogEvt e;
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
offlog_time_anchor(1800000000UL); /* 平台下发值, 与 mock dev_time_now() 不同 */
|
|
CHECK(offlog_count() == 1);
|
|
CHECK(offlog_read_idx(0, &e) == 0);
|
|
CHECK(e.type == OFFLOG_EVT_TIME_ANCHOR);
|
|
CHECK(e.unix_ts == 1800000000UL); /* 严格等于传参 */
|
|
CHECK(e.flags & OFFLOG_UNIX_VALID_FLAG);
|
|
printf("PASS test_time_anchor_exact\n");
|
|
}
|
|
|
|
/* 测试8: 协议导出 — enabled/seq_last/type_str/evt_to_json (log_stat/log_query 数据源) */
|
|
static void test_export_json(void)
|
|
{
|
|
OfflogEvt e;
|
|
char buf[256];
|
|
|
|
reset_flash();
|
|
offlog_init();
|
|
CHECK(offlog_enabled() == 1);
|
|
CHECK(offlog_seq_last() == 0); /* 尚无记录 */
|
|
|
|
offlog_boot(0x80000000UL); /* IWDG 复位 → rst=2147483648 */
|
|
CHECK(offlog_count() == 1);
|
|
CHECK(offlog_seq_last() == 1);
|
|
CHECK(offlog_read_idx(0, &e) == 0);
|
|
CHECK(strcmp(offlog_type_str(e.type), "boot") == 0);
|
|
offlog_evt_to_json(&e, buf, sizeof(buf));
|
|
CHECK(strstr(buf, "\"type\":\"boot\"") != NULL);
|
|
CHECK(strstr(buf, "\"data\":{\"rst\":2147483648}") != NULL);
|
|
|
|
/* coil: payload = sub(1=进) ch(2) value(97, 大端) */
|
|
{
|
|
uint8_t p[6] = {1, 2, 0, 0, 0, 97};
|
|
offlog_evt(OFFLOG_EVT_COIL, p, 6);
|
|
}
|
|
CHECK(offlog_read_idx(1, &e) == 0);
|
|
CHECK(strcmp(offlog_type_str(e.type), "coil") == 0);
|
|
offlog_evt_to_json(&e, buf, sizeof(buf));
|
|
CHECK(strstr(buf, "\"type\":\"coil\"") != NULL);
|
|
CHECK(strstr(buf, "\"sub\":\"car_enter\"") != NULL);
|
|
CHECK(strstr(buf, "\"ch\":2") != NULL);
|
|
CHECK(strstr(buf, "\"value\":97") != NULL);
|
|
|
|
/* evt_retry: msg_id=5 retry=2 */
|
|
{
|
|
uint8_t p[5] = {0, 0, 0, 5, 2};
|
|
offlog_evt(OFFLOG_EVT_EVT_RETRY, p, 5);
|
|
}
|
|
CHECK(offlog_read_idx(2, &e) == 0);
|
|
offlog_evt_to_json(&e, buf, sizeof(buf));
|
|
CHECK(strstr(buf, "\"msg_id\":5") != NULL);
|
|
CHECK(strstr(buf, "\"retry\":2") != NULL);
|
|
|
|
/* 无 payload 事件 → data:null */
|
|
offlog_iot_ready();
|
|
CHECK(offlog_read_idx(3, &e) == 0);
|
|
offlog_evt_to_json(&e, buf, sizeof(buf));
|
|
CHECK(strstr(buf, "\"type\":\"iot_ready\"") != NULL);
|
|
CHECK(strstr(buf, "\"data\":null") != NULL);
|
|
|
|
/* seq_first 公式: seq_last - count + 1 (4 条: seq 1..4) */
|
|
CHECK(offlog_seq_last() == 4);
|
|
CHECK(offlog_count() == 4);
|
|
printf("PASS test_export_json (seq_first=%lu seq_last=%lu)\n",
|
|
(unsigned long)(offlog_seq_last() - offlog_count() + 1),
|
|
(unsigned long)offlog_seq_last());
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_fresh_init();
|
|
test_ring_wrap();
|
|
test_power_loss_recovery();
|
|
test_sector_switch();
|
|
test_clear();
|
|
test_power_loss_mid_sector();
|
|
test_time_anchor_exact();
|
|
test_export_json();
|
|
|
|
if (failures) {
|
|
printf("\n%d FAILURE(S)\n", failures);
|
|
return 1;
|
|
}
|
|
printf("\nALL PASS\n");
|
|
return 0;
|
|
}
|