| 1 | // Package dbdoortest builds real litestream replicas for the db door's tests, |
| 2 | // on both sides of the wire (internal/console serves one, internal/dbdoor and |
| 3 | // the CLI restore one). A fake replica would prove only that the fake agrees |
| 4 | // with itself; these are written by litestream's own replication code, in |
| 5 | // the layout its file client reads, so a restore through the door is the |
| 6 | // restore production runs. |
| 7 | // |
| 8 | // Test support only: nothing outside a _test.go file may import it. |
| 9 | package dbdoortest |
| 10 | |
| 11 | import ( |
| 12 | "context" |
| 13 | "database/sql" |
| 14 | "fmt" |
| 15 | "path/filepath" |
| 16 | "testing" |
| 17 | "time" |
| 18 | |
| 19 | "github.com/benbjohnson/litestream" |
| 20 | lsfile "github.com/benbjohnson/litestream/file" |
| 21 | _ "modernc.org/sqlite" |
| 22 | ) |
| 23 | |
| 24 | // Rows is how many rows each batch inserts into table t. |
| 25 | const Rows = 10 |
| 26 | |
| 27 | // Replicate creates a WAL-mode SQLite database with a table t(id, body) and |
| 28 | // replicates it into dir in `batches` separate syncs, so the replica holds |
| 29 | // several L0 files rather than one. It returns the source database's path; |
| 30 | // the source holds batches*Rows rows when it returns. |
| 31 | func Replicate(t testing.TB, dir string, batches int) string { |
| 32 | t.Helper() |
| 33 | src := filepath.Join(t.TempDir(), "source.db") |
| 34 | db, err := sql.Open("sqlite", src) |
| 35 | if err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | defer db.Close() |
| 39 | if _, err := db.Exec(`PRAGMA journal_mode=WAL; CREATE TABLE t (id INTEGER PRIMARY KEY, body TEXT)`); err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | for b := 0; b < batches; b++ { |
| 43 | for i := 0; i < Rows; i++ { |
| 44 | if _, err := db.Exec(`INSERT INTO t (body) VALUES (?)`, fmt.Sprintf("row-%d-%d", b, i)); err != nil { |
| 45 | t.Fatal(err) |
| 46 | } |
| 47 | } |
| 48 | Sync(t, src, dir) |
| 49 | } |
| 50 | return src |
| 51 | } |
| 52 | |
| 53 | // Sync replicates the source's committed state into dir once, the way the |
| 54 | // lease package's Checkpoint does: open, one explicit sync, close. |
| 55 | func Sync(t testing.TB, src, dir string) { |
| 56 | t.Helper() |
| 57 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
| 58 | defer cancel() |
| 59 | ldb := litestream.NewDB(src) |
| 60 | ldb.SetMetaPath(filepath.Join(filepath.Dir(src), ".dbdoortest-litestream")) |
| 61 | ldb.MonitorInterval = 0 |
| 62 | ldb.Replica = litestream.NewReplicaWithClient(ldb, lsfile.NewReplicaClient(dir)) |
| 63 | ldb.Replica.MonitorEnabled = false |
| 64 | store := litestream.NewStore([]*litestream.DB{ldb}, litestream.CompactionLevels{{Level: 0}}) |
| 65 | store.CompactionMonitorEnabled = false |
| 66 | store.L0RetentionCheckInterval = 0 |
| 67 | store.SetRetentionEnabled(false) |
| 68 | if err := store.Open(ctx); err != nil { |
| 69 | t.Fatal(err) |
| 70 | } |
| 71 | syncErr := ldb.SyncAndWait(ctx) |
| 72 | if err := store.Close(ctx); err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | if syncErr != nil { |
| 76 | t.Fatal(syncErr) |
| 77 | } |
| 78 | } |
| 79 | |