feat(test): 阶段1收尾 — 数据中心单元测试34项全通过

- test_datacenter.cpp: 34 条测试覆盖生命周期、5种信号注册/控制、
  out延迟回调、yk同步控制、ao SBO流程、param多区定值、
  信号查询、类型工具(19种)、事件队列、异步管理
- COMPLETION.md: 记录阶段0/1完成事项清单
- x86 测试: 34 pass, 0 fail
- ARM 交叉编译: 10/10 模块全通过
This commit is contained in:
ypc 2026-07-07 17:57:32 +08:00
parent 7dd7572f46
commit ac4a183a60
2 changed files with 948 additions and 0 deletions

54
COMPLETION.md Normal file
View File

@ -0,0 +1,54 @@
# RTU_ALL_AI 开发完成事项清单
> 日期: 2026-07-07
---
## 阶段 0基础设施
| 任务 | 模块 | 状态 |
|------|------|:----:|
| 搭建构建系统 | `release/linux.mk`, `makefile`, `build.sh`, `copy_headers.sh` | ✅ |
| 创建 myBase.h 基础定义 | `release/inc/myBase.h` | ✅ |
| 实现侵入式链表 | `liblist` | ✅ |
| 实现日志系统 | `liblog` | ✅ |
| 实现基础工具函数65个 | `libfunc` | ✅ |
## 阶段 1数据基础设施
| 任务 | 模块 | 状态 |
|------|------|:----:|
| 实现 MD5 摘要算法 | `libmd5` | ✅ |
| 实现 xxHash 快速哈希 | `libmy_xxhash` | ✅ |
| 实现 cJSON 解析 | `libcJSON` | ✅ |
| 实现命令处理框架 | `libcmd` | ✅ |
| 实现 XML 解析 | `libxml` | ✅ |
| 实现数据中心15文件拆分 | `libdatacenter` | ✅ |
| 实现任务调度 | `libtask` | ✅ |
| 单元测试 | `test_datacenter.cpp`34项全通过 | ✅ |
| x86 编译验证 | 10/10 模块全通过 | ✅ |
| ARM 交叉编译验证 | 10/10 模块全通过 | ✅ |
## 阶段 2通讯与协议
| 任务 | 模块 | 状态 |
|------|------|:----:|
| 通讯抽象层 | `libcomm` | ⬜ |
| Modbus 协议栈 | `libmodbus` | ⬜ |
| IEC 60870-5-104 | `lib60870` | ⬜ |
| IEC 点表解析 | `libiec` | ⬜ |
| Modbus 主站管理 | `libmodbus_m` | ⬜ |
| 通讯通道解码 | `libcom_decode` | ⬜ |
| Web 管理服务 | `libweb_server` | ⬜ |
| 主程序入口 | `RTU` | ⬜ |
## 阶段 3系统集成
| 任务 | 模块 | 状态 |
|------|------|:----:|
| 端到端集成测试 | — | ⬜ |
| README 文档 | — | ⬜ |
## 阶段 4预留
ICP67 / IEC61850 MMS / MQTT / 嵌入式HTTP / PLC / 内部协议 —— 全部 ⬜

View File

