mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-01 14:57:38 +00:00
Build() 里已有构建期校验 diagnoseUnresolvable(查声明:缺依赖/环/重名),
本单**不重复**它。缺的是执行期那一层:声明全对、图也自洽时,plan 的层顺序
仍可能被改坏(手改 Layers / 图与代码漂移 / 有人把某层提前),此时 Build 绿,
模块 Init 拿到 nil 依赖,崩在业务代码里。
实测复现(本单判据的复现形状):把 kernelmod 真实登记表算出的第 1 层
(lua/memory/onnx-parser/…)与第 2 层(llm/text-memory/knowledge-store)交换,
llm.buildProviderManager 收到 0x0 的 LuaVM:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV ... addr=0x10]
internal/lua.(*VM).ConfigureConcurrency(0x0, ...) vm.go:213
internal/kernelmod/llm.buildProviderManager(..., 0x0, ...) mod.go:70
改动:Run 逐层维护 produced(**只有真跑完的层**才贡献设施,刻意不从 plan 预填),
每层 Init **之前**核对本层各模块 Requires ⊆ produced;缺则返回
*DependencyNotReadyError 并**本层一个 Init 都不调**(宁可什么都不装,也不装一半再崩)。
错误文本指名「第 N 层 / 模块 X 缺设施 [...]」并列已实际产出的设施。
判据:
internal/kernelmod/all/runtime_dep_test.go —— 用真实登记表算 plan 后做变异
A 正序前缀不误报
B 交换两层 ⇒ 判据红(指名 llm 缺 lua / text-memory 缺 memory,onnx /
knowledge-store 缺 knowledge-migrated),且 ks.LuaVM 仍为 nil(没跑到 llm)
C 删掉提供者整层 ⇒ 第 0 层就拒绝,IO/EventBus 仍 nil(一个模块都没跑)
internal/kernelboot/runtimedep_test.go —— 纯逻辑四形状 + 防 M2 自检
变异证明(4 个,逐个人工注入后跑判据,全部咬住):
M0 撤掉守卫 → 复现原 SIGSEGV(ConfigureConcurrency(0x0))
M1 守卫恒放行 → 5 条判据红
M2 produced 从 plan 预填 → 退化成 Build 重复实现,顺序类判据红
M3 报错丢模块名 → 指名类判据红
修前红/修后绿:
修前(M0):panic SIGSEGV @ lua.(*VM).ConfigureConcurrency(0x0)
修后:DependencyNotReadyError,文本含「第 1 层」「llm」「lua」「缺设施」
go build ./... = 0 error,go vet ./... = 0 error
go test ./internal/kernelboot/... ./internal/kernelmod/... = ok(含 -count=1)
169 lines
6.3 KiB
Go
169 lines
6.3 KiB
Go
package kernelboot
|
||
|
||
// 执行期依赖校验(verifyLayerDependencies)的纯逻辑判据(2026-10-24,ID:14)。
|
||
//
|
||
// ## 为什么与 Build 的判据分开
|
||
//
|
||
// Build 的判据(TestBuildRejectsMissingDependency / …Cycle …)验的是**声明**:
|
||
// 一个模块 Requires 了没人 Provides 的设施,或成环 ⇒ Build 就得拒绝。
|
||
//
|
||
// 本文件的判据假设**声明完全合法**(Build 能算出图),只把算好的 Plan 的
|
||
// 层顺序改坏 —— 这才是 Build 绿而 Run 崩的那类故障(实测 SIGSEGV 见
|
||
// internal/kernelmod/all/runtime_dep_test.go)。
|
||
//
|
||
// ## 覆盖的四类形状
|
||
//
|
||
// ① 正序(正确开销) ⇒ 不误报
|
||
// ② 提供者排到消费者之后 ⇒ 指名模块名 + 缺的设施
|
||
// ③ 提供者整层缺失 ⇒ 第 0 层就拒绝,一个 Init 都不跑
|
||
// ④ 层内多模块各缺不同依赖 ⇒ 逐个模块报(不是只报第一个)
|
||
//
|
||
// 另有一条"M2 型变异"的自检:如果 produced 从 plan 预填(拿声明当事实),
|
||
// ② ③ 会变成**永不触发**的假绿 —— 判据必须能把它抓出来。
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// chainPlan 造一个三层的合法计划:c → b → a(c 无依赖,a 最上层)。
|
||
func chainPlan(t *testing.T) *Plan {
|
||
t.Helper()
|
||
mods := []Module{
|
||
&fakeModule{name: "a", provides: []string{"fa"}, requires: []string{"fb"}},
|
||
&fakeModule{name: "b", provides: []string{"fb"}, requires: []string{"fc"}},
|
||
&fakeModule{name: "c", provides: []string{"fc"}},
|
||
}
|
||
plan, err := Build(mods)
|
||
if err != nil {
|
||
t.Fatalf("前置:声明必须合法(本文件只测顺序被改坏): %v", err)
|
||
}
|
||
return plan
|
||
}
|
||
|
||
// ★★★ ① 正序不误报。
|
||
func TestRunGuardAcceptsCorrectOrder(t *testing.T) {
|
||
if _, err := Run(chainPlan(t), RunOptions{}); err != nil {
|
||
t.Fatalf("★ 合法顺序被判成缺依赖(误报): %v", err)
|
||
}
|
||
}
|
||
|
||
// ★★★ ② 提供者排到消费者之后 ⇒ 指名道姓报错,且 Init 一个都没跑。
|
||
func TestRunGuardRejectsSwappedLayers(t *testing.T) {
|
||
plan := chainPlan(t)
|
||
// 把 b(提供 fb)与其消费者 a 交换:a 先跑,它要的 fb 还没产出。
|
||
swapped := &Plan{Layers: [][]Module{
|
||
plan.Layers[0], // c
|
||
plan.Layers[2], // a ← 原本在最后
|
||
plan.Layers[1], // b
|
||
}}
|
||
|
||
var inited []string
|
||
for _, l := range swapped.Layers {
|
||
for _, m := range l {
|
||
mm := m.(*fakeModule)
|
||
inner := mm.onInit
|
||
mm.onInit = func() {
|
||
inited = append(inited, mm.name)
|
||
if inner != nil {
|
||
inner()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
_, err := Run(swapped, RunOptions{})
|
||
if err == nil {
|
||
t.Fatal("★ 顺序被改坏却装配成功 —— 校验没生效(模块会拿到 nil 依赖)")
|
||
}
|
||
var dnr *DependencyNotReadyError
|
||
if !errors.As(err, &dnr) {
|
||
t.Fatalf("★ 期望 *DependencyNotReadyError,实际 %T: %v", err, err)
|
||
}
|
||
if got, want := strings.Join(dnr.Missing["a"], ","), "fb"; got != want {
|
||
t.Errorf("★ 缺依赖报错不准:期望 a 缺 fb,实际 %v", dnr.Missing)
|
||
}
|
||
if len(inited) != 1 || inited[0] != "c" {
|
||
t.Errorf("★ 违规层不该有任何模块被 Init(只应跑过 c 层),实际跑了 %v", inited)
|
||
}
|
||
for _, sub := range []string{"第 1 层", "模块 a", "fb"} {
|
||
if !strings.Contains(err.Error(), sub) {
|
||
t.Errorf("★ 报错文本缺少 %q(原文: %s)", sub, err.Error())
|
||
}
|
||
}
|
||
}
|
||
|
||
// ★★★ ③ 提供者整层缺失 ⇒ 第 0 层就拒绝,produced 为空。
|
||
func TestRunGuardRejectsMissingProviderLayer(t *testing.T) {
|
||
plan := chainPlan(t)
|
||
cut := &Plan{Layers: [][]Module{plan.Layers[1], plan.Layers[2]}}
|
||
|
||
_, err := Run(cut, RunOptions{})
|
||
if err == nil {
|
||
t.Fatal("★ 提供者整层缺失却装配成功")
|
||
}
|
||
var dnr *DependencyNotReadyError
|
||
if !errors.As(err, &dnr) {
|
||
t.Fatalf("★ 期望 *DependencyNotReadyError,实际 %T: %v", err, err)
|
||
}
|
||
if dnr.Layer != 0 {
|
||
t.Errorf("★ 应在第 0 层拒绝,实际第 %d 层", dnr.Layer)
|
||
}
|
||
if len(dnr.Produced) != 0 {
|
||
t.Errorf("★ 第 0 层之前不该有产出,实际 %v", dnr.Produced)
|
||
}
|
||
}
|
||
|
||
// ★★★ ④ 同层多模块各缺不同依赖 ⇒ 逐个报(不是只报第一个,否则要修 N 轮)。
|
||
func TestRunGuardReportsEveryMissingModuleInLayer(t *testing.T) {
|
||
// 把 a 与 b 放进同一层,且都不提供任何东西 ⇒ 两者都缺依赖。
|
||
merged := &Plan{Layers: [][]Module{
|
||
{&fakeModule{name: "a", requires: []string{"fb"}},
|
||
&fakeModule{name: "b", requires: []string{"fc"}}},
|
||
}}
|
||
_, err := Run(merged, RunOptions{})
|
||
if err == nil {
|
||
t.Fatal("★ 缺依赖却装配成功")
|
||
}
|
||
var dnr *DependencyNotReadyError
|
||
if !errors.As(err, &dnr) {
|
||
t.Fatalf("★ 期望 *DependencyNotReadyError,实际 %T: %v", err, err)
|
||
}
|
||
if len(dnr.Missing) != 2 {
|
||
t.Fatalf("★ 只报了 %d 个模块缺依赖(应 2 个,否则要修 N 轮才看得全): %v",
|
||
len(dnr.Missing), dnr.Missing)
|
||
}
|
||
if strings.Join(dnr.Missing["a"], ",") != "fb" || strings.Join(dnr.Missing["b"], ",") != "fc" {
|
||
t.Errorf("★ 报错内容不准: %v", dnr.Missing)
|
||
}
|
||
}
|
||
|
||
// ★★★ 自检(防 M2 型变异):把 produced 预填成"全部声明" ⇒ 上面 ②③ 必须红。
|
||
//
|
||
// 这条不是测被测代码,而是测**判据具不具备判别力**:
|
||
// 若有人把执行期校验改成"从 plan 预填 produced"(等于相信声明),
|
||
// 它就退化成 Build 的重复实现,抓不到顺序被改坏 —— 那时本文件的 ②③
|
||
// 会变绿(假绿)。所以这里直接把这种实现形态跑一遍,要求它**确实**放行违规,
|
||
// 从而证明 ②③ 的红不是"任何实现都会红"。
|
||
func TestGuardPrefilledFromPlanIsDetectablyWeaker(t *testing.T) {
|
||
plan := chainPlan(t)
|
||
prefilled := map[string]bool{}
|
||
for _, l := range plan.Layers {
|
||
for _, m := range l {
|
||
for _, p := range m.Provides() {
|
||
prefilled[p] = true
|
||
}
|
||
}
|
||
}
|
||
// 违规层:a 在第 0 层,但它要的 fb 由第 2 层的 b 提供。
|
||
layer := []Module{plan.Layers[2][0]}
|
||
if err := verifyLayerDependencies(0, layer, prefilled); err != nil {
|
||
t.Fatalf("★ 预填 produced(拿声明当事实)竟报错 —— 说明本判据的选择无法区分两种实现,需重设: %v", err)
|
||
}
|
||
// 同一层、同一顺序,用**空** produced(真事实)必须报错 ⇒ 判据有判别力。
|
||
if err := verifyLayerDependencies(0, layer, map[string]bool{}); err == nil {
|
||
t.Fatal("★ 空 produced 也不报错 —— verifyLayerDependencies 没有判别力")
|
||
}
|
||
}
|