diff --git a/.gitignore b/.gitignore index 35143d4..9a673a8 100644 --- a/.gitignore +++ b/.gitignore @@ -253,3 +253,7 @@ bin/pPMtest # CI test reports reports/ + +# CI test binaries (built into bin/) +bin/PowerMangerTests +bin/udpFeeder diff --git a/README.md b/README.md index 979cd16..fcceb6e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,25 @@ H100 能源管理程序 - [最新测试报告 (Markdown)](https://gitea2.zhaojingkui.xyz/zjk/H100PowerManger/src/branch/test-reports/test-report.md) - [JUnit XML](https://gitea2.zhaojingkui.xyz/zjk/H100PowerManger/raw/branch/test-reports/junit.xml) +## 自动化测试(PowerManger) + +CI 在 aarch64 (QEMU) 环境下运行两类测试,**不改动任何 `src/` 生产代码**: + +**单元测试(GoogleTest,`PowerMangerTests`,56 例)** +- `DriverTest`:10 个工况的设备表生成(STANDBY/岸基备航/水中备航/巡航/高速/上浮下潜/浮调/水下侦查/水面侦查/DJ)+ 设备状态读写 + JSON 往返 +- `SystemDataTest`:4 类配电/CCU 反馈的故障检测(断路器 0x55/0xAA/0x5A 映射、绝缘低、电源失电、漏水、DCDC、继电器位提取、缩放)+ 故障码集合/等级 + 漏水位 +- `FaultMapTest`:锂电池故障表(54) / 配电故障表(45) 完整性 +- `UpmsgTest`:上位机/外部通信 JSON 消息解析与序列化往返 +- `UdpCommTest`:UDP 校验和 / 消息头识别 / 校验解析 / 日期 +- `PmSysvariableTest`:`#pragma pack(1)` 协议结构体大小/偏移 + 枚举完整性 +- `SqliteTest`:用裸 sqlite3 读回校验 10 个 `insertData` 写入 + +**集成测试** +- 冒烟:MOOSDB + `pPowerManger` 拉起存活 +- UDP 回环:`udpFeeder` 发送真实 CCU 反馈 → 校验 `power_data.db` 落行(验证 UDP→SystemData→SQLite 全链路) + +本地跑测试:需要 GoogleTest(`git clone https://gitea2.zhaojingkui.xyz/zjk/googletest.git` 后 `cmake && make && make install`),然后 `./build.sh` 即可。 + ## 构建产物(Gitea Releases,手动部署) CI 构建的 aarch64 二进制(RK3588 / Ubuntu 20.04)会发布到 Gitea Releases,供手动下载部署: diff --git a/ci/Dockerfile b/ci/Dockerfile index d0aa9a0..a68bb9a 100755 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -63,4 +63,19 @@ RUN ./build-moos.sh -m -j"$(nproc)" && \ RUN apt-get update && apt-get install -y --no-install-recommends netbase \ && rm -rf /var/lib/apt/lists/* +#----------------------------------------------------------------------- +# GoogleTest (for unit tests) from the internal Gitea mirror. +# Cloned via the same BuildKit secret as MOOS-IvP; installed to /usr/local +# so `find_package(GTest)` works both inside the container and locally. +#----------------------------------------------------------------------- +RUN --mount=type=secret,id=gitea_token \ + set -eux; \ + TOKEN="$(cat /run/secrets/gitea_token)"; \ + git clone --depth 1 "https://zjk:${TOKEN}@gitea2.zhaojingkui.xyz/zjk/googletest.git" /opt/googletest; \ + mkdir -p /opt/googletest/build && cd /opt/googletest/build && \ + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_GMOCK=ON .. && \ + make -j"$(nproc)" && \ + make install && \ + ldconfig + WORKDIR /src diff --git a/ci/build-test.sh b/ci/build-test.sh index 45a9ae4..67b45be 100755 --- a/ci/build-test.sh +++ b/ci/build-test.sh @@ -26,8 +26,8 @@ fi #----------------------------------------------------------------------- # Result trackers #----------------------------------------------------------------------- -BUILD_OK=0; SQLITE_OK=0; SMOKE_OK=0 -BUILD_TIME=0; SQLITE_TIME=0; SMOKE_TIME=0 +BUILD_OK=0; SQLITE_OK=0; PMTESTS_OK=0; SMOKE_OK=0; INTEG_OK=0 +BUILD_TIME=0; SQLITE_TIME=0; PMTESTS_TIME=0; SMOKE_TIME=0; INTEG_TIME=0 now_ms() { date +%s; } @@ -72,7 +72,23 @@ if [ "${BUILD_OK}" = "1" ]; then fi #----------------------------------------------------------------------- -# 3. Startup smoke test: MOOSDB + pPowerManger liveness +# 3. PowerMangerTests (gtest) — 全部纯逻辑单测(工况设备表/故障检测/协议等) +#----------------------------------------------------------------------- +if [ "${BUILD_OK}" = "1" ]; then + echo ">>> Running PowerMangerTests (gtest)" + t0=$(now_ms) + if "${BIN_DIR}/PowerMangerTests" --gtest_output=xml:"${REPORT_DIR}/gtest-junit.xml" > /tmp/pmtests.log 2>&1; then + PMTESTS_OK=1 + echo ">>> PowerMangerTests passed" + else + echo ">>> PowerMangerTests FAILED" >&2 + tail -40 /tmp/pmtests.log >&2 + fi + PMTESTS_TIME=$(( $(now_ms) - t0 )) +fi + +#----------------------------------------------------------------------- +# 4. Startup smoke test: MOOSDB + pPowerManger liveness #----------------------------------------------------------------------- if [ "${BUILD_OK}" = "1" ]; then echo ">>> Smoke test: launch MOOSDB + pPowerManger" @@ -116,6 +132,23 @@ EOF echo ">>> Smoke test FAILED (pPowerManger exited)" >&2 fi + # --- 集成测试:UDP 反馈 → SQLite 落行 --- + if [ "${SMOKE_OK}" = "1" ]; then + echo ">>> Integration test: feed UDP feedback + check DB" + t1=$(now_ms) + if "${BIN_DIR}/udpFeeder" 127.0.0.1 5001 6 > /tmp/integ_feed.log 2>&1 \ + && sleep 2 \ + && PM_DB="${BIN_DIR}/power_data.db" python3 "${SRC_DIR}/test/integration/check_db.py" > /tmp/integ_check.log 2>&1; then + INTEG_OK=1 + echo ">>> Integration test PASSED (UDP->DB rows)" + else + echo ">>> Integration test FAILED" >&2 + cat /tmp/integ_feed.log >&2 + cat /tmp/integ_check.log >&2 + fi + INTEG_TIME=$(( $(now_ms) - t1 )) + fi + kill "${PM_PID}" "${MOOSDB_PID}" 2>/dev/null || true wait "${PM_PID}" "${MOOSDB_PID}" 2>/dev/null || true SMOKE_TIME=$(( $(now_ms) - t0 )) @@ -123,11 +156,11 @@ EOF fi #----------------------------------------------------------------------- -# 4. Generate visual test report (markdown + junit.xml) +# 5. Generate visual test report (markdown + junit.xml) #----------------------------------------------------------------------- mark() { [ "$1" = "1" ] && echo "✅ 通过" || echo "❌ 失败"; } ALL_OK=0 -[ "${BUILD_OK}" = "1" ] && [ "${SQLITE_OK}" = "1" ] && [ "${SMOKE_OK}" = "1" ] && ALL_OK=1 +[ "${BUILD_OK}" = "1" ] && [ "${SQLITE_OK}" = "1" ] && [ "${PMTESTS_OK}" = "1" ] && [ "${SMOKE_OK}" = "1" ] && [ "${INTEG_OK}" = "1" ] && ALL_OK=1 REPORT="${REPORT_DIR}/test-report.md" : > "$REPORT" @@ -141,7 +174,9 @@ md "| 步骤 | 结果 | 耗时 |" md "| :--- | :--- | :--- |" md "| 构建 (${ARCH}) | $(mark ${BUILD_OK}) | ${BUILD_TIME}s |" md "| 单元测试 SQLiteTest | $(mark ${SQLITE_OK}) | ${SQLITE_TIME}s |" +md "| 单元测试 PowerMangerTests (gtest) | $(mark ${PMTESTS_OK}) | ${PMTESTS_TIME}s |" md "| 冒烟测试 MOOSDB+pPowerManger | $(mark ${SMOKE_OK}) | ${SMOKE_TIME}s |" +md "| 集成测试 UDP反馈→SQLite | $(mark ${INTEG_OK}) | ${INTEG_TIME}s |" md "" if [ "${ALL_OK}" != "1" ]; then @@ -169,6 +204,17 @@ if [ "${ALL_OK}" != "1" ]; then md '' md "" fi + if [ "${PMTESTS_OK}" != "1" ]; then + md "### 单元测试 PowerMangerTests — ❌" + md "" + md '
PowerMangerTests 输出末尾' + md "" + md '```text' + tail -40 /tmp/pmtests.log >> "$REPORT" 2>/dev/null || true + md '```' + md '
' + md "" + fi if [ "${SMOKE_OK}" != "1" ]; then md "### 冒烟测试 MOOSDB+pPowerManger — ❌" md "" @@ -186,6 +232,23 @@ if [ "${ALL_OK}" != "1" ]; then md '' md "" fi + if [ "${INTEG_OK}" != "1" ]; then + md "### 集成测试 UDP反馈→SQLite — ❌" + md "" + md '
feed 日志末尾' + md "" + md '```text' + tail -20 /tmp/integ_feed.log >> "$REPORT" 2>/dev/null || true + md '```' + md '' + md '
check_db 日志末尾' + md "" + md '```text' + tail -10 /tmp/integ_check.log >> "$REPORT" 2>/dev/null || true + md '```' + md '
' + md "" + fi fi md "---" @@ -197,20 +260,26 @@ if [ -n "${RUN_LINK}" ]; then md "- 运行: [${RUN_LINK}](${RUN_LINK})" fi -# --- JUnit XML report --- +# --- JUnit XML report(汇总 gtest 单元测试 + 构建 + 冒烟 + 集成) --- cat > "${REPORT_DIR}/junit.xml" < - - + + $([ "${BUILD_OK}" = "1" ] || echo "") $([ "${SQLITE_OK}" = "1" ] || echo "") + + $([ "${PMTESTS_OK}" = "1" ] || echo "") + $([ "${SMOKE_OK}" = "1" ] || echo "") + + $([ "${INTEG_OK}" = "1" ] || echo "") + EOF @@ -219,6 +288,6 @@ echo ">>> Reports generated in ${REPORT_DIR}:" ls -la "${REPORT_DIR}" #----------------------------------------------------------------------- -# 5. Exit status +# 6. Exit status #----------------------------------------------------------------------- [ "${ALL_OK}" = "1" ] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1e8a705..7ae9542 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,10 +5,12 @@ project(H100PowerMangerTests) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) +# 需要 GoogleTest(由 CI Dockerfile 或本地安装,提供 GTestConfig/库) +find_package(GTest REQUIRED) -################################################SQLiteTest#################################################################### +################################################SQLiteTest(保留原有)####### # 添加 SQLite 测试可执行文件(不使用第三方测试库) -add_executable(SQLiteTest +add_executable(SQLiteTest until/testSqlitdb.cpp ${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3/SQLite.cpp ${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3/sqlite3.c @@ -16,7 +18,7 @@ add_executable(SQLiteTest ) # 包含目录 -target_include_directories(SQLiteTest PRIVATE +target_include_directories(SQLiteTest PRIVATE ${CMAKE_SOURCE_DIR}/src/pPowerManger ${CMAKE_SOURCE_DIR}/src/pPowerManger/sqlit3 ) @@ -27,4 +29,82 @@ target_link_libraries(SQLiteTest dl ) -################################################PowerMangerTest#################################################################### +################################################PowerMangerTests(gtest)#### +# 综合单元测试:Driver / SystemData / FaultMap / Upmsg / UdpComm / +# PmSysvariable / SQLite 读回。仅链接现有 src 源码,不改动生产代码。 +set(PM_SRC_DIR ${CMAKE_SOURCE_DIR}/src/pPowerManger) + +set(PM_TEST_SOURCES + until/DriverTest.cpp + until/SystemDataTest.cpp + until/FaultMapTest.cpp + until/UpmsgTest.cpp + until/UdpCommTest.cpp + until/PmSysvariableTest.cpp + until/SqliteTest.cpp +) + +add_executable(PowerMangerTests ${PM_TEST_SOURCES} + ${PM_SRC_DIR}/driver.cpp + ${PM_SRC_DIR}/udpcomm/udpComm.cpp + ${PM_SRC_DIR}/upmsg/upmsg_definitions.cpp + ${PM_SRC_DIR}/sqlit3/SQLite.cpp + ${PM_SRC_DIR}/sqlit3/sqlite3.c + ${PM_SRC_DIR}/logc/loguru.cpp +) + +target_include_directories(PowerMangerTests PRIVATE + ${PM_SRC_DIR} + ${PM_SRC_DIR}/sqlit3 + ${PM_SRC_DIR}/upmsg + ${PM_SRC_DIR}/udpcomm + ${PM_SRC_DIR}/logc +) + +# gtest 需要 C++14+ +set_target_properties(PowerMangerTests PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON +) + +# 兼容两种 gtest 安装方式:镜像安装(提供 GTest::gtest_main) 或 apt(仅 GTest_LIBRARIES) +if(TARGET GTest::gtest_main) + set(GTEST_LIBS GTest::gtest_main GTest::gtest) +else() + set(GTEST_LIBS ${GTest_LIBRARIES}) + target_include_directories(PowerMangerTests PRIVATE ${GTest_INCLUDE_DIRS}) +endif() + +target_link_libraries(PowerMangerTests + ${GTEST_LIBS} + ${MOOS_LIBRARIES} + mbutil + jsoncpp + m + pthread + dl +) + +################################################udpFeeder(集成测试用)####### +# 集成测试辅助:向 pPowerManger 发送真实 CCU 状态反馈 UDP 消息(字节级一致) +add_executable(udpFeeder + integration/udp_feeder.cpp + ${PM_SRC_DIR}/udpcomm/udpComm.cpp + ${PM_SRC_DIR}/logc/loguru.cpp +) +target_include_directories(udpFeeder PRIVATE + ${PM_SRC_DIR} + ${PM_SRC_DIR}/udpcomm + ${PM_SRC_DIR}/logc +) +set_target_properties(udpFeeder PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED ON +) +target_link_libraries(udpFeeder + ${MOOS_LIBRARIES} + mbutil + m + pthread + dl +) diff --git a/test/integration/check_db.py b/test/integration/check_db.py new file mode 100644 index 0000000..f646884 --- /dev/null +++ b/test/integration/check_db.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +#======================================================================= +# check_db.py: 校验 pPowerManger 写入的 power_data.db 是否有 CCU 反馈记录 +#(验证 UDP 反馈 → SystemData → SQLite 全链路)。 +#======================================================================= +import sys, os, sqlite3 + +def main(): + db = os.environ.get('PM_DB', '/src/bin/power_data.db') + if not os.path.exists(db): + print(f"DB not found: {db}") + return 1 + conn = sqlite3.connect(db) + cur = conn.cursor() + try: + cur.execute("SELECT COUNT(*) FROM ccuState") + n = cur.fetchone()[0] + except Exception as e: # noqa: BLE001 + print(f"query failed: {e}") + conn.close() + return 1 + conn.close() + print(f"ccuState rows = {n}") + return 0 if n > 0 else 1 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/test/integration/udp_feeder.cpp b/test/integration/udp_feeder.cpp new file mode 100644 index 0000000..ef9c6cc --- /dev/null +++ b/test/integration/udp_feeder.cpp @@ -0,0 +1,54 @@ +//======================================================================= +// udp_feeder.cpp: 集成测试用 — 向 pPowerManger 发送 CCU 状态反馈 UDP 消息。 +// 直接使用真实 C++ 结构体构造消息(字节级与协议一致,无 Python 打包漂移)。 +// 用法: udpFeeder [host] [port] [count] +//======================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "udpcomm/udpComm.h" +#include "udpcomm/ccuUdpMsg.h" + +int main(int argc, char* argv[]) +{ + const char* host = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 5001; + int count = (argc > 3) ? atoi(argv[3]) : 6; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { perror("socket"); return 1; } + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_port = htons(static_cast(port)); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + perror("inet_pton"); + close(fd); + return 1; + } + + udpComm u; + for (int i = 0; i < count; i++) { + msg_CcuStateFbMsg m; + memset(&m, 0, sizeof(m)); + m.header.start1 = CCU_UDPMSG_START1; + m.header.start2 = CCU_UDPMSG_START2; + m.header.id = CCU_STATE_FB_ID; + m.data.ins_soc = 42; + m.data.dyn_soc = 66; + m.data.fc_status = 0x04; + m.data.heartbeat = i + 1; + m.checkCode = u.calculateChecksum(reinterpret_cast(&m), sizeof(m)); + ssize_t n = sendto(fd, &m, sizeof(m), 0, reinterpret_cast(&addr), sizeof(addr)); + if (n < 0) { perror("sendto"); close(fd); return 1; } + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + close(fd); + printf("sent %d CCU state msgs to %s:%d\n", count, host, port); + return 0; +} diff --git a/test/until/DriverTest.cpp b/test/until/DriverTest.cpp new file mode 100644 index 0000000..cf330d8 --- /dev/null +++ b/test/until/DriverTest.cpp @@ -0,0 +1,252 @@ +//======================================================================= +// DriverTest: Driver 类的设备表生成(覆盖全部 10 个工况)、设备状态读写、 +// JSON 序列化/反序列化。 +// 不改动生产代码;仅链接 src/pPowerManger/driver.cpp。 +//======================================================================= +#include +#include +#include "driver.h" +#include "pmSysvariable.h" + +// ---------------------------------------------------------------------- +// 全字段比较宏(62 个设备字段) +// ---------------------------------------------------------------------- +#define EXPECT_TABLE_EQ(a, b) do { \ + EXPECT_EQ((a).depth1,(b).depth1); EXPECT_EQ((a).depth2,(b).depth2); \ + EXPECT_EQ((a).depth3,(b).depth3); EXPECT_EQ((a).overDeep1,(b).overDeep1); \ + EXPECT_EQ((a).overDeep2,(b).overDeep2); EXPECT_EQ((a).ctd1,(b).ctd1); \ + EXPECT_EQ((a).ctd2,(b).ctd2); EXPECT_EQ((a).soundVeloc,(b).soundVeloc); \ + EXPECT_EQ((a).commMast,(b).commMast); EXPECT_EQ((a).ins,(b).ins); \ + EXPECT_EQ((a).ntp,(b).ntp); \ + EXPECT_EQ((a).inputWaterSensor1,(b).inputWaterSensor1); \ + EXPECT_EQ((a).inputWaterSensor2,(b).inputWaterSensor2); \ + EXPECT_EQ((a).inputWaterSensor3,(b).inputWaterSensor3); \ + EXPECT_EQ((a).singleBeamSonar1,(b).singleBeamSonar1); \ + EXPECT_EQ((a).singleBeamSonar2,(b).singleBeamSonar2); \ + EXPECT_EQ((a).singleBeamSonar3,(b).singleBeamSonar3); \ + EXPECT_EQ((a).singleBeamSonar4,(b).singleBeamSonar4); \ + EXPECT_EQ((a).singleBeamSonar5,(b).singleBeamSonar5); \ + EXPECT_EQ((a).singleBeamSonar6,(b).singleBeamSonar6); \ + EXPECT_EQ((a).singleBeamSonar7,(b).singleBeamSonar7); \ + EXPECT_EQ((a).singleBeamSonar8,(b).singleBeamSonar8); \ + EXPECT_EQ((a).forwardSonar,(b).forwardSonar); EXPECT_EQ((a).DVL,(b).DVL); \ + EXPECT_EQ((a).lowFreqSonar,(b).lowFreqSonar); EXPECT_EQ((a).USBL,(b).USBL); \ + EXPECT_EQ((a).bowWaterInet1,(b).bowWaterInet1); \ + EXPECT_EQ((a).bowWaterInet2,(b).bowWaterInet2); \ + EXPECT_EQ((a).bowWaterInet3,(b).bowWaterInet3); \ + EXPECT_EQ((a).bowWaterInet4,(b).bowWaterInet4); \ + EXPECT_EQ((a).bowWaterInet5,(b).bowWaterInet5); \ + EXPECT_EQ((a).bowWaterInet6,(b).bowWaterInet6); \ + EXPECT_EQ((a).bowWaterInet7,(b).bowWaterInet7); \ + EXPECT_EQ((a).bowWaterInet8,(b).bowWaterInet8); \ + EXPECT_EQ((a).bowRudderDeploy,(b).bowRudderDeploy); \ + EXPECT_EQ((a).bowRudderServo1,(b).bowRudderServo1); \ + EXPECT_EQ((a).bowRudderServo2,(b).bowRudderServo2); \ + EXPECT_EQ((a).bowCover1,(b).bowCover1); EXPECT_EQ((a).bowCover2,(b).bowCover2); \ + EXPECT_EQ((a).bowCover3,(b).bowCover3); EXPECT_EQ((a).bowCover4,(b).bowCover4); \ + EXPECT_EQ((a).bowCover5,(b).bowCover5); EXPECT_EQ((a).bowCover6,(b).bowCover6); \ + EXPECT_EQ((a).waterInject,(b).waterInject); \ + EXPECT_EQ((a).mastLiftingServo,(b).mastLiftingServo); \ + EXPECT_EQ((a).buoyancyAdjust1,(b).buoyancyAdjust1); \ + EXPECT_EQ((a).buoyancyAdjust2,(b).buoyancyAdjust2); \ + EXPECT_EQ((a).buoyancyAdjust3,(b).buoyancyAdjust3); \ + EXPECT_EQ((a).buoyancyAdjust4,(b).buoyancyAdjust4); \ + EXPECT_EQ((a).buoyancyAdjust5,(b).buoyancyAdjust5); \ + EXPECT_EQ((a).sternRudderServo1,(b).sternRudderServo1); \ + EXPECT_EQ((a).sternRudderServo2,(b).sternRudderServo2); \ + EXPECT_EQ((a).sternRudderServo3,(b).sternRudderServo3); \ + EXPECT_EQ((a).sternRudderServo4,(b).sternRudderServo4); \ + EXPECT_EQ((a).thruster,(b).thruster); EXPECT_EQ((a).bowThrow,(b).bowThrow); \ + EXPECT_EQ((a).sternThrow,(b).sternThrow); \ + EXPECT_EQ((a).photoSensor,(b).photoSensor); \ + EXPECT_EQ((a).electSensor,(b).electSensor); \ + EXPECT_EQ((a).launchControler,(b).launchControler); \ + EXPECT_EQ((a).sideSonar,(b).sideSonar); EXPECT_EQ((a).buoyage,(b).buoyage); \ +} while (0) + +// 传感器统一全开 +static void setSensors(DriverTable& t) { + t.depth1 = t.depth2 = t.depth3 = 1; + t.overDeep1 = t.overDeep2 = 1; + t.ctd1 = t.ctd2 = 1; + t.soundVeloc = 1; + t.commMast = 1; + t.ins = 1; + t.ntp = 1; + t.inputWaterSensor1 = t.inputWaterSensor2 = t.inputWaterSensor3 = 1; +} + +// 执行机构(舵/浮调/推进/抛载)全开 +static void setActuatorsOn(DriverTable& t) { + t.bowRudderDeploy = t.bowRudderServo1 = t.bowRudderServo2 = 1; + t.buoyancyAdjust1 = t.buoyancyAdjust2 = t.buoyancyAdjust3 = 1; + t.buoyancyAdjust4 = t.buoyancyAdjust5 = 1; + t.sternRudderServo1 = t.sternRudderServo2 = t.sternRudderServo3 = t.sternRudderServo4 = 1; + t.thruster = 1; + t.bowThrow = t.sternThrow = 1; +} + +static DriverTable makeSTANDBY() { + DriverTable t{}; setSensors(t); /* 其余全 0 */ return t; +} +static DriverTable makeSHORE_WATER() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.mastLiftingServo = 1; + t.bowCover1 = t.bowCover2 = t.bowCover3 = 1; + t.bowCover4 = t.bowCover5 = t.bowCover6 = 1; + t.bowWaterInet1 = t.bowWaterInet2 = t.bowWaterInet3 = t.bowWaterInet4 = 1; + t.bowWaterInet5 = t.bowWaterInet6 = t.bowWaterInet7 = t.bowWaterInet8 = 1; + t.waterInject = 1; + return t; +} +static DriverTable makeCRUISE() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.mastLiftingServo = 1; + return t; // 水声设备不设置(保持 0) +} +static DriverTable makeHIGH_SPEED() { + DriverTable t{}; setSensors(t); + t.bowRudderDeploy = t.bowRudderServo1 = t.bowRudderServo2 = 1; + t.sternRudderServo1 = t.sternRudderServo2 = t.sternRudderServo3 = t.sternRudderServo4 = 1; + t.thruster = 1; t.bowThrow = t.sternThrow = 1; + /* mastLiftingServo / buoyancyAdjust 全 0 */ + return t; +} +static DriverTable makeASCEND_BUOYANCY() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.mastLiftingServo = 1; + return t; +} +static DriverTable makeUNDERWATER() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.sideSonar = DRIVER_STANDBY; /* mastLiftingServo = 0 */ + return t; +} +static DriverTable makeSURFACE() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.mastLiftingServo = 1; + t.photoSensor = t.electSensor = DRIVER_STANDBY; + return t; +} +static DriverTable makeDJ() { + DriverTable t{}; setSensors(t); setActuatorsOn(t); + t.mastLiftingServo = 1; + t.launchControler = 1; + t.buoyage = DRIVER_STANDBY; + t.bowCover1 = t.bowCover2 = t.bowCover3 = 1; + t.bowCover4 = t.bowCover5 = t.bowCover6 = 1; + t.bowWaterInet1 = t.bowWaterInet2 = t.bowWaterInet3 = t.bowWaterInet4 = 1; + t.bowWaterInet5 = t.bowWaterInet6 = t.bowWaterInet7 = t.bowWaterInet8 = 1; + return t; +} + +using TableFn = void (Driver::*)(DriverTable*); + +TEST(DriverTest, StandbyTable) { + DriverTable e = makeSTANDBY(); DriverTable a{}; Driver d; d.getSTANDBYTable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, ShoreBasedReadyTable) { + DriverTable e = makeSHORE_WATER(); DriverTable a{}; Driver d; d.getSHORE_BASED_READYable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, WaterBasedReadyTable) { + DriverTable e = makeSHORE_WATER(); DriverTable a{}; Driver d; d.getWATER_BASED_READYable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, CruiseModeTable) { + DriverTable e = makeCRUISE(); DriverTable a{}; Driver d; d.getCRUISE_MODEable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, HighSpeedModeTable) { + DriverTable e = makeHIGH_SPEED(); DriverTable a{}; Driver d; d.getHIGH_SPEED_MODEable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, AscendDescendTable) { + DriverTable e = makeASCEND_BUOYANCY(); DriverTable a{}; Driver d; d.getASCEND_DESCENDable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, BuoyancyAdjustTable) { + DriverTable e = makeASCEND_BUOYANCY(); DriverTable a{}; Driver d; d.getBUOYANCY_ADJUSTable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, UnderwaterReconTable) { + DriverTable e = makeUNDERWATER(); DriverTable a{}; Driver d; d.getUNDERWATER_RECONable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, SurfaceReconTable) { + DriverTable e = makeSURFACE(); DriverTable a{}; Driver d; d.getSURFACE_RECONable(&a); + EXPECT_TABLE_EQ(a, e); +} +TEST(DriverTest, DjModeTable) { + DriverTable e = makeDJ(); DriverTable a{}; Driver d; d.getDJ_MODEable(&a); + EXPECT_TABLE_EQ(a, e); +} + +// ---------------------------------------------------------------------- +// setDeviceState / getDeviceState +// ---------------------------------------------------------------------- +TEST(DriverTest, SetGetDeviceState) +{ + Driver d; + DriverTable t{}; + d.setDeviceState(t, DeviceId::thruster, DRIVER_FULL_POWER_ON); + EXPECT_EQ(d.getDeviceState(t, DeviceId::thruster), 1u); + d.setDeviceState(t, DeviceId::thruster, DRIVER_POWER_OFF); + EXPECT_EQ(d.getDeviceState(t, DeviceId::thruster), 0u); + d.setDeviceState(t, DeviceId::sideSonar, DRIVER_STANDBY); + EXPECT_EQ(d.getDeviceState(t, DeviceId::sideSonar), 4u); + d.setDeviceState(t, DeviceId::bowCover1, DRIVER_FULL_POWER_ON); + EXPECT_EQ(d.getDeviceState(t, DeviceId::bowCover1), 1u); +} + +// ---------------------------------------------------------------------- +// JSON 序列化/反序列化往返 +// ---------------------------------------------------------------------- +TEST(DriverTest, JsonRoundTrip) +{ + Driver d; + DriverTable t{}; + d.setDeviceState(t, DeviceId::thruster, DRIVER_FULL_POWER_ON); + d.setDeviceState(t, DeviceId::sideSonar, DRIVER_STANDBY); + d.setDeviceState(t, DeviceId::bowCover3, DRIVER_FULL_POWER_ON); + std::string json = d.saveDriverTableToJson(&t); + ASSERT_FALSE(json.empty()); + + DriverTable out{}; + EXPECT_EQ(d.loadDriverTableFromJson(json, out), 0); + EXPECT_EQ(out.thruster, 1u); + EXPECT_EQ(out.sideSonar, 4u); + EXPECT_EQ(out.bowCover3, 1u); + EXPECT_EQ(out.depth1, 0u); // 未设置字段为 0 +} + +TEST(DriverTest, LoadDriverIdFromJson) +{ + Driver d; + std::string json = R"({"thruster_uint8":1,"sideSonar_uint8":4})"; + auto cmds = d.loadDriverIdFromJson(json); + ASSERT_EQ(cmds.size(), 2u); + bool hasThruster = false, hasSide = false; + for (auto& c : cmds) { + if (c.id == DeviceId::thruster) { EXPECT_EQ(c.cmd, 1); hasThruster = true; } + if (c.id == DeviceId::sideSonar) { EXPECT_EQ(c.cmd, 4); hasSide = true; } + } + EXPECT_TRUE(hasThruster); + EXPECT_TRUE(hasSide); +} + +TEST(DriverTest, SafeReadUInt) +{ + Driver d; + Json::Value root; + root["a"] = 5u; + root["b"] = -3; + unsigned char v = 0; + EXPECT_EQ(d.SafeReadUInt(root, "a", v), 0); + EXPECT_EQ(v, 5u); + // 缺字段 + EXPECT_EQ(d.SafeReadUInt(root, "missing", v), -1); + // 类型错误(有符号负数) + EXPECT_EQ(d.SafeReadUInt(root, "b", v), -2); +} diff --git a/test/until/FaultMapTest.cpp b/test/until/FaultMapTest.cpp new file mode 100644 index 0000000..fb50ab0 --- /dev/null +++ b/test/until/FaultMapTest.cpp @@ -0,0 +1,45 @@ +//======================================================================= +// FaultMapTest: 锂电池/配电故障码表的完整性(码值唯一、级别正确) +// 不改动生产代码;仅验证 faultCode.h 中的静态表。 +//======================================================================= +#include +#include +#include "faultCode.h" + +TEST(FaultMapTest, BatteryFaultMap) +{ + const size_t n = sizeof(batfaultMap) / sizeof(batfaultMap[0]); + ASSERT_EQ(n, 54u) << "batfaultMap 应有 54 条"; + std::set codes; + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ(batfaultMap[i].id, (int)i + 1) << "id 必须从 1 连续"; + EXPECT_GE(batfaultMap[i].level, 1); + EXPECT_LE(batfaultMap[i].level, 4); + EXPECT_TRUE(batfaultMap[i].desc != nullptr && batfaultMap[i].desc[0] != '\0') + << "desc 不能为空, id=" << i + 1; + EXPECT_TRUE(codes.insert(batfaultMap[i].code).second) + << "重复故障码 0x" << std::hex << (int)batfaultMap[i].code; + } + // 抽样:4级故障码在 0xCx,3级在 0x8x,1级在 0x0x-0x1x + EXPECT_EQ(batfaultMap[0].code, 0xC1); // id1 四级 MBMS绝缘故障 + EXPECT_EQ(batfaultMap[9].code, 0x81); // id10 三级 + EXPECT_EQ(batfaultMap[40].code, 0x01); // id41 一级 +} + +TEST(FaultMapTest, DistributionFaultMap) +{ + const size_t n = sizeof(disfaultMap) / sizeof(disfaultMap[0]); + ASSERT_EQ(n, 45u) << "disfaultMap 应有 45 条"; + std::set codes; + for (size_t i = 0; i < n; ++i) { + EXPECT_EQ(disfaultMap[i].id, (int)i + 1) << "id 必须从 1 连续"; + EXPECT_GE(disfaultMap[i].level, 1); + EXPECT_LE(disfaultMap[i].level, 4); + EXPECT_TRUE(disfaultMap[i].desc != nullptr && disfaultMap[i].desc[0] != '\0'); + EXPECT_TRUE(codes.insert(disfaultMap[i].code).second) + << "重复故障码 " << disfaultMap[i].code; + } + // 断路器脱扣基本是 4 级故障 + EXPECT_EQ(disfaultMap[0].level, 3); // id1 燃料电池断路器 3级 + EXPECT_EQ(disfaultMap[1].level, 4); // id2 动力锂电池断路器 4级 +} diff --git a/test/until/PmSysvariableTest.cpp b/test/until/PmSysvariableTest.cpp new file mode 100644 index 0000000..dcf66c6 --- /dev/null +++ b/test/until/PmSysvariableTest.cpp @@ -0,0 +1,138 @@ +//======================================================================= +// PmSysvariableTest: 协议结构体 pack(1) 大小/偏移 + 枚举/宏完整性 +// 不改动生产代码;检测二进制协议漂移与枚举值变化。 +//======================================================================= +#include +#include +#include +#include "pmSysvariable.h" +#include "driver.h" +#include "udpcomm/ccuUdpMsg.h" + +// ---------------------------------------------------------------------- +// pack(1) 协议结构体大小断言 +// ---------------------------------------------------------------------- +TEST(PmSysvariableTest, HeaderStructSize) +{ + EXPECT_EQ(sizeof(m_header), 6u); // start1+start2+id+length = 1+1+2+2 + EXPECT_EQ(offsetof(m_header, id), 2u); + EXPECT_EQ(offsetof(m_header, length), 4u); +} + +TEST(PmSysvariableTest, PackedMessageStructSizes) +{ + // header(6) + flag(1) + failureCode(1) + Checksum(4) + EXPECT_EQ(sizeof(msg_CcuSetParmFbMsg), 12u); + // header(6) + m_date(8) + ccuColCmd + Checksum(4) + EXPECT_EQ(sizeof(msg_CcuColCmdMsg), 6u + sizeof(m_date) + sizeof(ccuColCmd) + 4u); + // header(6) + disHighVolBusCmd + cChecksum(1) + EXPECT_EQ(sizeof(msg_disHVBCmd), 6u + sizeof(disHighVolBusCmd) + 1u); + // header(6) + ccuState + Checksum(4) + EXPECT_EQ(sizeof(msg_CcuStateFbMsg), 6u + sizeof(ccuState) + 4u); +} + +TEST(PmSysvariableTest, NoImplicitPadding) +{ + // pack(1):字段偏移必须连续(抽样检查,防意外 padding) + EXPECT_EQ(offsetof(ccuColCmd, heartbeat), offsetof(ccuColCmd, powerBatEfficiency) + sizeof(unsigned short)); + EXPECT_EQ(offsetof(disHighVolBusCmd, coolingSystem), + offsetof(disHighVolBusCmd, dcdc5Module) + 1); +} + +// ---------------------------------------------------------------------- +// 宏值 +// ---------------------------------------------------------------------- +TEST(PmSysvariableTest, MacroConstants) +{ + EXPECT_EQ(BREAK_ON, 0x55); + EXPECT_EQ(BREAK_OFF, 0xAA); + EXPECT_EQ(BREAK_FALSE, 0x5A); + EXPECT_EQ(DIS_BREAKER_ON, 0x55); + EXPECT_EQ(DIS_BREAKER_OFF, 0xAA); + EXPECT_EQ(DIS_FAULTOFF, 0x5A); + EXPECT_EQ(DRIVER_POWER_OFF, 0x00); + EXPECT_EQ(DRIVER_FULL_POWER_ON, 0x01); + EXPECT_EQ(DRIVER_INSTRUMENT_POWER_ON, 0x02); + EXPECT_EQ(DRIVER_MOTIVE_POWER_ON, 0x03); + EXPECT_EQ(DRIVER_STANDBY, 0x04); +} + +// ---------------------------------------------------------------------- +// 工况枚举 +// ---------------------------------------------------------------------- +TEST(PmSysvariableTest, WorkConditionEnum) +{ + EXPECT_EQ((int)WorkCondition::CHANING, 0); + EXPECT_EQ((int)WorkCondition::STANDBY, 1); + EXPECT_EQ((int)WorkCondition::SHORE_BASED_READY, 2); + EXPECT_EQ((int)WorkCondition::WATER_BASED_READY, 3); + EXPECT_EQ((int)WorkCondition::REMOTE_CONTROL, 4); + EXPECT_EQ((int)WorkCondition::CRUISE_MODE, 5); + EXPECT_EQ((int)WorkCondition::HIGH_SPEED_MODE, 6); + EXPECT_EQ((int)WorkCondition::ASCEND_DESCEND, 7); + EXPECT_EQ((int)WorkCondition::BUOYANCY_ADJUST, 8); + EXPECT_EQ((int)WorkCondition::UNDERWATER_RECON, 9); + EXPECT_EQ((int)WorkCondition::SURFACE_RECON, 10); + EXPECT_EQ((int)WorkCondition::DJ_MODE, 11); + EXPECT_EQ((int)WorkCondition::LIFT_MODE, 12); +} + +TEST(PmSysvariableTest, TaskTypeEnum) +{ + EXPECT_EQ((int)TaskType::THROW_LOAD, 1); + EXPECT_EQ((int)TaskType::FLOAT_UP, 2); + EXPECT_EQ((int)TaskType::RECYCLE, 3); + EXPECT_EQ((int)TaskType::HOVER, 4); + EXPECT_EQ((int)TaskType::SAT_COMM, 5); + EXPECT_EQ((int)TaskType::SAT_CALIBRATE, 6); + EXPECT_EQ((int)TaskType::TOUR, 7); + EXPECT_EQ((int)TaskType::SAIL, 8); + EXPECT_EQ((int)TaskType::EMER_SAT_COMM, 9); + EXPECT_EQ((int)TaskType::EMER_SAIL, 10); + EXPECT_STREQ(TaskType::toString(TaskType::SAIL).c_str(), "sail"); + EXPECT_STREQ(TaskType::toString(TaskType::EMER_SAIL).c_str(), "emerSail"); + EXPECT_EQ((int)TaskType::fromString("tour"), (int)TaskType::TOUR); +} + +TEST(PmSysvariableTest, ModeEnum) +{ + EXPECT_EQ((int)PMMode::Normal, 0); + EXPECT_EQ((int)PMMode::Test, 1); + EXPECT_EQ((int)PMMode::Debug, 2); + EXPECT_EQ((int)RunMode::Normal, 0); + EXPECT_EQ((int)RunMode::Test, 1); + EXPECT_EQ((int)RunMode::Debug, 2); +} + +TEST(PmSysvariableTest, EquipmentCommandEnum) +{ + EXPECT_EQ((int)EquipmentCommand::OFF, 0); + EXPECT_EQ((int)EquipmentCommand::ON, 1); + EXPECT_EQ((int)EquipmentCommand::METER_ON, 2); + EXPECT_EQ((int)EquipmentCommand::POWER_ON, 3); + EXPECT_EQ((int)EquipmentCommand::METER_OFF, 4); + EXPECT_EQ((int)EquipmentCommand::POWER_OFF, 5); +} + +TEST(PmSysvariableTest, DeviceIdSpotCheck) +{ + EXPECT_EQ((int)DeviceId::thruster, 53); + EXPECT_EQ((int)DeviceId::sideSonar, 35); + EXPECT_EQ((int)DeviceId::bowRudderDeploy, 15); + EXPECT_EQ((int)DeviceId::mastLiftingServo, 41); + EXPECT_EQ((int)DeviceId::buoyancyAdjust1, 44); + EXPECT_EQ((int)DeviceId::sternRudderServo1, 49); + EXPECT_EQ((int)DeviceId::commMast, 40); + EXPECT_STREQ(DeviceId::toString(DeviceId::thruster).c_str(), "thruster"); +} + +TEST(PmSysvariableTest, DeviceIdSets) +{ + // 水声设备集合与无电设备集合互斥 + EXPECT_TRUE(underWterDevices.count(DeviceId::DVL) == 1); + EXPECT_TRUE(underWterDevices.count(DeviceId::singleBeamSonar1) == 1); + EXPECT_TRUE(noPowerDevices.count(DeviceId::depth1) == 1); + EXPECT_TRUE(noPowerDevices.count(DeviceId::ins) == 1); + // 无电设备不应出现在水声设备里 + EXPECT_TRUE(underWterDevices.count(DeviceId::depth1) == 0); +} diff --git a/test/until/SqliteTest.cpp b/test/until/SqliteTest.cpp new file mode 100644 index 0000000..3832811 --- /dev/null +++ b/test/until/SqliteTest.cpp @@ -0,0 +1,149 @@ +//======================================================================= +// SqliteTest(gtest): 验证 SQLite 封装 10 个 insertData 写入正确。 +// 不改动 SQLite 封装;用裸 sqlite3 打开同一文件读回验证。 +//======================================================================= +#include +#include +#include +#include +#include "sqlit3/SQLite.h" +#include "sqlit3/sqlite3.h" + +static std::string tmpPath(const char* name) { + return std::string("/tmp/pmsqlite_") + name + "_" + std::to_string(::getpid()) + ".db"; +} + +static bool rawQueryInt(const std::string& path, const std::string& sql, long long& out) { + sqlite3* db = nullptr; + if (sqlite3_open(path.c_str(), &db) != SQLITE_OK) return false; + sqlite3_stmt* stmt = nullptr; + bool ok = false; + if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { out = sqlite3_column_int64(stmt, 0); ok = true; } + } + if (stmt) sqlite3_finalize(stmt); + sqlite3_close(db); + return ok; +} + +static bool rawCount(const std::string& path, const std::string& table, long long& out) { + return rawQueryInt(path, "SELECT COUNT(*) FROM " + table, out); +} + +TEST(SqliteTest, InsertCcuStateReadBack) +{ + std::string path = tmpPath("ccu"); + { + SQLite db(path); + ASSERT_TRUE(db.createTable()); + ccuState c{}; c.ins_soc = 42; c.dyn_soc = 66; c.fc_status = 4; + EXPECT_TRUE(db.insertData(c)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT ins_soc FROM ccuState ORDER BY id DESC LIMIT 1", v)); + EXPECT_EQ(v, 42); + EXPECT_TRUE(rawQueryInt(path, "SELECT dyn_soc FROM ccuState ORDER BY id DESC LIMIT 1", v)); + EXPECT_EQ(v, 66); + EXPECT_TRUE(rawQueryInt(path, "SELECT fc_status FROM ccuState ORDER BY id DESC LIMIT 1", v)); + EXPECT_EQ(v, 4); + std::remove(path.c_str()); +} + +TEST(SqliteTest, InsertDisHighVolBusStateReadBack) +{ + std::string path = tmpPath("dihv"); + { + SQLite db(path); + ASSERT_TRUE(db.createTable()); + disHighVolBusState s{}; + s.fuelCellCircuitBreaker = 1; + s.powerBusVoltage = 5200; + EXPECT_TRUE(db.insertData(s)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT fuelCellCircuitBreaker FROM disHighVolBusState ORDER BY id DESC LIMIT 1", v)); + EXPECT_EQ(v, 1); + EXPECT_TRUE(rawQueryInt(path, "SELECT powerBusVoltage FROM disHighVolBusState ORDER BY id DESC LIMIT 1", v)); + EXPECT_EQ(v, 5200); + std::remove(path.c_str()); +} + +TEST(SqliteTest, InsertDisHighAVolBusStateReadBack) +{ + std::string path = tmpPath("dihva"); + { + SQLite db(path); ASSERT_TRUE(db.createTable()); + disHighAVolBusState s{}; s.bowFTDevice123CircuitBreaker = 1; s.busbarBVoltage = 100; + EXPECT_TRUE(db.insertData(s)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT bowFTDevice123CircuitBreaker FROM disHighAVolBusState", v)); + EXPECT_EQ(v, 1); + EXPECT_TRUE(rawQueryInt(path, "SELECT busbarBVoltage FROM disHighAVolBusState", v)); + EXPECT_EQ(v, 100); + std::remove(path.c_str()); +} + +TEST(SqliteTest, InsertDisLowMainBusStateReadBack) +{ + std::string path = tmpPath("dilmb"); + { + SQLite db(path); ASSERT_TRUE(db.createTable()); + disLowMainBusState s{}; s.unit4InstrumentDC48VCircuitBreaker = 1; + EXPECT_TRUE(db.insertData(s)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT unit4InstrumentDC48VCircuitBreaker FROM disLowMainBusState", v)); + EXPECT_EQ(v, 1); + std::remove(path.c_str()); +} + +TEST(SqliteTest, InsertDisLowBusStateReadBack) +{ + std::string path = tmpPath("dilb"); + { + SQLite db(path); ASSERT_TRUE(db.createTable()); + disLowBusState s{}; s.unit1CircuitBreaker = 1; + EXPECT_TRUE(db.insertData(s)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT unit1CircuitBreaker FROM disLowBusState", v)); + EXPECT_EQ(v, 1); + std::remove(path.c_str()); +} + +TEST(SqliteTest, InsertCommandsReadBack) +{ + std::string path = tmpPath("cmd"); + { + SQLite db(path); ASSERT_TRUE(db.createTable()); + ccuColCmd cc{}; cc.heartbeat = 5; EXPECT_TRUE(db.insertData(cc)); + disHighVolBusCmd c1{}; c1.dcdc5Module = 1; c1.coolingSystem = 1; EXPECT_TRUE(db.insertData(c1)); + disHighAVolBusCmd c2{}; c2.xczCircuitBreaker = 1; EXPECT_TRUE(db.insertData(c2)); + disLowMainBusCmd c3{}; c3.dcC1DCDistributionPanelCircuitBreaker = 1; EXPECT_TRUE(db.insertData(c3)); + disLowBusCmd c4{}; c4.unit2CircuitBreaker = 1; EXPECT_TRUE(db.insertData(c4)); + } + long long v = 0; + EXPECT_TRUE(rawQueryInt(path, "SELECT heartbeat FROM ccuColCmd", v)); EXPECT_EQ(v, 5); + EXPECT_TRUE(rawQueryInt(path, "SELECT dcdc5Module FROM disHighVolBusCmd", v)); EXPECT_EQ(v, 1); + EXPECT_TRUE(rawQueryInt(path, "SELECT xczCircuitBreaker FROM disHighAVolBusCmd", v)); EXPECT_EQ(v, 1); + EXPECT_TRUE(rawQueryInt(path, "SELECT dcC1DCDistributionPanelCircuitBreaker FROM disLowMainBusCmd", v)); EXPECT_EQ(v, 1); + EXPECT_TRUE(rawQueryInt(path, "SELECT unit2CircuitBreaker FROM disLowBusCmd", v)); EXPECT_EQ(v, 1); + std::remove(path.c_str()); +} + +TEST(SqliteTest, MultipleInsertsCount) +{ + std::string path = tmpPath("multi"); + { + SQLite db(path); ASSERT_TRUE(db.createTable()); + ccuState c{}; c.ins_soc = 10; + EXPECT_TRUE(db.insertData(c)); + EXPECT_TRUE(db.insertData(c)); + EXPECT_TRUE(db.insertData(c)); + } + long long n = 0; + EXPECT_TRUE(rawCount(path, "ccuState", n)); + EXPECT_EQ(n, 3); + std::remove(path.c_str()); +} diff --git a/test/until/SystemDataTest.cpp b/test/until/SystemDataTest.cpp new file mode 100644 index 0000000..df494ab --- /dev/null +++ b/test/until/SystemDataTest.cpp @@ -0,0 +1,203 @@ +//======================================================================= +// SystemDataTest: SystemData 的故障检测逻辑(断路器映射、绝缘、电源失电、 +// 漏水、DCDC、继电器位提取、缩放、故障码集合、漏水位)。 +// 不改动生产代码;仅链接现有 systemData.h + SQLite + MOOS。 +//======================================================================= +#include +#include "MOOS/libMOOS/Utils/MOOSUtilityFunctions.h" // MOOSTime (updateCommonStatus) +#include "systemData.h" +#include "faultCode.h" + +TEST(SystemDataTest, BreakerMapping) +{ + SystemData sd; + unsigned char status = 0; + // 0x55 = 闭合(1),无故障码 + sd.updateBreakerFault(status, 0x55, 99); + EXPECT_EQ(status, 1u); + EXPECT_EQ(sd.disSysFaultCode.count(99), 0u); + // 0xAA = 断开(0),无故障码 + sd.updateBreakerFault(status, 0xAA, 99); + EXPECT_EQ(status, 0u); + EXPECT_EQ(sd.disSysFaultCode.count(99), 0u); + // 0x5A = 故障脱扣(2),插入故障码 + sd.updateBreakerFault(status, 0x5A, 99); + EXPECT_EQ(status, 2u); + EXPECT_EQ(sd.disSysFaultCode.count(99), 1u); + // 再次正常后擦除 + sd.updateBreakerFault(status, 0x55, 99); + EXPECT_EQ(sd.disSysFaultCode.count(99), 0u); +} + +TEST(SystemDataTest, HighVolBusFaultDetection) +{ + SystemData sd; + disHighVolBusState s{}; + // 全 0xAA(断开,正常) + s.fuelCellCircuitBreaker = 0xAA; + s.powerLithiumBatteryCircuitBreaker = 0xAA; + s.propulsionMotorCircuitBreaker = 0xAA; + s.lithiumBatteryGroupInstrumentCircuitBreaker = 0xAA; + s.dcDc5ModuleCircuitBreaker = 0xAA; + s.bowHighVoltageDistributionBoxCircuitBreaker = 0xAA; + s.sternFTDevice45CircuitBreaker = 0xAA; + s.sternRudderSwitch1CircuitBreaker = 0xAA; + s.sternRudderSwitch2CircuitBreaker = 0xAA; + s.powerBusInsulationStatus = 0x55; + s.aBusInsulationStatus = 0x55; + s.meterPowerLossSignal = 0x55; + s.emergencyPowerLossSignal = 0x55; + sd.updateSystemData(s); + EXPECT_EQ(sd.power_bus_status.fuelCellCircuitBreaker, 0u); + EXPECT_EQ(sd.disSysFaultCode.count(1), 0u); + + // 燃料电断路器故障脱扣 + s.fuelCellCircuitBreaker = 0x5A; + sd.updateSystemData(s); + EXPECT_EQ(sd.power_bus_status.fuelCellCircuitBreaker, 2u); + EXPECT_EQ(sd.disSysFaultCode.count(1), 1u); + + // 动力锂电池闭合 + A汇流排/动力母排绝缘低 → 34/35 + s.fuelCellCircuitBreaker = 0xAA; + s.powerLithiumBatteryCircuitBreaker = 0x55; + s.powerBusInsulationStatus = 0xAA; + s.aBusInsulationStatus = 0xAA; + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(34), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(35), 1u); + EXPECT_EQ(sd.power_bus_status.powerBusInsulationStatus, 1u); + EXPECT_EQ(sd.power_bus_status.aBusInsulationStatus, 1u); + + // 电源失电 → 36/37 + s.meterPowerLossSignal = 0xAA; + s.emergencyPowerLossSignal = 0xAA; + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(36), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(37), 1u); + + // 全部恢复正常后故障码擦除 + s.powerLithiumBatteryCircuitBreaker = 0xAA; + s.powerBusInsulationStatus = 0x55; + s.aBusInsulationStatus = 0x55; + s.meterPowerLossSignal = 0x55; + s.emergencyPowerLossSignal = 0x55; + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(34), 0u); + EXPECT_EQ(sd.disSysFaultCode.count(35), 0u); + EXPECT_EQ(sd.disSysFaultCode.count(36), 0u); + EXPECT_EQ(sd.disSysFaultCode.count(37), 0u); +} + +TEST(SystemDataTest, HighAVolBusFaultDetection) +{ + SystemData sd; + disHighAVolBusState s{}; + s.actuatorCircuitBreaker = 0x5A; // 启闭机构断路器脱扣 → 14 + s.xczCircuitBreaker = 0x55; + s.instrumentPowerFailureSignal = 0x55; + s.emergencyPowerFailureSignal = 0x55; + s.waterIngressionAlarm = 0x55; + sd.updateSystemData(s); + EXPECT_EQ(sd.power_a_bus_status.actuatorCircuitBreaker, 2u); + EXPECT_EQ(sd.disSysFaultCode.count(14), 1u); + + s.actuatorCircuitBreaker = 0xAA; + s.instrumentPowerFailureSignal = 0xAA; // 失电 → 38 + s.emergencyPowerFailureSignal = 0xAA; // 失电 → 39 + s.waterIngressionAlarm = 0xAA; // 浸水 → leakStatus[BOW_HIGH_VOLTAGE] + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(14), 0u); + EXPECT_EQ(sd.disSysFaultCode.count(38), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(39), 1u); + EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_BOW_HIGH_VOLTAGE]); +} + +TEST(SystemDataTest, LowMainBusFaultDetection) +{ + SystemData sd; + disLowMainBusState s{}; + s.lithiumBatteryGroupInstrumentCircuitBreaker = 0x5A; // 19 + s.unit4InstrumentDC48VCircuitBreaker = 0x5A; // 21 + s.instrumentPowerFailureSignal = 0xAA; // 41 + s.emergencyPowerFailureSignal = 0xAA; // 42 + s.instrumentBusbarInsulationLow = 0xAA; // 43 + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(19), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(21), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(41), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(42), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(43), 1u); +} + +TEST(SystemDataTest, LowBusFaultDetection) +{ + SystemData sd; + disLowBusState s{}; + s.unit1CircuitBreaker = 0x5A; // 26 + s.emergencyLithiumBatteryGroup1CircuitBreaker = 0x5A; // 33 + s.powerFailureSignal = 0xAA; // 44 + s.emergencyPowerFailureSignal = 0xAA; // 45 + s.waterIngressionAlarm = 0xAA; // leakage BOW_LOW_VOLTAGE + sd.updateSystemData(s); + EXPECT_EQ(sd.disSysFaultCode.count(26), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(33), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(44), 1u); + EXPECT_EQ(sd.disSysFaultCode.count(45), 1u); + EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_BOW_LOW_VOLTAGE]); +} + +TEST(SystemDataTest, CcuStateScalingAndRelayBits) +{ + SystemData sd; + ccuState c{}; + c.ins_soc = 60; + c.dyn_soc = 80; + c.ins_relay_status1 = 0x12; // 高4位=1(充电), 低4位=2(正极) + c.ins_relay_status2 = 0x34; + c.ins_voltage_link = 4800; // ×0.1 = 480.0 + c.ins_water_leak_dcdc_status = 0x11; // 高4位=1 漏水, 低4位=1 DCDC故障 + c.dyn_water_leak_status = 0x21; // 高4位=2, 低4位=1 + sd.updateSystemData(c); + EXPECT_EQ(sd.ins_soc, 60); + EXPECT_EQ(sd.dyn_soc, 80); + EXPECT_EQ(sd.ins_pos_relay_status, 2u); // 低4位 + EXPECT_EQ(sd.ins_charge_relay_status, 1u); // 高4位 + EXPECT_EQ(sd.ins_precharge_relay_status, 4u); // 低4位 of status2 + EXPECT_EQ(sd.ins_neg_relay_status, 3u); // 高4位 of status2 + EXPECT_NEAR(sd.ins_voltage_link, 480.0, 0.01); + EXPECT_EQ(sd.water_leak_status_from_insbat, 1u); + EXPECT_EQ(sd.dcdc_status_from_insbat, 1u); + EXPECT_TRUE(sd.leakStatus[SystemData::LEAKAGE_LI_BATTERY]); +} + +TEST(SystemDataTest, FaultCodeSetAndLevel) +{ + SystemData sd; + EXPECT_EQ(sd.getFaultCodeLevel(), 0u); + sd.insertFaultCode(0x1102); // 0x11>>... level = (0x1102>>8)&0xF = 1 + EXPECT_EQ(sd.getFaultCodeLevel(), 1u); + sd.insertFaultCode(0x4402); // level = (0x4402>>8)&0xF = 4 + EXPECT_EQ(sd.getFaultCodeLevel(), 4u); + sd.eraseFaultCode(0x4402); + EXPECT_EQ(sd.getFaultCodeLevel(), 1u); + auto codes = sd.getFaultCode(); + EXPECT_EQ(codes.count(0x1102), 1u); +} + +TEST(SystemDataTest, LeakageCodeGeneration) +{ + SystemData sd; + EXPECT_EQ(sd.generateLeakageCode(), 0u); + sd.leakStatus[SystemData::LEAKAGE_BOW_EMERGENCY] = true; // 0x01 + sd.leakStatus[SystemData::LEAKAGE_FUEL_CELL] = true; // 0x08 + sd.leakStatus[SystemData::LEAKAGE_LI_BATTERY] = true; // 0x10 + EXPECT_EQ(sd.generateLeakageCode(), 0x19u); +} + +TEST(SystemDataTest, CommonStatusTimeout) +{ + SystemData sd; + sd.updateCommonStatus(30.0); + // 新建 SystemData 的 lastUpdateTime=0,与当前 MOOSTime 之差远大于 30s + EXPECT_TRUE(sd.device_common_status.ccu_status.isTimeout); +} diff --git a/test/until/UdpCommTest.cpp b/test/until/UdpCommTest.cpp new file mode 100644 index 0000000..a651063 --- /dev/null +++ b/test/until/UdpCommTest.cpp @@ -0,0 +1,111 @@ +//======================================================================= +// UdpCommTest: udpComm 纯逻辑函数(校验和、消息头识别、校验解析、日期) +// 仅链接现有 udpcomm/udpComm.cpp(+ MOOS 提供 MOOSTime 等符号)。 +//======================================================================= +#include +#include +#include "udpcomm/udpComm.h" + +TEST(UdpCommTest, CalculateChecksum) +{ + udpComm u; + unsigned char data[5] = {1, 2, 3, 4, 5}; + // 2 参:sum 覆盖 size - sizeof(Checksum)=size-4 字节 + EXPECT_EQ(u.calculateChecksum(data, 4), 0u); + EXPECT_EQ(u.calculateChecksum(data, 5), 1u); // 只 sum data[0]=1 + EXPECT_EQ(u.calculateChecksum(data, 6), 3u); // sum data[0..1]=1+2 + // 3 参:覆盖 dataSize - checkSize 字节 + EXPECT_EQ(u.calculateChecksum(data, 5, 2), 6u); // sum data[0..2]=1+2+3 + EXPECT_EQ(u.calculateChecksum(data, 5, 1), 10u);// sum data[0..3] +} + +TEST(UdpCommTest, GetMsgId) +{ + udpComm u; + m_header h{}; + char buf[sizeof(m_header)]; + unsigned short id = 0; + + // DIS 头 + h.start1 = DIS_MSG_HEAD1; + h.start2 = DIS_MSG_HEAD2; + h.id = DIS_HVBUS_FB_ID; + memcpy(buf, &h, sizeof(h)); + EXPECT_TRUE(u.getMsgId(buf, id)); + EXPECT_EQ(id, DIS_HVBUS_FB_ID); + + // CCU 头 + h.start1 = CCU_UDPMSG_START1; + h.start2 = CCU_UDPMSG_START2; + h.id = CCU_STATE_FB_ID; + memcpy(buf, &h, sizeof(h)); + EXPECT_TRUE(u.getMsgId(buf, id)); + EXPECT_EQ(id, CCU_STATE_FB_ID); + + // 非法头 + h.start1 = 0x11; + h.start2 = 0x22; + memcpy(buf, &h, sizeof(h)); + EXPECT_FALSE(u.getMsgId(buf, id)); +} + +TEST(UdpCommTest, GetMsgFromBuffCcu) +{ + udpComm u; + msg_CcuStateFbMsg m; + memset(&m, 0, sizeof(m)); + m.header.start1 = CCU_UDPMSG_START1; + m.header.start2 = CCU_UDPMSG_START2; + m.header.id = CCU_STATE_FB_ID; + m.data.ins_soc = 42; + m.data.dyn_soc = 66; + m.data.fc_status = 0x04; + m.checkCode = u.calculateChecksum((unsigned char*)&m, sizeof(m)); + + msg_CcuStateFbMsg out{}; + EXPECT_TRUE(u.getMsgFromBuff((char*)&m, out)); + EXPECT_EQ(out.data.ins_soc, 42u); + EXPECT_EQ(out.data.dyn_soc, 66u); + EXPECT_EQ(out.data.fc_status, 0x04u); + + // 篡改数据 → 校验失败 + m.data.dyn_soc = 99; + EXPECT_FALSE(u.getMsgFromBuff((char*)&m, out)); +} + +TEST(UdpCommTest, GetMsgFromBuffDis) +{ + udpComm u; + msg_disHighVolBusFbMsg m; + memset(&m, 0, sizeof(m)); + m.header.start1 = DIS_MSG_HEAD1; + m.header.start2 = DIS_MSG_HEAD2; + m.header.id = DIS_HVBUS_FB_ID; + m.data.fuelCellCircuitBreaker = 0x55; + m.data.powerBusVoltage = 5200; + m.checkCode = (cChecksum)(u.calculateChecksum((unsigned char*)&m, sizeof(m), sizeof(cChecksum)) & 0xFF); + + msg_disHighVolBusFbMsg out{}; + EXPECT_TRUE(u.getMsgFromBuff((char*)&m, out)); + EXPECT_EQ(out.data.fuelCellCircuitBreaker, 0x55u); + EXPECT_EQ(out.data.powerBusVoltage, 5200u); + + // 篡改 → 校验失败 + m.data.fuelCellCircuitBreaker = 0xAA; + EXPECT_FALSE(u.getMsgFromBuff((char*)&m, out)); +} + +TEST(UdpCommTest, GetDate) +{ + udpComm u; + unsigned short year = 0; + unsigned char month = 0, day = 0, hour = 0, minute = 0, second = 0, ms = 0; + EXPECT_TRUE(u.getDate(year, month, day, hour, minute, second, ms)); + EXPECT_GE(year, 2000u); + EXPECT_LE(year, 2100u); + EXPECT_GE(month, 1u); EXPECT_LE(month, 12u); + EXPECT_GE(day, 1u); EXPECT_LE(day, 31u); + EXPECT_LE(hour, 23u); + EXPECT_LE(minute, 59u); + EXPECT_LE(second, 59u); +} diff --git a/test/until/UpmsgTest.cpp b/test/until/UpmsgTest.cpp new file mode 100644 index 0000000..e2ed676 --- /dev/null +++ b/test/until/UpmsgTest.cpp @@ -0,0 +1,107 @@ +//======================================================================= +// UpmsgTest: 上位机/外部通信 JSON 消息解析与序列化往返 +// 仅链接现有 upmsg/*.h + upmsg_definitions.cpp。 +// 注意:部分 upmsg 头文件未包含 json/loguru/pmSysvariable,需先包含。 +//======================================================================= +#include +#include "json/json.h" +#include "logc/loguru.hpp" +#include "pmSysvariable.h" +#include "upmsg/uPower_sysState.h" +#include "upmsg/uExternComm_setOpMode.h" +#include "upmsg/uExternComm_setOpCondition.h" +#include "upmsg/uExternComm_setPowerSystem.h" +#include "upmsg/uExternComm_setDeviceSwitch.h" +#include "upmsg/uPlan_taskStart.h" +#include "upmsg/uPlan_planStop.h" +#include "upmsg/uDetect_xcVoltage_cmd.h" +#include "upmsg/uDetect_zdpVoltage_cmd.h" + +TEST(UpmsgTest, PowerSysStateParse) +{ + uPower_sysStateMsg msg; + MSControlCmd cmd; + EXPECT_TRUE(msg.parseControlCmdFromJson(cmd, R"({"powerCMD":1,"modCMD":2,"workCMD":5})")); + EXPECT_EQ(cmd.powerCMD, 1u); + EXPECT_EQ(cmd.modCMD, 2u); + EXPECT_EQ(cmd.workCMD, 5u); + // 缺字段 → false + EXPECT_FALSE(msg.parseControlCmdFromJson(cmd, R"({"powerCMD":1})")); + // 非法 JSON → false + EXPECT_FALSE(msg.parseControlCmdFromJson(cmd, "{bad json")); +} + +TEST(UpmsgTest, SetOpMode) +{ + uExternComm_setOpModeMsg msg; + EXPECT_EQ(msg.getSetOpMode("uExternComm_setOpMode_cmd", R"({"state_uint8":1})"), 1); + EXPECT_EQ(msg.getSetOpMode("uExternComm_setOpMode_cmd", R"({})"), -1); + EXPECT_EQ(msg.getSetOpMode("wrong_key", R"({"state_uint8":1})"), 0); // key 不匹配 → 0 +} + +TEST(UpmsgTest, SetOpCondition) +{ + uExternComm_setOpConditionMsg msg; + EXPECT_EQ(msg.getSetOpCondition("uExternComm_setOpCondition_cmd", R"({"state_uint8":7})"), 7); + EXPECT_EQ(msg.getSetOpCondition("uExternComm_setOpCondition_cmd", R"({})"), -1); +} + +TEST(UpmsgTest, SetPowerSystem) +{ + uExternComm_setPowerSystemMsg msg; + EXPECT_EQ(msg.getSetPMSys("uExternComm_setPowerSystem_cmd", R"({"powerCmd_uint8":4})"), 4); + EXPECT_EQ(msg.getSetPMSys("uExternComm_setPowerSystem_cmd", R"({})"), -1); +} + +TEST(UpmsgTest, SetDeviceSwitch) +{ + uExternComm_setDeviceSwitchMsg msg; + DeviceSwitchCmd c = msg.getSetDevSwitch("uExternComm_setDeviceSwitch_cmd", + R"({"id_uint8":53,"cmd_uint8":1})"); + EXPECT_EQ(c.id, 53); + EXPECT_EQ(c.cmd, 1); + // 空对象 → id=-1 + DeviceSwitchCmd c2 = msg.getSetDevSwitch("uExternComm_setDeviceSwitch_cmd", R"({})"); + EXPECT_EQ(c2.id, -1); +} + +TEST(UpmsgTest, TaskStart) +{ + uPlan_taskStartMsg msg; + MissionState m = msg.getMission("uPlan_taskStart_st", + R"({"taskType_str":"sail","balanceEnabled_bool":true,"bowRudderUnfold_bool":false})"); + EXPECT_EQ(m.type, (int)TaskType::SAIL); + EXPECT_TRUE(m.enableBuoyancyAdjustment); + EXPECT_FALSE(m.enableBowRudder); +} + +TEST(UpmsgTest, TaskStop) +{ + uPlan_planStopMsg msg; + EXPECT_TRUE(msg.getTaskStop("uPlan_planStop_st", R"({"op_int32":1})")); + EXPECT_FALSE(msg.getTaskStop("uPlan_planStop_st", R"({"op_int32":0})")); + EXPECT_FALSE(msg.getTaskStop("uPlan_planStop_st", R"({})")); +} + +TEST(UpmsgTest, XcVoltageRoundTrip) +{ + uDetect_xcVoltage_cmd cmd; + cmd.xcLowVoltageAdd_int32 = 1; + cmd.xcHighVoltageAdd_int32 = 2; + std::string json = cmd.toJsonString(); + uDetect_xcVoltage_cmd cmd2; + EXPECT_TRUE(cmd2.fromJsonString(json)); + EXPECT_EQ(cmd2.xcLowVoltageAdd_int32, 1); + EXPECT_EQ(cmd2.xcHighVoltageAdd_int32, 2); + EXPECT_FALSE(cmd2.fromJsonString("{bad json")); +} + +TEST(UpmsgTest, ZdpVoltageRoundTrip) +{ + uDetect_zdpVoltage_cmd cmd; + cmd.zdpVoltageAdd_int32 = 2; + std::string json = cmd.toJsonString(); + uDetect_zdpVoltage_cmd cmd2; + EXPECT_TRUE(cmd2.fromJsonString(json)); + EXPECT_EQ(cmd2.zdpVoltageAdd_int32, 2); +}