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
This commit is contained in:
@@ -295,12 +295,22 @@ func fileState(group []Record) int {
|
|||||||
|
|
||||||
var errSymlinkTarget = errors.New("checkpoint: restore target is a symlink")
|
var errSymlinkTarget = errors.New("checkpoint: restore target is a symlink")
|
||||||
|
|
||||||
|
// restoreForTesting 是测试缝:非空时在每次恢复前调用(返回错误即恢复失败)。
|
||||||
|
// 生产代码永不应设置它;恢复期的故障注入无法绕开分类层的预检
|
||||||
|
// (权限类失败对 root 不可重现,故用显式缝)。
|
||||||
|
var restoreForTesting func(abs string) error
|
||||||
|
|
||||||
func restore(group []Record) error {
|
func restore(group []Record) error {
|
||||||
if len(group) == 0 {
|
if len(group) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
first := group[0]
|
first := group[0]
|
||||||
abs := filepath.Join(first.Root, filepath.FromSlash(first.Path))
|
abs := filepath.Join(first.Root, filepath.FromSlash(first.Path))
|
||||||
|
if restoreForTesting != nil {
|
||||||
|
if err := restoreForTesting(abs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
// 符号链接防护:恢复会跟出根外(与 fs_write_file 的防护同语义)。
|
// 符号链接防护:恢复会跟出根外(与 fs_write_file 的防护同语义)。
|
||||||
// 按外部改动处理——由调用方归入 Skipped,不写不删。
|
// 按外部改动处理——由调用方归入 Skipped,不写不删。
|
||||||
if info, err := os.Lstat(abs); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
if info, err := os.Lstat(abs); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package checkpoint
|
package checkpoint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"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 {
|
if err := log.Record("m1", root, "b/fail.txt", nil, false, []byte("B后")); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
// dirB 只读:os.Remove 在其中失败。
|
// 测试缝注入恢复失败(权限法对 root 不可重现——CI 以 root 跑)。
|
||||||
if err := os.Chmod(dirB, 0o500); err != nil {
|
restoreForTesting = func(abs string) error {
|
||||||
t.Fatal(err)
|
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"})
|
res, err := log.Rewind("m1", []string{"m1"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("单文件失败不得中止:%v", err)
|
t.Fatalf("单文件失败不得中止:%v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user