carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "database/sql"
5 "os"
6 "path/filepath"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/genselect"
11 "github.com/carlosframework/platform/internal/lease"
12)
13
14// walLTXFixture builds a snapshot whose database is in WAL mode, as every
15// database restored from a real litestream replica is (litestream requires
16// it). With tables false the schema is created and dropped again, so the
17// file is a valid WAL-mode database that the restore check refuses ("no
18// tables") after it has opened it.
19func walLTXFixture(t *testing.T, value string, tables bool) (string, []byte, uint64) {
20 t.Helper()
21 path := filepath.Join(t.TempDir(), "fixture.db")
22 db, err := sql.Open("sqlite", path)
23 if err != nil {
24 t.Fatal(err)
25 }
26 db.SetMaxOpenConns(1)
27 stmts := []string{`PRAGMA journal_mode=WAL`, `CREATE TABLE fixture(value TEXT)`}
28 if tables {
29 stmts = append(stmts, `INSERT INTO fixture VALUES ('`+value+`')`)
30 } else {
31 stmts = append(stmts, `DROP TABLE fixture`)
32 }
33 for _, s := range stmts {
34 if _, err := db.Exec(s); err != nil {
35 t.Fatalf("%s: %v", s, err)
36 }
37 }
38 if err := db.Close(); err != nil {
39 t.Fatal(err)
40 }
41 raw := mustRead(t, path)
42 if len(raw) < 20 || raw[18] != 2 || raw[19] != 2 {
43 t.Fatalf("fixture header says journal mode bytes %d,%d; want 2,2 (WAL)", raw[18], raw[19])
44 }
45 return ltxFromDBFile(t, path, 9, 1, 1)
46}
47
48func restoreLeftovers(t *testing.T, dir string) []string {
49 t.Helper()
50 es, err := os.ReadDir(dir)
51 if err != nil {
52 t.Fatal(err)
53 }
54 var out []string
55 for _, e := range es {
56 if strings.HasPrefix(e.Name(), ".restore-") {
57 out = append(out, e.Name())
58 }
59 }
60 return out
61}
62
63// A restored database is in WAL mode, so the read-only check attaches a -wal
64// and a -shm it cannot delete. Install must leave none of them behind, on the
65// way to success and on the way to a failed check, and must never touch the
66// .restore-* files of a concurrent restore.
67func TestColdRestoreLeavesNoRestoreTempFilesBehindWALDatabase(t *testing.T) {
68 cases := []struct {
69 name string
70 tables bool
71 wantErr string
72 }{
73 {name: "success", tables: true},
74 {name: "failed check", tables: false, wantErr: "check restored database"},
75 }
76 for _, tc := range cases {
77 t.Run(tc.name, func(t *testing.T) {
78 dir := t.TempDir()
79 db := filepath.Join(dir, "shop.db")
80 key, snapshot, crc := walLTXFixture(t, "selected", tc.tables)
81 rd := &restoreReader{data: map[string][]byte{key + "@v1": snapshot}}
82 // Another restore's files, in flight beside ours.
83 others := []string{".restore-other.db", ".restore-other.db-shm", ".restore-other.db-wal"}
84 for _, o := range others {
85 if err := os.WriteFile(filepath.Join(dir, o), []byte("not ours"), 0o600); err != nil {
86 t.Fatal(err)
87 }
88 }
89 err := (ColdRestore{Reader: rd, Bucket: "bucket", Generation: "one", Work: &restoreWork{}}).Install(t.Context(), db,
90 &genselect.Plan{Generation: "one", MaxTXID: 1, Pin: &genselect.Pin{SnapshotCRC64: crc}, Files: []genselect.File{{Key: key, VersionID: "v1", Level: 9, MinTXID: 1, MaxTXID: 1, Size: int64(len(snapshot))}}})
91 if tc.wantErr == "" {
92 if err != nil {
93 t.Fatalf("Install: %v", err)
94 }
95 if err := lease.CheckDB(db); err != nil {
96 t.Fatalf("installed database: %v", err)
97 }
98 // The query above may attach its own sidecars to the installed
99 // database; they are named for it, not for the restore temp.
100 } else {
101 if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
102 t.Fatalf("Install error = %v, want %q", err, tc.wantErr)
103 }
104 if _, err := os.Stat(db); !os.IsNotExist(err) {
105 t.Fatalf("a refused restore left a database: %v", err)
106 }
107 }
108 got := restoreLeftovers(t, dir)
109 if strings.Join(got, ",") != strings.Join(others, ",") {
110 t.Fatalf(".restore-* files = %v, want only the concurrent restore's %v", got, others)
111 }
112 })
113 }
114}
115