@ -0,0 +1,894 @@
/**
* @file test_datacenter.cpp
* @brief
* @details 5 /out yk
* ao SBO param
*/
#include "myDatacenter.h"
#include "myBase.h"
#include <cstdio>
#include <cstring>
#include <cassert>
/* ===== 测试辅助 ===== */
static int g_pass = 0;
static int g_fail = 0;
#define TEST(name) \
do \
{ \
printf(" TEST: %s ... ", name); \
} while (0)
#define OK() \
do \
{ \
printf("OK\n"); \
g_pass++; \
} while (0)
#define FAIL(msg) \
do \
{ \
printf("FAIL: %s\n", msg); \
g_fail++; \
} while (0)
/* ===== 回调测试变量 ===== */
static int g_out_changed = 0;
static int g_yk_changed = 0;
static int g_ao_changed = 0;
static int g_param_changed = 0;
static int g_soe_received = 0;
static void on_out_changed(const char *saddr, uint8_t data_type,
const void *p_new, const void *p_old)
{
(void)saddr;
(void)data_type;
(void)p_new;
(void)p_old;
g_out_changed++;
}
static void on_yk_changed(const char *saddr, dc_ctrl_step_t step,
uint8_t data_type, uint8_t zone,
const void *p_data)
{
(void)saddr;
(void)step;
(void)data_type;
(void)zone;
(void)p_data;
g_yk_changed++;
}
static void on_ao_changed(const char *saddr, dc_ctrl_step_t step,
uint8_t data_type, uint8_t zone,
const void *p_data)
{
(void)saddr;
(void)step;
(void)data_type;
(void)zone;
(void)p_data;
g_ao_changed++;
}
static void on_param_changed(const char *saddr, dc_ctrl_step_t step,
uint8_t data_type, uint8_t zone,
const void *p_data)
{
(void)saddr;
(void)step;
(void)data_type;
(void)zone;
(void)p_data;
g_param_changed++;
}
static void on_soe_pop(void *p_data)
{
(void)p_data;
g_soe_received++;
}
/* ===== 测试用例 ===== */
/**
* @brief 1
*/
static void test_lifecycle(void)
{
TEST("dc_init()");
{
int r = dc_init(NULL);
if (DC_OK != r)
{
FAIL("init failed");
return;
}
OK();
}
TEST("dc_init() double");
{
int r = dc_init(NULL);
if (DC_OK != r)
{
FAIL("double init should return OK");
return;
}
OK();
}
TEST("dc_cleanup()");
{
dc_cleanup();
/* re-init for following tests */
dc_init(NULL);
OK();
}
}
/**
* @brief 2out + +
*/
static void test_out_signal(void)
{
int yc_val = 100;
TEST("dc_signal_out()");
{
int r = dc_signal_out("RPC1:YC:1", "test yc", DATA_TYPE_S32,
&yc_val, "protocol");
if (DC_OK != r)
{
FAIL("register failed");
return;
}
OK();
}
TEST("dc_signal_out() duplicate");
{
int r = dc_signal_out("RPC1:YC:1", "dup", DATA_TYPE_S32,
&yc_val, "protocol");
if (DC_ERR_EXISTS != r)
{
FAIL("duplicate should return EXISTS");
return;
}
OK();
}
TEST("dc_signal_out_link_with_callback()");
{
void *p = NULL;
g_out_changed = 0;
int r = dc_signal_out_link_with_callback("RPC1:YC:1", &p,
on_out_changed, "web");
if (DC_OK != r || p != &yc_val)
{
FAIL("link failed");
return;
}
OK();
}
TEST("dc_set_out_signal_val() + dc_run_100ms()");
{
g_out_changed = 0;
yc_val = 200;
int r = dc_set_out_signal_val("RPC1:YC:1", &yc_val, "protocol");
if (DC_OK != r)
{
FAIL("set val failed");
return;
}
/* 回路阻断protocol 自身不会被回调 */
dc_run_100ms();
if (g_out_changed != 1)
{
char msg[64];
snprintf(msg, sizeof(msg), "cb count=%d, expected 1", g_out_changed);
FAIL(msg);
return;
}
OK();
}
}
/**
* @brief 3in
*/
static void test_in_signal(void)
{
TEST("dc_signal_in()");
{
void *p = NULL;
int r = dc_signal_in("RPC1:YC:1:in", "test in", "RPC1:YC:1",
&p, "protocol");
if (DC_OK != r || !p)
{
FAIL("in register failed");
return;
}
OK();
}
TEST("dc_signal_in() bad link");
{
void *p = NULL;
int r = dc_signal_in("RPC1:YC:99:in", "bad", "RPC1:YC:99",
&p, "protocol");
if (DC_ERR_NOTFOUND != r)
{
FAIL("bad link should return NOTFOUND");
return;
}
OK();
}
}
/**
* @brief 4yk +
*/
static void test_yk_signal(void)
{
uint8_t yk_val = 0;
TEST("dc_signal_yk()");
{
int r = dc_signal_yk("RPC1:YK:1", "test yk", DATA_TYPE_U8,
DC_CTRL_DIRECT, &yk_val, on_yk_changed, "web");
if (DC_OK != r)
{
FAIL("yk register failed");
return;
}
OK();
}
TEST("dc_signal_yk_set_status() DIRECT");
{
g_yk_changed = 0;
dc_ctrl_t ctrl = {};
ctrl.type = DC_CTRL_DIRECT;
ctrl.data_type = DATA_TYPE_U8;
uint8_t v = 1;
int r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_DIRECT,
&ctrl, &v, "protocol");
if (DC_OK != r || yk_val != 1)
{
FAIL("yk set failed");
return;
}
if (g_yk_changed != 1)
{
FAIL("yk callback not triggered");
return;
}
/* 回路阻断web 自身 */ OK();
}
TEST("dc_signal_yk_set_status() SBO flow");
{
g_yk_changed = 0;
dc_ctrl_t ctrl = {};
ctrl.type = DC_CTRL_SBO;
ctrl.data_type = DATA_TYPE_U8;
ctrl.p_data = malloc(1);
*(uint8_t *)ctrl.p_data = 2;
uint8_t sel_val = 2;
int r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_SELECT,
&ctrl, &sel_val, "web");
if (DC_OK != r)
{
FAIL("SBO SELECT failed");
free(ctrl.p_data);
return;
}
/* DIRECT with matching value */
r = dc_signal_yk_set_status("RPC1:YK:1", DC_STEP_DIRECT,
&ctrl, &sel_val, "web");
if (DC_OK != r)
{
FAIL("SBO DIRECT failed");
free(ctrl.p_data);
return;
}
free(ctrl.p_data);
OK();
}
}
/**
* @brief 5ao + SBO
*/
static void test_ao_signal(void)
{
float ao_val = 0.0f;
TEST("dc_signal_ao()");
{
int r = dc_signal_ao("RPC1:AO:1", "test ao", DATA_TYPE_F32,
DC_CTRL_SBO, &ao_val, on_ao_changed, "protocol");
if (DC_OK != r)
{
FAIL("ao register failed");
return;
}
OK();
}
TEST("dc_signal_ao_set_val() SBO");
{
g_ao_changed = 0;
dc_ctrl_t ctrl = {};
ctrl.type = DC_CTRL_SBO;
ctrl.data_type = DATA_TYPE_F32;
ctrl.p_data = malloc(4);
*(float *)ctrl.p_data = 3.14f;
float v = 3.14f;
int r = dc_signal_ao_set_val("RPC1:AO:1", DC_STEP_SELECT,
&ctrl, &v, "web");
if (DC_OK != r)
{
FAIL("ao SELECT failed");
free(ctrl.p_data);
return;
}
r = dc_signal_ao_set_val("RPC1:AO:1", DC_STEP_DIRECT,
&ctrl, &v, "web");
if (DC_OK != r)
{
FAIL("ao DIRECT failed");
free(ctrl.p_data);
return;
}
free(ctrl.p_data);
if (g_ao_changed < 2)
{
FAIL("ao callback not triggered");
return;
}
/* ctrl->step should be reset to READY */
if (ctrl.step != DC_STEP_READY)
{
FAIL("ctrl step not reset to READY");
return;
}
OK();
}
TEST("dc_signal_ao_set_val_without_check()");
{
g_ao_changed = 0;
float v = 2.71f;
int r = dc_signal_ao_set_val_without_check("RPC1:AO:1", DATA_TYPE_F32,
&v, "protocol");
if (DC_OK != r)
{
FAIL("ao set without check failed");
return;
}
OK();
}
}
/**
* @brief 6param
*/
static void test_param_signal(void)
{
uint32_t p1 = 10;
uint32_t p2 = 20;
void *parr[2] = {&p1, &p2};
TEST("dc_signal_param()");
{
int r = dc_signal_param("RPC1:PARAM:1", "test param", DATA_TYPE_U32,
DC_CTRL_SBO, parr, 2, on_param_changed, "web");
if (DC_OK != r)
{
FAIL("param register failed");
return;
}
OK();
}
TEST("dc_signal_param_set_val() zone 0");
{
g_param_changed = 0;
dc_ctrl_t ctrl = {};
ctrl.type = DC_CTRL_SBO;
ctrl.data_type = DATA_TYPE_U32;
ctrl.p_data = malloc(4);
*(uint32_t *)ctrl.p_data = 55;
uint32_t v = 55;
int r = dc_signal_param_set_val("RPC1:PARAM:1", DC_STEP_SELECT,
&ctrl, 0, &v, "web");
if (DC_OK != r)
{
FAIL("param SELECT failed");
free(ctrl.p_data);
return;
}
r = dc_signal_param_set_val("RPC1:PARAM:1", DC_STEP_DIRECT,
&ctrl, 0, &v, "web");
if (DC_OK != r)
{
FAIL("param DIRECT failed");
free(ctrl.p_data);
return;
}
free(ctrl.p_data);
if (p1 != 55)
{
FAIL("zone0 not updated");
return;
}
OK();
}
}
/**
* @brief 7
*/
static void test_query(void)
{
TEST("dc_get_out_signal_info()");
{
char desc[64] = {0};
uint8_t dt = 0;
void *p = NULL;
int r = dc_get_out_signal_info("RPC1:YC:1", desc, 64, &dt, &p);
if (DC_OK != r || dt != DATA_TYPE_S32)
{
FAIL("query out failed");
return;
}
OK();
}
TEST("dc_get_signal_count()");
{
int n = dc_get_signal_count("out");
if (n < 1)
{
FAIL("out count should be >= 1");
return;
}
n = dc_get_signal_count("param");
if (n < 1)
{
FAIL("param count should be >= 1");
return;
}
OK();
}
TEST("dc_get_signal_info_by_id()");
{
char saddr[64] = {0};
char desc[64] = {0};
char dtstr[32] = {0};
uint8_t ct = 0;
int r = dc_get_signal_info_by_id("out", 0, saddr, 64, desc, 64,
dtstr, 32, &ct, NULL, 0);
if (DC_OK != r)
{
FAIL("get by id failed");
return;
}
OK();
}
TEST("dc_get_signal_val()");
{
uint32_t v = 42;
char buf[32] = {0};
int r = dc_get_signal_val(&v, DATA_TYPE_U32, buf, 32);
if (DC_OK != r || 0 != strcmp(buf, "42"))
{
FAIL("val to str failed");
return;
}
OK();
}
}
/**
* @brief 8
*/
static void test_type_util(void)
{
TEST("dc_get_type_name()");
{
const char *n = dc_get_type_name(DATA_TYPE_F32);
if (0 != strcmp(n, "float"))
{
FAIL("type name mismatch");
return;
}
OK();
}
TEST("dc_get_type_id_by_name()");
{
uint8_t id = dc_get_type_id_by_name("uint32_t");
if (DATA_TYPE_U32 != id)
{
FAIL("type id mismatch");
return;
}
OK();
}
TEST("dc_get_type_len()");
{
if (4 != dc_get_type_len(DATA_TYPE_F32))
{
FAIL("float len should be 4");
return;
}
if (8 != dc_get_type_len(DATA_TYPE_D64))
{
FAIL("double len should be 8");
return;
}
OK();
}
TEST("dc_create_data + dc_destroy_data");
{
void *p = dc_create_data(DATA_TYPE_S32);
if (!p)
{
FAIL("create data failed");
return;
}
dc_destroy_data(p, DATA_TYPE_S32);
OK();
}
TEST("dc_copy_val + dc_compare_val");
{
int a = 10;
int b = 0;
dc_copy_val(&b, &a, DATA_TYPE_S32);
if (b != 10)
{
FAIL("copy val failed");
return;
}
if (0 != dc_compare_val(&a, &b, DATA_TYPE_S32))
{
FAIL("compare equal failed");
return;
}
b = 20;
if (-1 != dc_compare_val(&a, &b, DATA_TYPE_S32))
{
FAIL("compare unequal failed");
return;
}
OK();
}
TEST("dc_set_val_from_str()");
{
float f = 0.0f;
int r = dc_set_val_from_str(&f, DATA_TYPE_F32, "3.14");
if (DC_OK != r || f < 3.13f || f > 3.15f)
{
FAIL("set from str failed");
return;
}
OK();
}
TEST("dc_set_val_from_str() IP");
{
uint8_t ip[4] = {0};
int r = dc_set_val_from_str(ip, DATA_TYPE_IP, "192.168.1.1");
if (DC_OK != r || ip[0] != 192 || ip[1] != 168 || ip[2] != 1 || ip[3] != 1)
{
FAIL("IP parse failed");
return;
}
OK();
}
}
/**
* @brief 9
*/
static void test_event_queue(void)
{
TEST("dc_event_queue_push + register");
{
int r = dc_event_register_queue_pop(on_soe_pop);
if (DC_OK != r)
{
FAIL("register pop failed");
return;
}
g_soe_received = 0;
int dummy = 99;
r = dc_event_queue_push(&dummy);
if (DC_OK != r)
{
FAIL("event push failed");
return;
}
/* 消耗队列 */
dc_run_100ms();
if (g_soe_received < 1)
{
FAIL("event not consumed");
return;
}
OK();
}
TEST("dc_disturb_dd_queue_push");
{
int dummy = 1;
int r = dc_disturb_dd_queue_push(&dummy);
if (DC_OK != r)
{
FAIL("disturb push failed");
return;
}
OK();
}
TEST("dc_fault_queue_push");
{
int dummy = 1;
int r = dc_fault_queue_push(&dummy);
if (DC_OK != r)
{
FAIL("fault push failed");
return;
}
OK();
}
}
/**
* @brief 10
*/
static void test_async_mgmt(void)
{
TEST("dc_async_pending_count()");
{
int n = dc_async_pending_count();
if (n < 0)
{
FAIL("pending count failed");
return;
}
OK();
}
TEST("dc_async_set_default_timeout()");
{
dc_async_set_default_timeout(5000);
OK();
}
TEST("dc_param_cfg_changed()");
{
int r = dc_param_cfg_changed();
if (r < 0)
{
FAIL("cfg changed failed");
return;
}
OK();
}
}
/* ===== 入口 ===== */
int main(void)
{
printf("=== test_datacenter ===\n\n");
/* 初始化 */
dc_init(NULL);
/* 信号注册 */
test_out_signal();
test_in_signal();
test_yk_signal();
test_ao_signal();
test_param_signal();
/* 查询 */
test_query();
/* 工具 */
test_type_util();
/* 事件队列 */
test_event_queue();
/* 异步管理 */
test_async_mgmt();
/* 清理 */
dc_cleanup();
/* 生命周期 */
dc_init(NULL);
test_lifecycle();
dc_cleanup();
/* 结果 */
printf("\n=== result: %d pass, %d fail ===\n", g_pass, g_fail);
return (g_fail > 0) ? 1 : 0;
}