Files
HomeAgent/internal/agent/api/codec_abimacro_test.go
JianFeeeee 8070844fe9 build(csrc): C 基础设施门禁(ABI 版本 / 告警 / sanitizer / 交叉编译 / 模糊测试)
C 化从「一把刀」推进到「可持续推进」,本轮先把基础设施建起来:
不建它,后续每个 C 切片都在裸奔(无告警门禁、无内存安全检查、
无交叉编译验证、无 ABI 漂移检测)。

新增门禁(make check-csrc / check-csrc-full,已接进 make test):
- csrc-lint   gcc+clang 双编译器 × -Wall -Wextra -Wpedantic -Wshadow
              -Wconversion,零告警才算过(-Wconversion 是为 cgo 窄化
              准备的:size_t→int 截断在默认档下是静默的)
- csrc-abi    ABI 版本运行期自述 + 荒谬值检查
- csrc-headers 头文件自包含性(每个 .h 能单独编过)
- csrc-sanitize ASan+UBSan 跑 C 契约测试
- csrc-cross  arm64 交叉编译(homed 的真实发布目标)
- csrc-fuzz   libFuzzer:内存安全 + 6 条不变式(可 CI 门禁)

新增 ABI 契约(csrc/include/ha_abi.h):
- ha_codec.h 声明「签名冻结」,但冻结只写在注释里;现改为
  HA_CODEC_ABI_MAJOR/MINOR + 运行期自述 + Go 侧常量,
  三方交叉断言,版本漂移在测试期判红而非线上表现为行为诡异。
- 门禁当场抓出我自己的两个真 bug:①_Static_assert 是 C11 而项目
  是 -std=c99(-Wpedantic 报的);②##msg 不能拼接字符串字面量,
  导致两个断言共用一个 typedef 名(clang 报的)。

基础设施当场抓出的三个真实缺陷(都是「本机 gcc 能编过、别处会炸」类):
- bench 用了 POSIX clock_gettime,而 CMake 刻意 C_EXTENSIONS OFF
  (严格 c99)→ 头文件未声明;补 _POSIX_C_SOURCE(须在任何头之前)
- 头文件缺 include 时只在「恰好被别的头先包含」处静默编过
- Go 不允许在 _test.go 用 cgo ⇒ C 侧 const 桥接只能放非测试文件,
  且 cgo 生成的 *_Cvar_* 不是 Go 常量(「两边一起错成一样」的盲区,
  改用 C 函数返回 + 编译期 _Static_assert 补上)

实测(全部当场可复现):
- 双编译器 × c99/c11 零告警;ASan+UBSan PASS
- libFuzzer 91s 跑 3329316 次、零崩溃(6 条不变式全过)
- 变异测试:改 C 侧宏 / 让 Go 常量与 C 函数「一起错成一样」,
  均被对应断言抓住(证明门禁不是摆设)
- C 侧纯函数基准(首次把函数体成本与 cgo 边界成本分开测):
  ascii_1k 121.7ns/8.4GB/s;zh_1k 1434ns;truncate 两者均约 128-134ns
- 全量 go test -count=1 ./...:57 包 0 FAIL
- make build-linux-arm64 → ELF aarch64
- 纪律检查 FAIL=0;SDK 公开接口 diff = 0 行

未动:csrc/ 是内核 C ABI,不属 third_party/homeagent-sdk 公开接口。
2026-09-26 09:44:17 +08:00

38 lines
1.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build cgo
package api
// codec_abimacro_test.go —— 堵住「Go 常量与 C 函数一起错成一样」的盲区。
//
// TestABIVersionMatches 比的是「Go 常量 vs C 函数返回值」。若有人同时把
// Go 常量和 C 函数一起改成 2(而忘了改 ha_abi.h 的宏),那条测试照样通过
// —— **两边一起错成一样**是它的盲区。本测试直接问 C 侧宏。
//
// (C 侧宏的取法在 codec_cgo.go:Go 不允许在 _test.go 里用 cgo,
// 故 const 桥接只能写在非测试文件。)
import "testing"
func TestABIMacroMatchesRuntimeAndGo(t *testing.T) {
macro := codecABIVersionMacroValue()
runtime := codecABIVersion()
if macro != runtime {
t.Fatalf("C 侧宏与运行期值不一致:\n"+
" HA_CODEC_ABI_VERSION(宏展开)= %d\n"+
" ha_codec_abi_version() = %d\n"+
" 说明:改了 ha_abi.h 的宏但没同步改 ha_codec_abi_version(),或反之。",
macro, runtime)
}
wantGo := codecABIMajorExpected*1000 + codecABIMinorExpected
if macro != wantGo {
t.Fatalf("C 侧宏与 Go 侧常量不一致:\n"+
" C 宏 HA_CODEC_ABI_VERSION = %d (major=%d minor=%d)\n"+
" Go 常量期望 = %d (major=%d minor=%d)\n"+
" 改法:同步更新 ha_abi.h 与 codec_cgo.go 的 codecABIMajor/MinorExpected。",
macro, macro/1000, macro%1000,
wantGo, wantGo/1000, wantGo%1000)
}
}