Files
replicate/replicate_test.go
zeekay 6b67e835e8 fix(sqlite): route all SQLite onto hanzoai/sqlite — one driver, no double-register under cgo
Base embeds replicate into commerce's CGO=1 (SQLCipher) binary. replicate
unconditionally imported modernc.org/sqlite (driver registration + FileControl +
modernc-syntax DSN), so under cgo hanzoai/sqlite registered mattn as "sqlite"
AND modernc.init registered "sqlite" again -> panic: sql: Register called twice
for driver sqlite. Fix: import ONLY github.com/hanzoai/sqlite (the canonical
dual-backend driver; mattn under cgo, modernc otherwise); never modernc directly.

- replicate.go: _ modernc -> _ hanzoai/sqlite (sole driver registration).
- db.go setPersistWAL: driverConn.(sqlite.FileControl)+FileControlPersistWAL ->
  sqlite.SetPersistWAL(driverConn, true) (backend-neutral PERSIST_WAL bridge).
- db.go init: modernc-syntax DSN + sql.Open -> sqlite.OpenPragma("file:"+path,
  {busy_timeout, wal_autocheckpoint=0}). mattn SILENTLY DROPS _wal_autocheckpoint
  from a DSN; OpenPragma applies it per pooled connection via PRAGMA, so
  checkpoint control holds under BOTH backends (no lost WAL frames under cgo).
- cmd/replicate/main.go + 4 root _test.go files: _ modernc -> _ hanzoai/sqlite.
- go.mod: require hanzoai/sqlite v0.2.3; modernc now // indirect.

Verified: CGO=0 build+tests green (incl -race); CGO=1 build+tests green, the
Register-twice panic is GONE, WAL/replication/checkpoint/restore round-trip
(incl encrypted e2e) all pass. Pre-existing (fail identically on origin/main):
7 *Metrics tests (replicate_db_size=0). CGO=1 -race
TestDB_CheckpointPageGapWithConcurrentWrites hangs — a mattn+race artifact of the
test's second busy_timeout=0 pool (persists with a plain sql.Open, so NOT this
change); never ran under mattn before (main panicked at init).
2026-07-08 15:45:11 -07:00

164 lines
21 KiB
Go

package replicate_test
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/hanzoai/ltx"
_ "github.com/hanzoai/sqlite"
"github.com/hanzoai/replicate"
)
func TestChecksum(t *testing.T) {
// Ensure a WAL header, frame header, & frame data can be checksummed in one pass.
t.Run("OnePass", func(t *testing.T) {
input, err := hex.DecodeString("377f0682002de218000010000000000052382eac857b1a4e00000002000000020d000000080fe0000ffc0ff80ff40ff00fec0fe80fe40fe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000208020902070209020602090205020902040209020302090202020902010209")
if err != nil {
t.Fatal(err)
}
s0, s1 := replicate.Checksum(binary.LittleEndian, 0, 0, input)
if got, want := [2]uint32{s0, s1}, [2]uint32{0xdc2f3e84, 0x540488d3}; got != want {
t.Fatalf("Checksum()=%x, want %x", got, want)
}
})
// Ensure we get the same result as OnePass even if we split up into multiple calls.
t.Run("Incremental", func(t *testing.T) {
// Compute checksum for beginning of WAL header.
s0, s1 := replicate.Checksum(binary.LittleEndian, 0, 0, MustDecodeHexString("377f0682002de218000010000000000052382eac857b1a4e"))
if got, want := [2]uint32{s0, s1}, [2]uint32{0x81153b65, 0x87178e8f}; got != want {
t.Fatalf("Checksum()=%x, want %x", got, want)
}
// Continue checksum with WAL frame header & frame contents.
s0a, s1a := replicate.Checksum(binary.LittleEndian, s0, s1, MustDecodeHexString("0000000200000002"))
s0b, s1b := replicate.Checksum(binary.LittleEndian, s0a, s1a, MustDecodeHexString(`0d000000080fe0000ffc0ff80ff40ff00fec0fe80fe40fe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000208020902070209020602090205020902040209020302090202020902010209`))
if got, want := [2]uint32{s0b, s1b}, [2]uint32{0xdc2f3e84, 0x540488d3}; got != want {
t.Fatalf("Checksum()=%x, want %x", got, want)
}
})
}
func TestLTXDir(t *testing.T) {
if got, want := replicate.LTXDir("foo"), "foo/ltx"; got != want {
t.Fatalf("LTXDir()=%v, want %v", got, want)
}
}
func TestLTXLevelDir(t *testing.T) {
if got, want := replicate.LTXLevelDir("foo", 0), "foo/ltx/0"; got != want {
t.Fatalf("LTXLevelDir()=%v, want %v", got, want)
}
}
func LTXFilePath(t *testing.T) {
t.Helper()
if got, want := replicate.LTXFilePath("foo", 0, ltx.TXID(100), ltx.TXID(200)), "-"; got != want {
t.Fatalf("LTXPath()=%v, want %v", got, want)
}
}
func MustDecodeHexString(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
func TestNewLTXError(t *testing.T) {
t.Run("MissingFile", func(t *testing.T) {
err := replicate.NewLTXError("open", "/path/to/file.ltx", 0, 1, 1, os.ErrNotExist)
if err.Hint == "" {
t.Fatal("expected hint for missing file error")
}
if !strings.Contains(err.Hint, "missing") {
t.Errorf("hint should mention missing file, got: %s", err.Hint)
}
if !strings.Contains(err.Hint, "replicate reset") {
t.Errorf("hint should mention reset command, got: %s", err.Hint)
}
})
t.Run("CorruptedFile", func(t *testing.T) {
err := replicate.NewLTXError("decode", "/path/to/file.ltx", 0, 1, 1, replicate.ErrLTXCorrupted)
if err.Hint == "" {
t.Fatal("expected hint for corrupted file error")
}
if !strings.Contains(err.Hint, "corrupted") {
t.Errorf("hint should mention corruption, got: %s", err.Hint)
}
})
t.Run("ChecksumMismatch", func(t *testing.T) {
err := replicate.NewLTXError("validate", "/path/to/file.ltx", 0, 1, 1, replicate.ErrChecksumMismatch)
if err.Hint == "" {
t.Fatal("expected hint for checksum mismatch error")
}
})
t.Run("ErrorString", func(t *testing.T) {
err := replicate.NewLTXError("open", "/path/to/file.ltx", 0, 1, 1, os.ErrNotExist)
errStr := err.Error()
if !strings.Contains(errStr, "open") {
t.Errorf("error should contain operation, got: %s", errStr)
}
if !strings.Contains(errStr, "/path/to/file.ltx") {
t.Errorf("error should contain path, got: %s", errStr)
}
})
t.Run("Unwrap", func(t *testing.T) {
underlying := errors.New("underlying error")
err := replicate.NewLTXError("read", "/path/to/file.ltx", 0, 1, 1, underlying)
if !errors.Is(err, underlying) {
t.Error("LTXError should unwrap to underlying error")
}
})
}
func TestLTXErrorHints(t *testing.T) {
// Test that ErrLTXMissing also triggers appropriate hints
t.Run("ErrLTXMissing", func(t *testing.T) {
err := replicate.NewLTXError("open", "/path/to/file.ltx", 0, 1, 1, replicate.ErrLTXMissing)
if err.Hint == "" {
t.Fatal("expected hint for ErrLTXMissing")
}
if !strings.Contains(err.Hint, "replicate reset") {
t.Errorf("hint should mention reset command, got: %s", err.Hint)
}
})
}
func TestLTXError_IsAutoRecoverable(t *testing.T) {
tests := []struct {
name string
err error
recoverable bool
}{
{"NotExist", os.ErrNotExist, true},
{"LTXMissing", replicate.ErrLTXMissing, true},
{"LTXCorrupted", replicate.ErrLTXCorrupted, true},
{"ChecksumMismatch", replicate.ErrChecksumMismatch, true},
{"WrappedCorrupted", fmt.Errorf("%w: bad data", replicate.ErrLTXCorrupted), true},
{"PermissionDenied", os.ErrPermission, false},
{"GenericError", errors.New("something went wrong"), false},
{"IOError", fmt.Errorf("disk failure: %w", errors.New("EIO")), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ltxErr := replicate.NewLTXError("open", "/path/to/file.ltx", 0, 1, 1, tt.err)
if got := ltxErr.IsAutoRecoverable(); got != tt.recoverable {
t.Fatalf("IsAutoRecoverable()=%v, want %v (err=%v)", got, tt.recoverable, tt.err)
}
})
}
}