mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 22:37:03 +00:00
22dcce6e6c
writeFileInPlace0600 read a failed f.Stat() as "not a regular file" and skipped the truncate, so a shorter body left the old tail behind and credentials.json was corrupt. It now returns the stat error before writing anything. statOpenFile is the test seam. Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr
44 lines
1.3 KiB
Go
44 lines
1.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// BUG-3810: a failed stat used to read as "not a regular file", which skips
|
|
// the truncate, so a shorter body left the old tail behind. It now fails the
|
|
// write before anything is written.
|
|
func TestBUG3810_FailedStatFailsTheWriteAndLeavesTheFile(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "credentials.json")
|
|
old := `{"credentials": {"a long body that a shorter write would leave a tail of": {}}}`
|
|
if err := os.WriteFile(path, []byte(old), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
statErr := errors.New("stat failed")
|
|
orig := statOpenFile
|
|
statOpenFile = func(*os.File) (os.FileInfo, error) { return nil, statErr }
|
|
t.Cleanup(func() { statOpenFile = orig })
|
|
|
|
if err := writeFileInPlace0600(path, []byte(`{}`)); !errors.Is(err, statErr) {
|
|
t.Fatalf("write with a failed stat: err = %v, want the stat error", err)
|
|
}
|
|
got, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != old {
|
|
t.Fatalf("a refused write changed the file to %q", got)
|
|
}
|
|
|
|
// CONTROL: with the stat working, the shorter body replaces the file whole.
|
|
statOpenFile = orig
|
|
if err := writeFileInPlace0600(path, []byte(`{}`)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, _ := os.ReadFile(path); string(got) != `{}` {
|
|
t.Fatalf("a working write left %q, want {}", got)
|
|
}
|
|
}
|