package fsutil import ( "errors" "os" "path/filepath" "syscall" "testing" ) func TestWriteFileAtomic_RoundTripAndDirSync(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "output.md") if err := WriteFileAtomic(path, []byte("第一版 \n"), 0o644); err != nil { t.Fatal(err) } if err := WriteFileAtomic(path, []byte("第二版\n"), 0o644); err != nil { t.Fatal(err) } got, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(got) != "第二版\n" { t.Fatalf("got %q", got) } } func TestWriteFileAtomic_RenamesThenSyncsParentDir(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "output.md") var synced string orig := dirSync t.Cleanup(func() { dirSync = orig }) dirSync = func(d string) error { synced = d return orig(d) } if err := WriteFileAtomic(path, []byte("durable\n"), 0o644); err != nil { t.Fatal(err) } if synced != dir { t.Fatalf("parent dir sync got %q want %q", synced, dir) } } func TestWriteFileAtomic_DirSyncUnsupportedIsIgnored(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "output.md") orig := dirSync t.Cleanup(func() { dirSync = orig }) dirSync = func(string) error { return syscall.EINVAL } if err := WriteFileAtomic(path, []byte("ok\n"), 0o644); err != nil { t.Fatal(err) } got, _ := os.ReadFile(path) if string(got) != "ok\n" { t.Fatalf("got %q", got) } } func TestWriteFileAtomic_DirSyncHardErrorSurfacesAfterRename(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "output.md") orig := dirSync t.Cleanup(func() { dirSync = orig }) dirSync = func(string) error { return errors.New("sync boom") } if err := WriteFileAtomic(path, []byte("kept\n"), 0o644); err == nil { t.Fatal("hard dir sync error must surface") } got, err := os.ReadFile(path) if err != nil || string(got) != "kept\n" { t.Fatalf("rename must have succeeded before sync error: %q %v", got, err) } } func TestWriteFileAtomicExclusive_RefusesExisting(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "keep.md") if err := WriteFileAtomicExclusive(path, []byte("原备份\n"), 0o644); err != nil { t.Fatal(err) } if err := WriteFileAtomicExclusive(path, []byte("覆盖\n"), 0o644); err == nil { t.Fatal("exclusive write must refuse existing backup") } got, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(got) != "原备份\n" { t.Fatalf("existing backup mutated: %q", got) } } func TestSyncDir_OpensTarget(t *testing.T) { dir := t.TempDir() if err := SyncDir(dir); err != nil { t.Fatal(err) } if err := SyncDir(filepath.Join(dir, "missing")); err == nil { t.Fatal("missing dir must fail") } } func TestIgnorableDirSync(t *testing.T) { if !ignorableDirSync(syscall.EINVAL) || !ignorableDirSync(syscall.ENOTSUP) || !ignorableDirSync(syscall.ENOSYS) { t.Fatal("expected compatibility errno to be ignored") } if ignorableDirSync(errors.New("other")) { t.Fatal("hard errors must not be ignored") } }