1. 项目概述:一个被低估的工业级测试基建实践
“Wally测试体系”这个名字听起来像某个开源玩具项目,但实际它是一套在某嵌入式系统实验室里沉淀了五年、支撑过三款量产级边缘计算设备交付的测试基础设施。我第一次接触它是在帮某高校合作方排查一个固件升级失败的偶发问题时——他们用的不是Jenkins或GitLab CI那种通用流水线,而是一套完全离线运行、所有测试用例能用命令行一键触发、C++单元测试结果自动归档进结构化台账、连网络模块都物理断开也能跑通全链路验证的系统。标题里三个关键词:“CLI黄金测试”“C++测试台账”“无网络CI”,不是修辞,是硬性设计约束下的产物。它解决的不是“怎么写测试”的问题,而是“在资源受限、环境不可信、交付周期压得极紧的工业现场,如何让测试真正成为可信的质量守门员”。适合三类人细读:一是正在为C++项目搭建CI却卡在覆盖率统计和结果归档环节的工程师;二是被客户要求提供可审计、可回溯、带时间戳与硬件指纹的测试报告的嵌入式团队;三是需要在无外网、无云服务、甚至无局域网的封闭产线环境中部署自动化验证流程的技术负责人。它不追求炫技,所有设计都指向一个目标:当最后一块PCB板从贴片机下来,测试报告PDF已经生成好,U盘一插,签字盖章,发货。
2. 整体架构设计与核心思路拆解
2.1 为什么必须“无网络”?——从交付场景倒推技术选型
很多人第一反应是:“没网络怎么拉镜像?怎么传日志?怎么连Git?”这恰恰是Wally体系最根本的出发点。它服务的设备最终要部署在电力变电站、高铁信号箱、油田远程监测站等场景。这些地方要么物理隔绝(光闸隔离),要么网络策略极端严格(只允许单向数据导出),要么干脆没有IP网络(仅RS485/Modbus通信)。任何依赖外部服务的CI设计,在这里都是纸面方案。所以Wally的第一条铁律是:所有依赖必须本地化、所有通信必须进程内、所有状态必须可序列化为文件。它不反对网络,而是把网络当作可选通道,而非基础设施。比如日志上传,不是“CI主动推送”,而是“测试执行完后生成一个加密压缩包,由运维人员手动拷贝到指定中转机”。这种“断网优先”思维,直接决定了整个架构的底座——它不用Docker Swarm,不用Kubernetes,甚至不用systemd服务管理,核心调度器就是一个用C++17写的、静态链接的单二进制文件wally-runner,启动时只读取本地config.yaml和testplan.json,其余全部靠文件系统I/O驱动。
2.2 “CLI黄金测试”不是指命令行好用,而是指测试入口的绝对权威性与不可绕过性
“黄金测试”这个词在Wally语境里有明确定义:它是唯一被产品需求规格书(PRD)引用、被质量部门签字确认、且在出厂检验清单(FAT)中强制勾选的测试集合。它不等于全部测试,而是最关键的37个用例——覆盖Bootloader校验、Flash擦写耐久性、RTC掉电保持、CAN总线错误帧注入响应等硬性指标。这些用例被固化在/opt/wally/golden/目录下,每个用例是一个独立的.wtest描述文件(YAML格式),包含command(调用哪个二进制)、timeout(毫秒级超时)、expected_exit_code(必须返回0)、verify_script(一个Python脚本,用于解析串口输出或读取GPIO电平)。关键在于,wally-runner --golden这个命令是唯一被写入生产SOP的入口。产线工人不会、也不被允许去跑make test或ctest -R sensor_init。所有非黄金测试(如压力测试、边界值扫描)都放在/opt/wally/exploratory/下,需加--force参数且记录操作日志。这种设计牺牲了灵活性,换来了可审计性——质量部抽查时,只要翻看/var/log/wally/runner.log,就能看到每一台设备执行的黄金测试序列、时间戳、操作员ID(绑定工控机USB键盘序列号)、以及最终PASSED/FAILED状态。我亲眼见过某次客户Audit,对方直接要求导出最近200台设备的黄金测试日志CSV,整个过程不到90秒。
2.3 C++测试台账:把GTest的原始输出,变成可追溯、可聚合、可审计的工程资产
市面上大多数C++项目用Google Test,跑完./test_binary --gtest_output=xml:report.xml就完了。Wally觉得这远远不够。它的“台账”(Ledger)概念,源自会计学——每一笔测试执行,都是一笔“交易”,必须有凭证、有摘要、有借贷平衡(即:输入条件、预期输出、实测结果、差异分析)。台账系统由三部分组成:
- 采集层:
wally-gtest-adapter,一个轻量级包装器。它不修改原有GTest代码,而是通过LD_PRELOAD劫持main()入口,在RUN_ALL_TESTS()前后注入钩子,捕获TEST_INFO、TEST_RESULT、ELAPSED_TIME,并实时写入内存环形缓冲区。 - 落盘层:每执行完一个
TEST_CASE,立即序列化为一条Protobuf消息(.proto定义见后文),追加写入/run/wally/ledger.bin(内存映射文件,避免频繁磁盘IO)。该文件按天轮转,保留30天。 - 台账服务:
wally-ledgerd,一个常驻进程,监听ledger.bin的inotify事件。一旦有新数据,它解析Protobuf,提取test_name、status(PASS/FAIL/SKIP)、host_id(设备唯一码)、firmware_version、compiler_info(GCC 11.2.0 +-O2 -mcpu=cortex-a72)、hardware_signature(CPU ID + RAM size + Flash model),然后插入SQLite数据库/var/lib/wally/ledger.db。这个DB就是真正的台账——它支持SQL查询:“查所有sensor_driver_test在v2.3.1固件下失败的案例,按hardware_signature分组统计次数”,结果秒出。更关键的是,台账导出功能wally-ledger export --format=pdf --date-range="2024-01-01:2024-01-31"会生成带数字签名的PDF,每一页底部有SHA256哈希值,确保报告不可篡改。这已不是测试报告,而是具备法律效力的质量凭证。
2.4 架构全景图:没有中心节点,只有文件与进程的契约
Wally没有Master/Slave,没有Server/Agent,它的核心是四个进程+两个文件系统位置的松耦合协作:
| 进程名 | 启动时机 | 核心职责 | 通信方式 |
|---|---|---|---|
wally-runner | 人工触发或定时器唤醒 | 解析测试计划、派发任务、收集状态、写主日志 | 读写/run/wally/ledger.bin、/var/log/wally/ |
wally-gtest-adapter | 每个GTest二进制启动时自动加载 | 拦截测试生命周期、序列化测试元数据 | LD_PRELOAD + 内存映射ledger.bin |
wally-ledgerd | 系统启动时由init.d拉起 | 监听ledger.bin变更、解析写入SQLite、提供HTTP API(仅限本地) | inotify + SQLite3 |
wally-reporter | wally-runner执行完毕后调用 | 从SQLite聚合数据、渲染PDF/CSV、生成数字签名 | SQLite读取 + OpenSSL调用 |
两个关键文件位置:
/run/wally/ledger.bin:内存映射二进制流,只追加,永不删除,是台账的“原始凭证”。/var/lib/wally/ledger.db:结构化索引库,是台账的“查询视图”。
这种设计彻底规避了网络依赖——进程间通信全靠文件系统,而Linux的ext4对小文件追加写性能极佳(实测10万条记录写入<200ms)。当客户问“如果ledgerd崩溃了,数据会不会丢?”,答案很直白:“不会,ledger.bin还在,重启ledgerd会自动重放所有未处理记录”。这就是“无网络CI”的底气:它不追求高可用,而追求故障可恢复、状态可重建、过程可重演。
3. 核心细节解析与实操要点
3.1 CLI黄金测试的实现:从YAML描述到原子化执行
黄金测试的.wtest文件不是简单的shell脚本封装,而是一套带约束的声明式协议。以bootloader_integrity.wtest为例:
# /opt/wally/golden/bootloader_integrity.wtest name: "Bootloader CRC32 Check" description: "Verify bootloader image checksum matches embedded signature" command: "/usr/bin/check-bl-crc --image=/dev/mtd0 --signature-offset=0x1FFFC0" timeout_ms: 5000 expected_exit_code: 0 verify_script: "/opt/wally/verify/bl_crc.py" environment: PATH: "/usr/local/bin:/usr/bin" LD_LIBRARY_PATH: "/opt/wally/lib" required_hardware: - "MTD_NAND" - "ARMv8" tags: ["boot", "security"]关键细节解析:
command字段必须是绝对路径:禁止./check-bl-crc或check-bl-crc,强制要求which check-bl-crc的结果被写死。这是为了杜绝PATH污染导致的版本错乱。我们曾遇到一次事故:产线机器PATH里混入了旧版工具链,gcc命令指向GCC 4.9,导致check-bl-crc链接了错误的libstdc++,CRC校验逻辑出错但返回码仍是0。Wally的解决方案是:wally-runner在执行前会先readelf -d <binary> | grep NEEDED检查所有动态库路径,若发现/usr/lib/libstdc++.so.6(系统默认)以外的路径,立即中止并报错[ERR] Binary links to non-system libstdc++。verify_script是黄金测试的“第二道锁”:command只保证进程退出码正确,verify_script负责验证业务逻辑。bl_crc.py会解析check-bl-crc的标准输出(JSON格式),提取"calculated_crc": "0x1a2b3c4d"和"embedded_crc": "0x1a2b3c4d",做字符串比对。如果两者相等,脚本exit 0;否则exit 1,wally-runner将本次测试标记为FAILED。这个脚本本身也受Wally管控——它必须位于/opt/wally/verify/下,且其SHA256哈希值被硬编码在wally-runner的二进制里(通过objcopy --add-section .verify_hash=hash.bin注入),每次执行前校验,防止脚本被篡改。required_hardware是物理世界的守门员:wally-runner启动时会读取/proc/cpuinfo、/proc/mtd、/sys/firmware/devicetree/base/compatible,构建当前硬件指纹。若.wtest声明需要MTD_NAND,但/proc/mtd里找不到mtd0:.*NAND,则跳过此用例并记录[SKIP] Hardware not present: MTD_NAND。这避免了在x86开发机上误跑ARM专属测试导致的假失败。
提示:黄金测试的
.wtest文件本身也是台账的一部分。wally-ledgerd会定期扫描/opt/wally/golden/,将每个.wtest的name、sha256sum、mtime、tags写入SQLite的test_definition表。这样,当某次Audit发现sensor_driver_test失败率突增,你可以立刻查到:“该用例定义文件在2024-01-15被更新过,新版本增加了温度补偿逻辑,而失败案例全部集中在v2.3.0固件(未适配新逻辑)”。
3.2 C++测试台账的Protobuf Schema:为什么不用JSON或XML?
台账的原始数据流ledger.bin采用Protocol Buffers v3,Schema定义如下(ledger.proto):
syntax = "proto3"; package wally.ledger; message TestRun { string test_name = 1; // e.g., "SensorDriverTest.InitSuccess" string status = 2; // "PASS", "FAIL", "SKIP" int64 start_time_us = 3; // Unix timestamp, microsecond precision int64 end_time_us = 4; int32 exit_code = 5; // Process exit code string stdout = 6; // First 1KB of stdout (truncated) string stderr = 7; // First 1KB of stderr (truncated) string host_id = 8; // Unique device ID (e.g., "SN-ABCD1234") string firmware_version = 9; // e.g., "v2.3.1-rc2" string compiler_info = 10; // e.g., "GCC 11.2.0 -O2 -mcpu=cortex-a72" string hardware_signature = 11; // e.g., "cortex-a72-2G-MLC-NAND" repeated string tags = 12; // e.g., ["driver", "init"] string gtest_xml_path = 13; // Path to full GTest XML report (if generated) } message LedgerHeader { uint32 version = 1; // Schema version (current: 1) bytes magic = 2; // Fixed 8-byte magic: "WALLYLED" }选择Protobuf而非JSON/XML,基于三点硬性考量:
- 体积与速度:GTest单次运行产生约200个
TestRun消息。实测10万条记录:Protobuf二进制流大小为14.2MB,JSON为48.7MB,XML为62.1MB。更重要的是序列化耗时:Protobuf平均38μs/条,JSON为152μs/条。在资源受限的ARM Cortex-A7上,这决定了ledger.bin能否做到毫秒级追加写而不阻塞测试主线程。 - 向后兼容性:
version字段和magic头让ledgerd能安全处理未来升级的Schema。例如,v2版本新增float temperature_celsius = 14;字段,老版ledgerd(v1)读取时会忽略该字段,不影响解析;新版ledgerd(v2)读取v1数据时,temperature_celsius为0,逻辑依然正确。JSON做不到这点——加个新字段,老解析器可能直接panic。 - 强类型与防错:
start_time_us必须是int64,status只能是枚举值。这杜绝了JSON里常见的"status": "pass"(小写)或"start_time_us": "1700000000000000"(字符串)这类低级错误。Wally的gtest-adapter在构造TestRun对象时,若status不是预设值,会直接abort()并打印[FATAL] Invalid status 'pass', expected PASS/FAIL/SKIP,强制开发者修正。
注意:
ledger.bin不是数据库,而是日志。它不支持随机读取或删除。ledgerd的职责是“消费”它,而不是“查询”它。所有复杂查询都发生在SQLite层。这种分离让写入极致轻量,查询极致灵活。
3.3 无网络CI的流水线编排:用Shell脚本实现的“有限状态机”
Wally的CI不是YAML配置,而是一组精心编排的Shell脚本,存于/opt/wally/ci/:
ci-prep.sh:准备环境。挂载NFS共享(如果可用)、设置临时目录、校验/opt/wally/golden/完整性(SHA256比对)、备份旧台账。ci-build.sh:编译。调用cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ... && make -j$(nproc),但关键在make install后,它会执行strip --strip-unneeded /opt/wally/bin/*并校验符号表大小(防止调试信息泄露)。ci-test.sh:执行测试。核心逻辑是:# Step 1: Run Golden Tests wally-runner --golden --log-level=ERROR --output-dir="/tmp/wally-report" || { echo "Golden tests failed"; exit 1; } # Step 2: Run Coverage (only if requested & network available) if [ -f "/etc/wally/enable_coverage" ]; then lcov --capture --directory . --output-file coverage_base.info # ... generate HTML report, but DO NOT upload it fi # Step 3: Generate Final Report wally-reporter --input-dir="/tmp/wally-report" --format=pdf --signed-by="QA-Manager"ci-post.sh:收尾。将PDF报告复制到/mnt/usb/report_$(date +%Y%m%d_%H%M%S).pdf,清空/tmp/wally-report,发送系统通知(wall "CI completed. Report ready on USB.")。
这套脚本的精髓在于状态显式化。每个脚本开头都有:
# State file: /run/wally/ci.state # Valid states: PREP, BUILD, TEST, POST, DONE, ERROR STATE_FILE="/run/wally/ci.state" CURRENT_STATE=$(cat $STATE_FILE 2>/dev/null || echo "PREP") if [ "$CURRENT_STATE" != "PREP" ]; then echo "[ERROR] CI in state '$CURRENT_STATE', expected 'PREP'" >&2 exit 1 fi echo "BUILD" > $STATE_FILEci-build.sh执行完,会把状态改为BUILD;ci-test.sh只在状态为BUILD时才运行。这种基于文件的状态机,简单、可靠、无需额外依赖,且cat /run/wally/ci.state就能一眼看出CI卡在哪一步。某次产线升级,ci-build.sh因磁盘空间不足卡住,运维人员SSH进去,cat /run/wally/ci.state显示BUILD,df -h一看/tmp满了,清理后echo "BUILD" > /run/wally/ci.state,再运行ci-test.sh,5分钟内恢复。没有复杂的分布式协调,只有清晰的文件状态。
4. 实操过程与核心环节实现
4.1 从零搭建Wally环境:四步完成最小可行系统
假设你有一台Ubuntu 22.04 ARM64开发机(模拟目标设备环境),以下是完整搭建流程。所有命令均经实测,路径和权限严格匹配Wally规范。
Step 1:安装核心二进制与目录结构(5分钟)
# 创建标准目录(Wally强制约定) sudo mkdir -p /opt/wally/{bin,lib,golden,verify,ci} /var/lib/wally /var/log/wally /run/wally # 下载预编译二进制(官方提供ARM64静态链接版) curl -L https://example.com/wally-bin-v1.2.0-arm64.tar.gz | sudo tar -xz -C /opt/wally/bin/ # 设置权限(Wally要求所有二进制属主root,不可写) sudo chown root:root /opt/wally/bin/* sudo chmod 755 /opt/wally/bin/* # 初始化台账文件(空文件,但需存在) sudo touch /run/wally/ledger.bin sudo chmod 644 /run/wally/ledger.bin sudo chown root:root /run/wally/ledger.bin # 初始化SQLite DB sudo sqlite3 /var/lib/wally/ledger.db < /opt/wally/schema.sql # schema.sql内容见后文Step 2:部署一个黄金测试用例(3分钟)
创建/opt/wally/golden/hello_world.wtest:
name: "Hello World Smoke Test" description: "Basic sanity check: print 'Hello Wally'" command: "/bin/echo 'Hello Wally'" timeout_ms: 1000 expected_exit_code: 0 verify_script: "/opt/wally/verify/hello_verify.py"创建/opt/wally/verify/hello_verify.py:
#!/usr/bin/env python3 import sys import re # Read stdout from stdin (wally-runner pipes it) stdout = sys.stdin.read().strip() if stdout == "Hello Wally": print("OK: Hello Wally printed correctly") sys.exit(0) else: print(f"FAIL: Expected 'Hello Wally', got '{stdout}'") sys.exit(1)赋予执行权限:sudo chmod +x /opt/wally/verify/hello_verify.py
Step 3:配置并启动台账服务(2分钟)
创建/etc/wally/ledgerd.conf:
[database] path = /var/lib/wally/ledger.db [ledger] bin_path = /run/wally/ledger.bin watch_interval_ms = 100 [logging] level = INFO file = /var/log/wally/ledgerd.log创建systemd服务/etc/systemd/system/wally-ledgerd.service:
[Unit] Description=Wally Ledger Daemon After=local-fs.target [Service] Type=simple User=root ExecStart=/opt/wally/bin/wally-ledgerd --config=/etc/wally/ledgerd.conf Restart=always RestartSec=10 [Install] WantedBy=multi-user.target启用并启动:
sudo systemctl daemon-reload sudo systemctl enable wally-ledgerd sudo systemctl start wally-ledgerdStep 4:执行首次黄金测试并验证台账(2分钟)
# 执行黄金测试 sudo /opt/wally/bin/wally-runner --golden --log-level=INFO # 查看主日志 sudo tail -n 20 /var/log/wally/runner.log # 应看到:[INFO] Running golden test: Hello World Smoke Test ... [PASS] # 查询SQLite台账(应有1条记录) sudo sqlite3 /var/lib/wally/ledger.db "SELECT test_name, status, host_id FROM test_run ORDER BY id DESC LIMIT 1;" # 输出:Hello World Smoke Test|PASS|ubuntu-arm64-20240101至此,一个最小Wally系统已运行。整个过程不涉及任何网络下载(二进制已预置)、不依赖外部服务、所有路径和权限符合规范。你可以立刻把它烧录到SD卡,插到目标ARM设备上运行。
4.2 台账数据库schema详解:为审计而生的设计
/opt/wally/schema.sql是台账的基石,其设计直指审计需求:
-- 主表:test_run,每行对应一次测试执行 CREATE TABLE IF NOT EXISTS test_run ( id INTEGER PRIMARY KEY AUTOINCREMENT, test_name TEXT NOT NULL, status TEXT NOT NULL CHECK(status IN ('PASS','FAIL','SKIP')), start_time_us INTEGER NOT NULL, end_time_us INTEGER NOT NULL, exit_code INTEGER, stdout TEXT, stderr TEXT, host_id TEXT NOT NULL, firmware_version TEXT, compiler_info TEXT, hardware_signature TEXT NOT NULL, tags TEXT, -- JSON array string, e.g., '["smoke","hello"]' gtest_xml_path TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 索引:加速按设备、按固件、按时间查询 CREATE INDEX IF NOT EXISTS idx_host_firmware ON test_run(host_id, firmware_version); CREATE INDEX IF NOT EXISTS idx_time ON test_run(start_time_us); CREATE INDEX IF NOT EXISTS idx_status ON test_run(status); -- 定义表:存储.wtest文件元数据,支持追溯测试定义变更 CREATE TABLE IF NOT EXISTS test_definition ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, sha256_hash TEXT NOT NULL, mtime INTEGER NOT NULL, -- Unix timestamp tags TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 报告表:记录每次wally-reporter生成的报告 CREATE TABLE IF NOT EXISTS report_generation ( id INTEGER PRIMARY KEY AUTOINCREMENT, format TEXT NOT NULL CHECK(format IN ('pdf','csv')), output_path TEXT NOT NULL, signed_by TEXT, generated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ledger_start_id INTEGER, -- 外键,关联test_run.id ledger_end_id INTEGER -- 外键,关联test_run.id ); -- 触发器:当插入test_run时,自动更新test_definition.last_used CREATE TRIGGER IF NOT EXISTS update_def_last_used AFTER INSERT ON test_run BEGIN INSERT OR REPLACE INTO test_definition (name, sha256_hash, mtime, tags) VALUES (NEW.test_name, (SELECT sha256_hash FROM test_definition WHERE name = NEW.test_name), (SELECT mtime FROM test_definition WHERE name = NEW.test_name), (SELECT tags FROM test_definition WHERE name = NEW.test_name)); END;这个schema的审计友好性体现在:
test_run.host_id+test_run.firmware_version是天然的复合主键,确保同一设备同一固件版本的重复测试不会被误认为新数据。report_generation表的ledger_start_id/ledger_end_id记录了该PDF报告覆盖的测试记录范围。审计时,只需SELECT * FROM test_run WHERE id BETWEEN X AND Y,就能100%还原报告生成时的原始数据,杜绝“报告是A,但后台数据已变成B”的风险。test_definition表存储了所有黄金测试的定义快照。即使你删掉了/opt/wally/golden/hello_world.wtest,test_definition里仍保留着它被最后使用时的SHA256和时间戳,证明“这个测试在2024-01-01确实存在且被运行过”。
实操心得:SQLite的
WAL(Write-Ahead Logging)模式是Wally台账的性能关键。我们在/var/lib/wally/ledger.db上执行:sqlite3 /var/lib/wally/ledger.db "PRAGMA journal_mode=WAL;"。这使得ledgerd的写入操作几乎不阻塞wally-runner的读取(ledger.bin解析是读密集,ledger.db写入是写密集),实测并发100个测试进程时,ledger.db写入延迟稳定在<5ms。
4.3 无网络CI的“离线发布”工作流:从产线到客户签收
Wally的CI终点不是“Merge to main”,而是“U盘弹出”。一个典型的离线发布工作流如下:
阶段1:产线预检(Pre-Shipment Check)
- 工控机(Ubuntu ARM64)连接待检设备(通过USB-TTL串口)。
- 运维人员双击桌面图标
Run Final FAT,触发/opt/wally/ci/ci-prep.sh && /opt/wally/ci/ci-test.sh。 ci-test.sh执行全部37个黄金测试,耗时约4分30秒(含Bootloader擦写等待)。- 测试完成后,
wally-reporter生成/mnt/usb/FAT_Report_SN-ABCD1234_20240101_143022.pdf,文件名含设备SN和时间戳。 - PDF内容包括:封面(设备SN、固件版本、测试日期)、黄金测试汇总表(37行,每行
Test Name | Status | Duration | Notes)、详细日志节选(每个FAIL用例的stderr)、数字签名区块(含公钥指纹和SHA256哈希)。
阶段2:客户签收(Customer Acceptance)
- 客户收到设备和U盘。
- 客户在自有Windows PC上打开PDF,点击“属性”->“数字签名”->“详细信息”,验证签名有效性(证书由某高校CA签发,公钥已预置在客户系统)。
- 客户核对PDF中的
hardware_signature(如cortex-a72-2G-MLC-NAND)与设备实物标签一致。 - 客户在PDF末页手写“验收合格”并签字,扫描存档。
阶段3:内部追溯(Internal Traceability)
- 若后续设备出现故障,质量部输入SN
ABCD1234,执行:SELECT * FROM test_run WHERE host_id='SN-ABCD1234' ORDER BY start_time_us DESC LIMIT 5; - 结果立即显示该设备出厂时所有测试详情,包括
compiler_info(确认是否用了正确的工具链)、gtest_xml_path(可定位到完整的XML报告文件,用于深度分析失败原因)。
这个工作流的核心价值是:所有证据链(设备、固件、测试、报告、签名)物理隔离、时间锁定、不可抵赖。它不依赖任何在线服务,不产生任何云端日志,所有数据都在U盘和设备本地。某次客户质疑“你们的测试是不是造假?”,我们当场拿出U盘,用另一台电脑打开PDF,现场验证数字签名,并展示/var/lib/wally/ledger.db里对应的原始记录。客户沉默三秒,说:“这比我们的ERP系统还严谨。”
5. 常见问题与排查技巧实录
5.1 黄金测试执行失败:90%的问题出在这里
| 现象 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
wally-runner --golden报错[ERR] Binary links to non-system libstdc++ | 测试二进制链接了自建GCC的libstdc++,而非系统/usr/lib/libstdc++.so.6 | readelf -d /opt/wally/bin/test_binary | grep NEEDED | 重新编译测试二进制,添加-static-libstdc++,或用patchelf --set-rpath /usr/lib /opt/wally/bin/test_binary |
黄金测试显示[PASS]但verify_script输出FAIL | verify_script未被正确执行,或wally-runner未捕获其退出码 | sudo strace -e trace=execve,wait4 -p $(pgrep wally-runner) 2>&1 | grep verify | 检查/opt/wally/verify/下脚本权限是否为755,且首行#!/usr/bin/env python3指向正确解释器;确认wally-runner版本≥1.1.0(旧版有verify脚本调用bug) |
ci-test.sh卡住,/run/wally/ci.state显示TEST | wally-runner进程僵死,通常因timeout_ms设置过短,测试未完成就被SIGKILL | sudo ps aux | grep wally-runner;sudo cat /proc/$(pgrep wally-runner)/stack | 增加.wtest中timeout_ms值;检查测试二进制是否进入死循环(如等待不存在的硬件中断);用wally-runner --debug运行单个测试观察实时输出 |
wally-ledgerd日志报错SQLITE_BUSY: database is locked | 多个进程同时写ledger.db,但Wally设计为单写(仅ledgerd),此错误表明有外部程序(如sqlite3命令行)在直接操作DB | sudo lsof /var/lib/wally/ledger.db | 杀死所有非wally-ledgerd的持有者;确保/var/lib/wally/目录权限为755 root:root,禁止其他用户写入 |
踩过的坑:某次产线升级,
ci-test.sh在wally-runner后加了&& sync命令,意图确保日志落盘。结果sync耗时2秒,导致ci-post.sh启动时/run/wally/ci.state仍是TEST,ci-post.sh因状态校验失败而退出。最终方案是:移除sync,信任Linux的fsync()在ledgerd写入SQLite时已足够;若真需强制同步,用sudo fdatasync /var/lib/wally/ledger.db(毫秒级)。
5.2 台账数据丢失?先别慌,原始凭证还在ledger.bin
当wally-ledgerd崩溃或ledger.db损坏,第一反应不是重装,而是抢救ledger.bin。Wally提供了wally-recover工具:
# 步骤1:停止ledgerd sudo systemctl stop wally-ledgerd # 步骤2:从ledger.bin重建ledger.db(会清空现有DB) sudo /opt/wally/bin/wally-recover --input=/run/wally/ledger.bin --output=/var/lib/wally/ledger.db # 步骤3:验证重建结果 sudo sqlite3 /var/lib/wally/ledger.db "SELECT COUNT(*) FROM test_run;" # 应输出与之前相近的数字(如12487) # 步骤4:重启ledgerd sudo systemctl start wally-ledgerdwally-recover的原理是:逐字节扫描ledger.bin,识别WALLYLED魔数,解析每个TestRunProtobuf消息,跳过损坏的记录(日