From f2fbefa2b90c1c18f884a6a48dabe4b891bc90cf Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 7 Oct 2026 15:50:04 +0000 Subject: [PATCH] Make the partial-restore failure test root-proof via a test seam Why: CI runs as root, where chmod-based failure injection does not fail (root bypasses file permissions), and any filesystem state that would fail restore is pre-detected by the classification pass. A package-level test seam makes the injection deterministic on any uid. Evidence: GOWORK=off go test (-race) green locally. Impact: test-only seam, documented as never-for-production. Refs: Agent-Native#33 --- runtime/checkpoint/checkpoint.go | 10 ++++++++++ runtime/checkpoint/n3_test.go | 13 +++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/runtime/checkpoint/checkpoint.go b/runtime/checkpoint/checkpoint.go index e53304e..b7304f9 100644 --- a/runtime/checkpoint/checkpoint.go +++ b/runtime/checkpoint/checkpoint.go @@ -295,12 +295,22 @@ func fileState(group []Record) int { var errSymlinkTarget = errors.New("checkpoint: restore target is a symlink") +// restoreForTesting 是测试缝:非空时在每次恢复前调用(返回错误即恢复失败)。 +// 生产代码永不应设置它;恢复期的故障注入无法绕开分类层的预检 +// (权限类失败对 root 不可重现,故用显式缝)。 +var restoreForTesting func(abs string) error + func restore(group []Record) error { if len(group) == 0 { return nil } first := group[0] abs := filepath.Join(first.Root, filepath.FromSlash(first.Path)) + if restoreForTesting != nil { + if err := restoreForTesting(abs); err != nil { + return err + } + } // 符号链接防护:恢复会跟出根外(与 fs_write_file 的防护同语义)。 // 按外部改动处理——由调用方归入 Skipped,不写不删。 if info, err := os.Lstat(abs); err == nil && info.Mode()&os.ModeSymlink != 0 { diff --git a/runtime/checkpoint/n3_test.go b/runtime/checkpoint/n3_test.go index 5fb6af6..d19f443 100644 --- a/runtime/checkpoint/n3_test.go +++ b/runtime/checkpoint/n3_test.go @@ -1,8 +1,10 @@ package checkpoint import ( + "errors" "os" "path/filepath" + "strings" "testing" ) @@ -78,11 +80,14 @@ func TestRewindPartialFailureContinues(t *testing.T) { if err := log.Record("m1", root, "b/fail.txt", nil, false, []byte("B后")); err != nil { t.Fatal(err) } - // dirB 只读:os.Remove 在其中失败。 - if err := os.Chmod(dirB, 0o500); err != nil { - t.Fatal(err) + // 测试缝注入恢复失败(权限法对 root 不可重现——CI 以 root 跑)。 + restoreForTesting = func(abs string) error { + if strings.HasSuffix(abs, "b/fail.txt") { + return errors.New("注入的恢复失败") + } + return nil } - defer func() { _ = os.Chmod(dirB, 0o755) }() + defer func() { restoreForTesting = nil }() res, err := log.Rewind("m1", []string{"m1"}) if err != nil { t.Fatalf("单文件失败不得中止:%v", err)