Split up DB-level unit testing
parent
16ae659175
commit
ca6f071fb5
@ -0,0 +1,297 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rqlite/rqlite/command"
|
||||
)
|
||||
|
||||
// Test_TableCreationInMemory tests basic operation of an in-memory database,
|
||||
// ensuring that using different connection objects (as the Execute and Query
|
||||
// will do) works properly i.e. that the connections object work on the same
|
||||
// in-memory database.
|
||||
func Test_TableCreationInMemory(t *testing.T) {
|
||||
db := mustCreateInMemoryDatabase()
|
||||
defer db.Close()
|
||||
|
||||
if !db.InMemory() {
|
||||
t.Fatal("in-memory database marked as not in-memory")
|
||||
}
|
||||
|
||||
r, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{}]`, asJSON(r); exp != got {
|
||||
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||||
}
|
||||
|
||||
q, err := db.QueryStringStmt("SELECT * FROM foo")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query empty table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(q); exp != got {
|
||||
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_LoadIntoMemory(t *testing.T) {
|
||||
db, path := mustCreateDatabase()
|
||||
defer db.Close()
|
||||
defer os.Remove(path)
|
||||
|
||||
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
r, err := db.QueryStringStmt("SELECT * FROM foo")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query empty table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(r); exp != got {
|
||||
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||||
}
|
||||
|
||||
inmem, err := LoadIntoMemory(path, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create loaded in-memory database: %s", err.Error())
|
||||
}
|
||||
|
||||
// Ensure it has been loaded correctly into the database
|
||||
r, err = inmem.QueryStringStmt("SELECT * FROM foo")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query empty table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{"columns":["id","name"],"types":["integer","text"]}]`, asJSON(r); exp != got {
|
||||
t.Fatalf("unexpected results for query, expected %s, got %s", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DeserializeIntoMemory(t *testing.T) {
|
||||
db, path := mustCreateDatabase()
|
||||
defer db.Close()
|
||||
defer os.Remove(path)
|
||||
|
||||
_, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
req := &command.Request{
|
||||
Transaction: true,
|
||||
Statements: []*command.Statement{
|
||||
{
|
||||
Sql: `INSERT INTO foo(id, name) VALUES(1, "fiona")`,
|
||||
},
|
||||
{
|
||||
Sql: `INSERT INTO foo(id, name) VALUES(2, "fiona")`,
|
||||
},
|
||||
{
|
||||
Sql: `INSERT INTO foo(id, name) VALUES(3, "fiona")`,
|
||||
},
|
||||
{
|
||||
Sql: `INSERT INTO foo(id, name) VALUES(4, "fiona")`,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = db.Execute(req, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert records: %s", err.Error())
|
||||
}
|
||||
|
||||
// Get byte representation of database on disk which, according to SQLite docs
|
||||
// is the same as a serialized version.
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read database on disk: %s", err.Error())
|
||||
}
|
||||
|
||||
newDB, err := DeserializeIntoMemory(b, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to deserialize database: %s", err.Error())
|
||||
}
|
||||
defer newDB.Close()
|
||||
|
||||
ro, err := newDB.QueryStringStmt(`SELECT * FROM foo`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"fiona"],[3,"fiona"],[4,"fiona"]]}]`, asJSON(ro); exp != got {
|
||||
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
||||
}
|
||||
|
||||
// Write a lot of records to the new database, to ensure it's fully functional.
|
||||
req = &command.Request{
|
||||
Statements: []*command.Statement{
|
||||
{
|
||||
Sql: `INSERT INTO foo(name) VALUES("fiona")`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for i := 0; i < 5000; i++ {
|
||||
_, err = newDB.Execute(req, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to insert records: %s", err.Error())
|
||||
}
|
||||
}
|
||||
ro, err = newDB.QueryStringStmt(`SELECT COUNT(*) FROM foo`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query table: %s", err.Error())
|
||||
}
|
||||
if exp, got := `[{"columns":["COUNT(*)"],"types":[""],"values":[[5004]]}]`, asJSON(ro); exp != got {
|
||||
t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ParallelOperationsInMemory runs multiple accesses concurrently, ensuring
|
||||
// that correct results are returned in every goroutine. It's not 100% that this
|
||||
// test would bring out a bug, but it's almost 100%.
|
||||
//
|
||||
// See https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
|
||||
func Test_ParallelOperationsInMemory(t *testing.T) {
|
||||
db := mustCreateInMemoryDatabase()
|
||||
defer db.Close()
|
||||
|
||||
if _, err := db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := db.ExecuteStringStmt("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := db.ExecuteStringStmt("CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"); err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
// Confirm schema is as expected, when checked from same goroutine.
|
||||
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
||||
t.Fatalf("failed to query for schema after creation: %s", err.Error())
|
||||
} else {
|
||||
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
||||
t.Fatalf("schema not as expected during after creation, exp %s, got %s", exp, got)
|
||||
}
|
||||
}
|
||||
|
||||
var exWg sync.WaitGroup
|
||||
exWg.Add(3)
|
||||
|
||||
foo := make(chan time.Time)
|
||||
bar := make(chan time.Time)
|
||||
qux := make(chan time.Time)
|
||||
done := make(chan bool)
|
||||
|
||||
ticker := time.NewTicker(1 * time.Millisecond)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case t := <-ticker.C:
|
||||
foo <- t
|
||||
bar <- t
|
||||
qux <- t
|
||||
case <-done:
|
||||
close(foo)
|
||||
close(bar)
|
||||
close(qux)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer exWg.Done()
|
||||
for range foo {
|
||||
if _, err := db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`); err != nil {
|
||||
t.Logf("failed to insert records into foo: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer exWg.Done()
|
||||
for range bar {
|
||||
if _, err := db.ExecuteStringStmt(`INSERT INTO bar(id, name) VALUES(1, "fiona")`); err != nil {
|
||||
t.Logf("failed to insert records into bar: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer exWg.Done()
|
||||
for range qux {
|
||||
if _, err := db.ExecuteStringStmt(`INSERT INTO qux(id, name) VALUES(1, "fiona")`); err != nil {
|
||||
t.Logf("failed to insert records into qux: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var qWg sync.WaitGroup
|
||||
qWg.Add(3)
|
||||
for i := 0; i < 3; i++ {
|
||||
go func(j int) {
|
||||
defer qWg.Done()
|
||||
var n int
|
||||
for {
|
||||
if rows, err := db.QueryStringStmt(`SELECT sql FROM sqlite_master`); err != nil {
|
||||
t.Logf("failed to query for schema during goroutine %d execution: %s", j, err.Error())
|
||||
} else {
|
||||
n++
|
||||
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
|
||||
t.Logf("schema not as expected during goroutine execution, exp %s, got %s, after %d queries", exp, got, n)
|
||||
}
|
||||
}
|
||||
if n == 500000 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
qWg.Wait()
|
||||
|
||||
close(done)
|
||||
exWg.Wait()
|
||||
}
|
||||
|
||||
// Test_TableCreationLoadRawInMemory tests for https://sqlite.org/forum/forumpost/d443fb0730
|
||||
func Test_TableCreationLoadRawInMemory(t *testing.T) {
|
||||
db := mustCreateInMemoryDatabase()
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.ExecuteStringStmt("CREATE TABLE logs (entry TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
// Insert some records continually, as fast as possible. Do it from a goroutine.
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
_, err := db.ExecuteStringStmt(`INSERT INTO logs(entry) VALUES("hello")`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Get the count over and over again.
|
||||
for i := 0; i < 5000; i++ {
|
||||
rows, err := db.QueryStringStmt(`SELECT COUNT(*) FROM logs`)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to query for count: %s", err)
|
||||
}
|
||||
|
||||
if rows[0].Error != "" {
|
||||
t.Fatalf("rows had error after %d queries: %s", i, rows[0].Error)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_IsValidSQLiteOnDisk(t *testing.T) {
|
||||
path := mustTempFile()
|
||||
defer os.Remove(path)
|
||||
|
||||
dsn := fmt.Sprintf("file:%s", path)
|
||||
db, err := sql.Open("sqlite3", dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create SQLite database: %s", err.Error())
|
||||
}
|
||||
_, err = db.Exec("CREATE TABLE foo (name TEXT)")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create table: %s", err.Error())
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatalf("failed to close database: %s", err.Error())
|
||||
}
|
||||
|
||||
if !IsValidSQLiteFile(path) {
|
||||
t.Fatalf("good SQLite file marked as invalid")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read SQLite file: %s", err.Error())
|
||||
}
|
||||
if !IsValidSQLiteData(data) {
|
||||
t.Fatalf("good SQLite data marked as invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FileCreationOnDisk(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dbPath := path.Join(dir, "test_db")
|
||||
|
||||
db, err := Open(dbPath, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open new database: %s", err.Error())
|
||||
}
|
||||
if db == nil {
|
||||
t.Fatal("database is nil")
|
||||
}
|
||||
if db.InMemory() {
|
||||
t.Fatal("on-disk database marked as in-memory")
|
||||
}
|
||||
if db.FKEnabled() {
|
||||
t.Fatal("FK constraints marked as enabled")
|
||||
}
|
||||
if db.Path() != dbPath {
|
||||
t.Fatal("database path is incorrect")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||
t.Fatalf("%s does not exist after open", dbPath)
|
||||
}
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to close database: %s", err.Error())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue