mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-15 07:06:15 +00:00
atomicWriteFile wrote a temp file and renamed it, but never fsync'd — so an unclean power-cut on a journaling NAND filesystem (UBIFS on the speaker's /mnt/nv) could leave the renamed datastore file present but 0 bytes (the rename was journalled, the data blocks were not flushed). Now fsync the temp file before the rename and the parent directory after, via os.Root.OpenFile/Open; directory fsync is best-effort (unsupported on some filesystems). Pairs with the read-side resilience fix (#459): durability prevents the 0-byte files; resilience tolerates any that already exist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.5 KiB
Go
53 lines
1.5 KiB
Go
package datastore
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// TestAtomicWriteFile_DurableRoundTrip guards the durable write path added for
|
|
// #458: content must round-trip, an overwrite must truncate cleanly, and no
|
|
// `.tmp` sidecar may be left behind. (The fsync durability itself isn't
|
|
// unit-testable without power-loss fault injection; this is the functional
|
|
// regression guard so the fsync rework doesn't break writes.)
|
|
func TestAtomicWriteFile_DurableRoundTrip(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "st-atomic-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Cleanup(func() { _ = os.RemoveAll(tempDir) })
|
|
|
|
ds := NewDataStore(tempDir)
|
|
|
|
dir := ds.AccountDeviceDir("1234567", "001122334455")
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
path := filepath.Join(dir, "Sources.xml")
|
|
|
|
if err := ds.atomicWriteFile(path, []byte("first")); err != nil {
|
|
t.Fatalf("atomicWriteFile (create) failed: %v", err)
|
|
}
|
|
|
|
if got, _ := os.ReadFile(path); string(got) != "first" {
|
|
t.Errorf("content after create = %q, want %q", got, "first")
|
|
}
|
|
|
|
// Overwrite must truncate the previous (longer) content, not leave a tail.
|
|
if err := ds.atomicWriteFile(path, []byte("hi")); err != nil {
|
|
t.Fatalf("atomicWriteFile (overwrite) failed: %v", err)
|
|
}
|
|
|
|
if got, _ := os.ReadFile(path); string(got) != "hi" {
|
|
t.Errorf("content after overwrite = %q, want %q", got, "hi")
|
|
}
|
|
|
|
// No leftover temp sidecar.
|
|
if _, err := os.Stat(path + ".tmp"); !os.IsNotExist(err) {
|
|
t.Errorf("expected no %s.tmp leftover, stat err = %v", path, err)
|
|
}
|
|
}
|