github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/tests/parallel/parallel_test.go (about) 1 package tests 2 3 import ( 4 "io" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "golang.org/x/sync/errgroup" 12 13 "github.com/ncruces/go-sqlite3" 14 _ "github.com/ncruces/go-sqlite3/embed" 15 _ "github.com/ncruces/go-sqlite3/tests/testcfg" 16 "github.com/ncruces/go-sqlite3/vfs" 17 _ "github.com/ncruces/go-sqlite3/vfs/adiantum" 18 "github.com/ncruces/go-sqlite3/vfs/memdb" 19 ) 20 21 func Test_parallel(t *testing.T) { 22 if !vfs.SupportsFileLocking { 23 t.Skip("skipping without locks") 24 } 25 26 var iter int 27 if testing.Short() { 28 iter = 1000 29 } else { 30 iter = 5000 31 } 32 33 name := "file:" + 34 filepath.Join(t.TempDir(), "test.db") + 35 "?_pragma=busy_timeout(10000)" + 36 "&_pragma=journal_mode(truncate)" + 37 "&_pragma=synchronous(off)" 38 testParallel(t, name, iter) 39 testIntegrity(t, name) 40 } 41 42 func Test_wal(t *testing.T) { 43 if !vfs.SupportsSharedMemory { 44 t.Skip("skipping without shared memory") 45 } 46 47 name := "file:" + 48 filepath.Join(t.TempDir(), "test.db") + 49 "?_pragma=busy_timeout(10000)" + 50 "&_pragma=journal_mode(wal)" + 51 "&_pragma=synchronous(off)" 52 testParallel(t, name, 1000) 53 testIntegrity(t, name) 54 } 55 56 func Test_memdb(t *testing.T) { 57 var iter int 58 if testing.Short() { 59 iter = 1000 60 } else { 61 iter = 5000 62 } 63 64 memdb.Delete("test.db") 65 memdb.Create("test.db", nil) 66 name := "file:/test.db?vfs=memdb" 67 testParallel(t, name, iter) 68 testIntegrity(t, name) 69 } 70 71 func Test_adiantum(t *testing.T) { 72 if !vfs.SupportsFileLocking { 73 t.Skip("skipping without locks") 74 } 75 76 var iter int 77 if testing.Short() { 78 iter = 1000 79 } else { 80 iter = 5000 81 } 82 83 name := "file:" + 84 filepath.ToSlash(filepath.Join(t.TempDir(), "test.db")) + 85 "?vfs=adiantum" + 86 "&_pragma=hexkey(e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855)" 87 testParallel(t, name, iter) 88 testIntegrity(t, name) 89 } 90 91 func TestMultiProcess(t *testing.T) { 92 if !vfs.SupportsFileLocking { 93 t.Skip("skipping without locks") 94 } 95 if testing.Short() { 96 t.Skip("skipping in short mode") 97 } 98 99 file := filepath.Join(t.TempDir(), "test.db") 100 t.Setenv("TestMultiProcess_dbfile", file) 101 102 name := "file:" + file + 103 "?_pragma=busy_timeout(10000)" + 104 "&_pragma=journal_mode(truncate)" + 105 "&_pragma=synchronous(off)" 106 107 cmd := exec.Command(os.Args[0], append(os.Args[1:], "-test.v", "-test.run=TestChildProcess")...) 108 out, err := cmd.StdoutPipe() 109 if err != nil { 110 t.Fatal(err) 111 } 112 if err := cmd.Start(); err != nil { 113 t.Fatal(err) 114 } 115 116 var buf [3]byte 117 // Wait for child to start. 118 if _, err := io.ReadFull(out, buf[:]); err != nil { 119 t.Fatal(err) 120 } else if str := string(buf[:]); str != "===" { 121 t.Fatal(str) 122 } 123 124 testParallel(t, name, 1000) 125 if err := cmd.Wait(); err != nil { 126 t.Error(err) 127 } 128 testIntegrity(t, name) 129 } 130 131 func TestChildProcess(t *testing.T) { 132 file := os.Getenv("TestMultiProcess_dbfile") 133 if file == "" || testing.Short() { 134 t.SkipNow() 135 } 136 137 name := "file:" + file + 138 "?_pragma=busy_timeout(10000)" + 139 "&_pragma=journal_mode(truncate)" + 140 "&_pragma=synchronous(off)" 141 142 testParallel(t, name, 1000) 143 } 144 145 func Benchmark_memdb(b *testing.B) { 146 sqlite3.Initialize() 147 b.ResetTimer() 148 149 memdb.Delete("test.db") 150 memdb.Create("test.db", nil) 151 name := "file:/test.db?vfs=memdb" 152 testParallel(b, name, b.N) 153 } 154 155 func testParallel(t testing.TB, name string, n int) { 156 writer := func() error { 157 db, err := sqlite3.Open(name) 158 if err != nil { 159 return err 160 } 161 defer db.Close() 162 163 err = db.BusyHandler(func(count int) (retry bool) { 164 time.Sleep(time.Millisecond) 165 return true 166 }) 167 if err != nil { 168 return err 169 } 170 171 err = db.Exec(`CREATE TABLE IF NOT EXISTS users (id INT, name VARCHAR(10))`) 172 if err != nil { 173 return err 174 } 175 176 err = db.Exec(`INSERT INTO users (id, name) VALUES (0, 'go'), (1, 'zig'), (2, 'whatever')`) 177 if err != nil { 178 return err 179 } 180 181 return db.Close() 182 } 183 184 reader := func() error { 185 db, err := sqlite3.Open(name) 186 if err != nil { 187 return err 188 } 189 defer db.Close() 190 191 err = db.BusyTimeout(10 * time.Second) 192 if err != nil { 193 return err 194 } 195 196 stmt, _, err := db.Prepare(`SELECT id, name FROM users`) 197 if err != nil { 198 return err 199 } 200 defer stmt.Close() 201 202 row := 0 203 for stmt.Step() { 204 row++ 205 } 206 if err := stmt.Err(); err != nil { 207 return err 208 } 209 if row%3 != 0 { 210 t.Errorf("got %d rows, want multiple of 3", row) 211 } 212 213 err = stmt.Close() 214 if err != nil { 215 return err 216 } 217 218 return db.Close() 219 } 220 221 err := writer() 222 if err != nil { 223 t.Fatal(err) 224 } 225 226 var group errgroup.Group 227 group.SetLimit(6) 228 for i := 0; i < n; i++ { 229 if i&7 != 7 { 230 group.Go(reader) 231 } else { 232 group.Go(writer) 233 } 234 } 235 err = group.Wait() 236 if err != nil { 237 t.Error(err) 238 } 239 } 240 241 func testIntegrity(t testing.TB, name string) { 242 db, err := sqlite3.Open(name) 243 if err != nil { 244 t.Fatal(err) 245 } 246 defer db.Close() 247 248 test := `PRAGMA integrity_check` 249 if testing.Short() { 250 test = `PRAGMA quick_check` 251 } 252 253 stmt, _, err := db.Prepare(test) 254 if err != nil { 255 t.Fatal(err) 256 } 257 defer stmt.Close() 258 259 for stmt.Step() { 260 if row := stmt.ColumnText(0); row != "ok" { 261 t.Error(row) 262 } 263 } 264 if err := stmt.Err(); err != nil { 265 t.Fatal(err) 266 } 267 268 err = stmt.Close() 269 if err != nil { 270 t.Fatal(err) 271 } 272 273 err = db.Close() 274 if err != nil { 275 t.Fatal(err) 276 } 277 }