测试:GoogleTest 全面单测(10工况设备表/故障检测/协议/消息/SQLite读回)+ UDP→SQLite 集成测试,src 零改动
build-test-deploy / ci (push) Failing after 11m45s

This commit is contained in:
zjk
2026-08-14 21:29:57 +08:00
parent f51a3ac750
commit de298414ff
14 changed files with 1286 additions and 13 deletions
+4
View File
@@ -253,3 +253,7 @@ bin/pPMtest
# CI test reports
reports/
# CI test binaries (built into bin/)
bin/PowerMangerTests
bin/udpFeeder
+19
View File
@@ -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,供手动下载部署:
+15
View File
@@ -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
+78 -9
View File
@@ -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 '</details>'
md ""
fi
if [ "${PMTESTS_OK}" != "1" ]; then
md "### 单元测试 PowerMangerTests — ❌"
md ""
md '<details><summary>PowerMangerTests 输出末尾</summary>'
md ""
md '```text'
tail -40 /tmp/pmtests.log >> "$REPORT" 2>/dev/null || true
md '```'
md '</details>'
md ""
fi
if [ "${SMOKE_OK}" != "1" ]; then
md "### 冒烟测试 MOOSDB+pPowerManger — ❌"
md ""
@@ -186,6 +232,23 @@ if [ "${ALL_OK}" != "1" ]; then
md '</details>'
md ""
fi
if [ "${INTEG_OK}" != "1" ]; then
md "### 集成测试 UDP反馈→SQLite — ❌"
md ""
md '<details><summary>feed 日志末尾</summary>'
md ""
md '```text'
tail -20 /tmp/integ_feed.log >> "$REPORT" 2>/dev/null || true
md '```'
md ''
md '<details><summary>check_db 日志末尾</summary>'
md ""
md '```text'
tail -10 /tmp/integ_check.log >> "$REPORT" 2>/dev/null || true
md '```'
md '</details>'
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" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="H100PowerManger" tests="3" failures="$([ "${ALL_OK}" = "1" ] && echo 0 || echo 1)" time="$((BUILD_TIME+SQLITE_TIME+SMOKE_TIME))">
<testsuite name="H100PowerManger CI" tests="3" failures="$([ "${ALL_OK}" = "1" ] && echo 0 || echo 1)" time="$((BUILD_TIME+SQLITE_TIME+SMOKE_TIME))">
<testsuites name="H100PowerManger" tests="5" failures="$([ "${ALL_OK}" = "1" ] && echo 0 || echo 1)" time="$((BUILD_TIME+SQLITE_TIME+PMTESTS_TIME+SMOKE_TIME+INTEG_TIME))">
<testsuite name="H100PowerManger CI" tests="5" failures="$([ "${ALL_OK}" = "1" ] && echo 0 || echo 1)" time="$((BUILD_TIME+SQLITE_TIME+PMTESTS_TIME+SMOKE_TIME+INTEG_TIME))">
<testcase name="build_${ARCH}" classname="ci.build" time="${BUILD_TIME}">
$([ "${BUILD_OK}" = "1" ] || echo "<failure message=\"build failed\"/>")
</testcase>
<testcase name="SQLiteTest" classname="ci.unit" time="${SQLITE_TIME}">
$([ "${SQLITE_OK}" = "1" ] || echo "<failure message=\"SQLiteTest failed\"/>")
</testcase>
<testcase name="PowerMangerTests" classname="ci.unit" time="${PMTESTS_TIME}">
$([ "${PMTESTS_OK}" = "1" ] || echo "<failure message=\"PowerMangerTests failed\"/>")
</testcase>
<testcase name="SmokeTest_MOOSDB_pPowerManger" classname="ci.smoke" time="${SMOKE_TIME}">
$([ "${SMOKE_OK}" = "1" ] || echo "<failure message=\"smoke test failed\"/>")
</testcase>
<testcase name="IntegrationTest_UDP_To_SQLite" classname="ci.integration" time="${INTEG_TIME}">
$([ "${INTEG_OK}" = "1" ] || echo "<failure message=\"integration test failed\"/>")
</testcase>
</testsuite>
</testsuites>
EOF
@@ -219,6 +288,6 @@ echo ">>> Reports generated in ${REPORT_DIR}:"
ls -la "${REPORT_DIR}"
#-----------------------------------------------------------------------
# 5. Exit status
# 6. Exit status
#-----------------------------------------------------------------------
[ "${ALL_OK}" = "1" ]
+84 -4
View File
@@ -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
)
+27
View File
@@ -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())
+54
View File
@@ -0,0 +1,54 @@
//=======================================================================
// udp_feeder.cpp: 集成测试用 — 向 pPowerManger 发送 CCU 状态反馈 UDP 消息。
// 直接使用真实 C++ 结构体构造消息(字节级与协议一致,无 Python 打包漂移)。
// 用法: udpFeeder [host] [port] [count]
//=======================================================================
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <thread>
#include <chrono>
#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<unsigned short>(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<unsigned char*>(&m), sizeof(m));
ssize_t n = sendto(fd, &m, sizeof(m), 0, reinterpret_cast<sockaddr*>(&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;
}
+252
View File
@@ -0,0 +1,252 @@
//=======================================================================
// DriverTest: Driver 类的设备表生成(覆盖全部 10 个工况)、设备状态读写、
// JSON 序列化/反序列化。
// 不改动生产代码;仅链接 src/pPowerManger/driver.cpp。
//=======================================================================
#include <gtest/gtest.h>
#include <cstring>
#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);
}
+45
View File
@@ -0,0 +1,45 @@
//=======================================================================
// FaultMapTest: 锂电池/配电故障码表的完整性(码值唯一、级别正确)
// 不改动生产代码;仅验证 faultCode.h 中的静态表。
//=======================================================================
#include <gtest/gtest.h>
#include <set>
#include "faultCode.h"
TEST(FaultMapTest, BatteryFaultMap)
{
const size_t n = sizeof(batfaultMap) / sizeof(batfaultMap[0]);
ASSERT_EQ(n, 54u) << "batfaultMap 应有 54 条";
std::set<uint8_t> 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<int> 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级
}
+138
View File
@@ -0,0 +1,138 @@
//=======================================================================
// PmSysvariableTest: 协议结构体 pack(1) 大小/偏移 + 枚举/宏完整性
// 不改动生产代码;检测二进制协议漂移与枚举值变化。
//=======================================================================
#include <gtest/gtest.h>
#include <cstddef>
#include <string>
#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);
}
+149
View File
@@ -0,0 +1,149 @@
//=======================================================================
// SqliteTest(gtest): 验证 SQLite 封装 10 个 insertData 写入正确。
// 不改动 SQLite 封装;用裸 sqlite3 打开同一文件读回验证。
//=======================================================================
#include <gtest/gtest.h>
#include <cstdio>
#include <string>
#include <unistd.h>
#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());
}
+203
View File
@@ -0,0 +1,203 @@
//=======================================================================
// SystemDataTest: SystemData 的故障检测逻辑(断路器映射、绝缘、电源失电、
// 漏水、DCDC、继电器位提取、缩放、故障码集合、漏水位)。
// 不改动生产代码;仅链接现有 systemData.h + SQLite + MOOS。
//=======================================================================
#include <gtest/gtest.h>
#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);
}
+111
View File
@@ -0,0 +1,111 @@
//=======================================================================
// UdpCommTest: udpComm 纯逻辑函数(校验和、消息头识别、校验解析、日期)
// 仅链接现有 udpcomm/udpComm.cpp(+ MOOS 提供 MOOSTime 等符号)。
//=======================================================================
#include <gtest/gtest.h>
#include <cstring>
#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);
}
+107
View File
@@ -0,0 +1,107 @@
//=======================================================================
// UpmsgTest: 上位机/外部通信 JSON 消息解析与序列化往返
// 仅链接现有 upmsg/*.h + upmsg_definitions.cpp。
// 注意:部分 upmsg 头文件未包含 json/loguru/pmSysvariable,需先包含。
//=======================================================================
#include <gtest/gtest.h>
#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);
}