mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 16:57:32 +00:00
test(agent): 让 rt 跨进程 e2e 真正跑起来 + 策略撤销改为行为判据
## 一、e2e 一直 SKIP,等于从未验证
`TestExternalRTChild_*` 4 条判据长期 SKIP(缺插件产物)。本次用
`third_party/hologramagentsdk/example/rtvoice/build/plugin.bin`
实跑,立刻暴露两处**测试与插件不同步**:
rtvoice_inject 插件已改名为 <name>_say ⇒ 注入失败
rtvoice_dump 插件从来没有这个工具 ⇒ 观测失败
改名发生在插件侧,而判据长期不运行,所以没人发现。这正是
"SKIP 判据等于没有判据"的实证。
修法:
· 注入改用 `rtvoice-e2e_say`(插件当前唯一调试入口,走与麦克风
完全相同的 p.Push → InjectInterruptTextOpts 路径)
· 输出观测改为对 outputch 设备做一次**跨进程 RPC**(channelDevice
句柄 → 插件进程 → 拿回执),不依赖插件内部私有状态
## 二、策略撤销判据从源码扫描改为行为断言
`TestChildTeardownRevokesPolicy` 原先扫源码文本,找停止函数体里有没有
`revokeChildPolicy`。R2/R3 把撤销移进骨架 Cleanup 后文本匹配失效 ——
但行为是对的,判据自己变红。
这暴露出它抓不住的真实风险:`StopDeps.RevokePolicy` 是**注入**的,
漏填时 Cleanup 里的 nil 检查会静默跳过,而字符串仍在别处,源码扫描照样绿。
现在分两层:
· kernelmod/all 保留源码断言,但钉**两个环节成对**:
① Cleanup 真的调用 deps.RevokePolicy
② 各类型真的注入它
并增加"单数与复数必须共用骨架 Stop"的结构护栏
· agent/core 新增行为判据 childpolicy_test.go:
声明策略 → 走真实停止路径 → 主体表里必须消失
## 三、验证
go build ./... PASS
go vet ./... PASS
gofmt -l 干净
go test ./... -count=1 62 包全过,0 FAIL
go test ./... -count=1 -race 0 FAIL
rt 跨进程 e2e(4 条,不再 SKIP):
TestExternalRTChild_FullChain PASS(rtchild 跑了 1 轮)
TestExternalRTChild_ReuseDoesNotLeakTranscript PASS
TestExternalRTChild_BulkStopUnregistersInbound PASS
TestExternalRTChild_RepeatedCyclesDoNotAccumulate PASS
变异验证:断开 routeInputByOwner 的 rt 分支 ⇒
FullChain 立即红,并准确报出"输入没有到达 rtSlot"
干净检出(git worktree)单独 build + vet + test + e2e PASS
This commit is contained in:
@ -298,32 +298,87 @@ func TestParentDeclaresChildPolicy(t *testing.T) {
|
||||
// 这条单列,因为本仓反复吃亏在"单数路径改了、复数路径漏了":
|
||||
// `StopRTChild` 注销入站登记而 `StopRTChildren` 漏了,就是这么来的。
|
||||
//
|
||||
// 用源码断言:真销毁需要完整记忆栈/rt 通道,而"前置不满足就 skip"
|
||||
// 的判据等于没有(本仓已有同型先例:TestHiddenDependencyOnnxDeclared)。
|
||||
// ## 2026-11-04 适配:撤销动作已移入骨架
|
||||
//
|
||||
// R2/R3 把"撤销策略"从两个停止函数搬进了 `child.Registry.Cleanup`,
|
||||
// 由各类型注入的 `StopDeps.RevokePolicy` 触发。于是判据需要钉**两个环节**:
|
||||
//
|
||||
// ① 骨架真的会调用注入的 RevokePolicy(否则注入等于没接)
|
||||
// ② 各类型真的注入了它(否则骨架有地方调、但没人填)
|
||||
//
|
||||
// 只钉 ①:注入漏填时永远绿。
|
||||
// 只钉 ②:骨架不调时永远绿。
|
||||
//
|
||||
// 两个环节都缺失才是真实缺口,所以必须**成对**断言。
|
||||
//
|
||||
// 行为版判据放在 `internal/agent/core`(那里有真实脚手架),
|
||||
// 本处保留源码级断言,因为它能跨包检查"装配是否被人改掉"。
|
||||
func TestChildTeardownRevokesPolicy(t *testing.T) {
|
||||
cases := []struct{ file, fn, why string }{
|
||||
// ① 骨架:Cleanup 必须调用 deps.RevokePolicy。
|
||||
//
|
||||
// 变红条件:删掉 registry.go 里那两行 ⇒ 红。
|
||||
reg := readMod(t, "../../agent/core/child/registry.go")
|
||||
ci := strings.Index(reg, "func (r *Registry) Cleanup(")
|
||||
if ci < 0 {
|
||||
t.Fatal("找不到 Registry.Cleanup —— 符号改名了?判据需要跟着改")
|
||||
}
|
||||
cleanupBody := reg[ci:]
|
||||
if end := strings.Index(cleanupBody, "\n}\n"); end > 0 {
|
||||
cleanupBody = cleanupBody[:end]
|
||||
}
|
||||
if !strings.Contains(cleanupBody, "deps.RevokePolicy") {
|
||||
t.Error("★ Cleanup 没有调用 deps.RevokePolicy —— 子策略永远不会被撤销。\n" +
|
||||
" 后果:主体表以调用方给的名字为键,回收后同名重建会继承\n" +
|
||||
" 上一代父留下的约束(而那个父可能已经不在了),且表单调增长。")
|
||||
}
|
||||
|
||||
// ② 各类型:必须往 StopDeps 里填 RevokePolicy。
|
||||
//
|
||||
// rt 走 children.StopOne/StopAll ⇒ 由 rtStopDeps 提供。
|
||||
// 驻留子走自己的 teardownResidentWith ⇒ 那里直接调。
|
||||
cases := []struct{ file, anchor, needle, why string }{
|
||||
{"../../agent/core/rtchild.go", "func (a *Agent) rtStopDeps(",
|
||||
"RevokePolicy: a.revokeChildPolicy",
|
||||
"rtchild 的 StopDeps 必须注入撤销动作"},
|
||||
{"../../agent/core/resident.go", "func (a *Agent) teardownResidentWith(",
|
||||
"revokeChildPolicy",
|
||||
"驻留子销毁必须撤销父为它声明的策略"},
|
||||
{"../../agent/core/rtchild.go", "func (a *Agent) StopRTChild(",
|
||||
"rtchild 单数停止必须撤销策略"},
|
||||
{"../../agent/core/rtchild.go", "func (a *Agent) StopRTChildren(",
|
||||
"rtchild 批量停止必须与单数路径对称地撤销策略"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
src := readMod(t, c.file)
|
||||
i := strings.Index(src, c.fn)
|
||||
i := strings.Index(src, c.anchor)
|
||||
if i < 0 {
|
||||
t.Fatalf("%s 里找不到 %s —— 符号改名了?判据需要跟着改", c.file, c.fn)
|
||||
t.Fatalf("%s 里找不到 %s —— 符号改名了?判据需要跟着改", c.file, c.anchor)
|
||||
}
|
||||
body := src[i:]
|
||||
if end := strings.Index(body, "\n}\n"); end > 0 {
|
||||
body = body[:end]
|
||||
}
|
||||
if !strings.Contains(body, "revokeChildPolicy") {
|
||||
t.Errorf("★ %s 里没有 revokeChildPolicy —— %s。\n"+
|
||||
if !strings.Contains(body, c.needle) {
|
||||
t.Errorf("★ %s 里没有 %q —— %s。\n"+
|
||||
" 后果:主体表以调用方给的名字为键,回收后同名重建会继承\n"+
|
||||
" 上一代父留下的约束(而那个父可能已经不在了),且表单调增长。",
|
||||
c.fn, c.why)
|
||||
" 上一代父留下的约束,且表单调增长。", c.anchor, c.needle, c.why)
|
||||
}
|
||||
}
|
||||
|
||||
// ③ 单数与复数必须**共用同一条骨架路径**(这是"对称"的结构保证)。
|
||||
//
|
||||
// 变红条件:StopRTChildren 改回自己写一遍循环 + 撤销 ⇒ 红。
|
||||
rt := readMod(t, "../../agent/core/rtchild.go")
|
||||
for _, fn := range []string{
|
||||
"func (a *Agent) StopRTChild(", "func (a *Agent) StopRTChildren(",
|
||||
} {
|
||||
i := strings.Index(rt, fn)
|
||||
if i < 0 {
|
||||
t.Fatalf("找不到 %s", fn)
|
||||
}
|
||||
body := rt[i:]
|
||||
if end := strings.Index(body, "\n}\n"); end > 0 {
|
||||
body = body[:end]
|
||||
}
|
||||
if !strings.Contains(body, "a.children.Stop") {
|
||||
t.Errorf("★ %s 没走骨架的 StopOne/StopAll —— 单数与复数各写一遍时,"+
|
||||
"必然出现\"改一处漏一处\"(engineering-lessons §2.3)", fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user