testing: WIP
parent
d7f9e92fc4
commit
2f0ab90271
15
README.md
15
README.md
|
@ -4,15 +4,10 @@ An in-process, in-memory database for Go.
|
||||||
|
|
||||||
## TO DO
|
## TO DO
|
||||||
|
|
||||||
* mapindex_test.go
|
* database: test for concurrent writers
|
||||||
* ~~TestFullMapIndex~~
|
* Create writers in different ID ranges of Users and Accounts
|
||||||
* btreeindex_test.go
|
* Check results at end.
|
||||||
* TestPartialBTreeIndex
|
* database: WAL shipping
|
||||||
* btreeiterator_test.go (?)
|
* database: WAL shipping with network disconnects
|
||||||
* collection
|
|
||||||
* database
|
|
||||||
* WAL shipping
|
|
||||||
* WAL shipping with network disconnects
|
|
||||||
|
|
||||||
* BTreeIndex:
|
* BTreeIndex:
|
||||||
* Should insert panic if item is replaced?
|
* Should insert panic if item is replaced?
|
||||||
|
|
|
@ -118,6 +118,7 @@ func (t *BTreeIndex[T]) Len() int {
|
||||||
func (t *BTreeIndex[T]) insert(item *T) {
|
func (t *BTreeIndex[T]) insert(item *T) {
|
||||||
if t.include == nil || t.include(item) {
|
if t.include == nil || t.include(item) {
|
||||||
t.modify(func(bt *btree.BTreeG[*T]) {
|
t.modify(func(bt *btree.BTreeG[*T]) {
|
||||||
|
// TODO: Panic if replaces.
|
||||||
bt.ReplaceOrInsert(item)
|
bt.ReplaceOrInsert(item)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -129,6 +130,7 @@ func (t *BTreeIndex[T]) update(old, new *T) {
|
||||||
bt.Delete(old)
|
bt.Delete(old)
|
||||||
}
|
}
|
||||||
if t.include == nil || t.include(new) {
|
if t.include == nil || t.include(new) {
|
||||||
|
// TODO: Panic if replaces.
|
||||||
bt.ReplaceOrInsert(new)
|
bt.ReplaceOrInsert(new)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -175,15 +175,8 @@ func (c Collection[T]) Get(id uint64) (t T, ok bool) {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
func (c *Collection[T]) loadData() {
|
func (c *Collection[T]) loadData() {
|
||||||
toRemove := []int{}
|
for _, idx := range c.indices {
|
||||||
for i, idx := range c.indices {
|
must(idx.load(c.items.m))
|
||||||
if err := idx.load(c.items.m); err != nil {
|
|
||||||
toRemove = append([]int{i}, toRemove...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range toRemove {
|
|
||||||
c.indices = append(c.indices[:i], c.indices[i+1:]...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package mdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -74,10 +75,72 @@ func TestCollection(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update w/ failed valiation
|
testWithDB(t, "update failed validation", func(t *testing.T, db *DB) {
|
||||||
|
c := db.Users.c
|
||||||
|
user := User{ID: c.NextID(), Name: "adsf", Email: "a@b.com"}
|
||||||
|
if _, err := c.Insert(user); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete on secondary
|
err := c.Update(user.ID, func(u User) (User, error) {
|
||||||
// Delete not found
|
u.Name = ""
|
||||||
// Get found
|
return u, nil
|
||||||
// Get not found
|
})
|
||||||
|
if !errors.Is(err, ErrInvalidName) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testWithDB(t, "delete on secondary", func(t *testing.T, db *DB) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err == nil {
|
||||||
|
t.Fatal("No panic")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c := db.Users.c
|
||||||
|
user := User{ID: c.NextID(), Name: "adsf", Email: "a@b.com"}
|
||||||
|
if _, err := c.Insert(user); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.primary = false
|
||||||
|
|
||||||
|
c.Delete(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
testWithDB(t, "delete not found", func(t *testing.T, db *DB) {
|
||||||
|
c := db.Users.c
|
||||||
|
user := User{ID: c.NextID(), Name: "adsf", Email: "a@b.com"}
|
||||||
|
if _, err := c.Insert(user); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
c.Delete(user.ID + 1) // Does nothing.
|
||||||
|
})
|
||||||
|
|
||||||
|
testWithDB(t, "get", func(t *testing.T, db *DB) {
|
||||||
|
c := db.Users.c
|
||||||
|
user := User{ID: c.NextID(), Name: "adsf", Email: "a@b.com"}
|
||||||
|
if _, err := c.Insert(user); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u2, ok := c.Get(user.ID)
|
||||||
|
if !ok || !reflect.DeepEqual(user, u2) {
|
||||||
|
t.Fatal(ok, u2, user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
testWithDB(t, "get not found", func(t *testing.T, db *DB) {
|
||||||
|
c := db.Users.c
|
||||||
|
user := User{ID: c.NextID(), Name: "adsf", Email: "a@b.com"}
|
||||||
|
if _, err := c.Insert(user); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u2, ok := c.Get(user.ID - 1)
|
||||||
|
if ok {
|
||||||
|
t.Fatal(ok, u2)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,7 @@ func TestMain(m *testing.M) {
|
||||||
func testWithDB(t *testing.T, name string, inner func(t *testing.T, db *DB)) {
|
func testWithDB(t *testing.T, name string, inner func(t *testing.T, db *DB)) {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
root, err := os.MkdirTemp("", "")
|
root, err := os.MkdirTemp("", "")
|
||||||
if err != nil {
|
must(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
db := OpenDB(root, true)
|
db := OpenDB(root, true)
|
||||||
|
|
|
@ -1,76 +1,32 @@
|
||||||
package mdb
|
package mdb
|
||||||
|
|
||||||
/*
|
import (
|
||||||
func TestLogShip(t *testing.T) {
|
"os"
|
||||||
type Item struct {
|
"testing"
|
||||||
ID uint64
|
|
||||||
Name string
|
"git.crumpington.com/private/mdb/testconn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShipping(t *testing.T) {
|
||||||
|
run := func(name string, inner func(t *testing.T, db1 *DB, db2 *DB, network *testconn.Network)) {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
root1, err := os.MkdirTemp("", "")
|
||||||
|
must(err)
|
||||||
|
defer os.RemoveAll(root1)
|
||||||
|
root2, err := os.MkdirTemp("", "")
|
||||||
|
must(err)
|
||||||
|
defer os.RemoveAll(root2)
|
||||||
|
|
||||||
|
db1 := OpenDB(root1, true)
|
||||||
|
defer db1.Close()
|
||||||
|
db2 := OpenDB(root2, false)
|
||||||
|
defer db2.Close()
|
||||||
|
|
||||||
|
inner(t, db1, db2, testconn.NewNetwork())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
newDB := func(root string, primary bool) (*Database, *Collection[Item]) {
|
run("simple", func(t *testing.T, db1, db2 *DB, network *testconn.Network) {
|
||||||
var db *Database
|
// TODO
|
||||||
if primary {
|
|
||||||
db = NewPrimary(root)
|
|
||||||
} else {
|
|
||||||
db = NewSecondary(root)
|
|
||||||
}
|
|
||||||
c := NewCollection(db, "collection", func(i *Item) uint64 { return i.ID })
|
|
||||||
NewBTreeIndex(c,
|
|
||||||
func(i, j *Item) bool { return i.Name < j.Name },
|
|
||||||
func(i *Item) bool { return i.Name != "" })
|
|
||||||
return db, c
|
|
||||||
}
|
|
||||||
|
|
||||||
root1 := filepath.Join(os.TempDir(), randString())
|
|
||||||
root2 := filepath.Join(os.TempDir(), randString())
|
|
||||||
//log.Print(root1, " --> ", root2)
|
|
||||||
defer os.RemoveAll(root1)
|
|
||||||
defer os.RemoveAll(root2)
|
|
||||||
|
|
||||||
dbLeader, colLeader := newDB(root1, true)
|
|
||||||
dbLeader.Start()
|
|
||||||
defer dbLeader.Close()
|
|
||||||
|
|
||||||
dbFollower, _ := newDB(root2, false)
|
|
||||||
dbFollower.Start()
|
|
||||||
defer dbFollower.Close()
|
|
||||||
|
|
||||||
c1, c2 := net.Pipe()
|
|
||||||
go dbLeader.SyncSend(c1)
|
|
||||||
go dbFollower.SyncRecv(c2)
|
|
||||||
|
|
||||||
item1 := Item{1, "one"}
|
|
||||||
item2 := Item{2, ""}
|
|
||||||
item3 := Item{3, "three"}
|
|
||||||
item4 := Item{4, ""}
|
|
||||||
item5 := Item{5, "five"}
|
|
||||||
|
|
||||||
item1, _ = colLeader.Insert(item1)
|
|
||||||
item2, _ = colLeader.Insert(item2)
|
|
||||||
item3, _ = colLeader.Insert(item3)
|
|
||||||
item4, _ = colLeader.Insert(item4)
|
|
||||||
item5, _ = colLeader.Insert(item5)
|
|
||||||
colLeader.Delete(item2.ID)
|
|
||||||
|
|
||||||
colLeader.Update(item4.ID, func(old Item) (Item, error) {
|
|
||||||
old.Name = "UPDATED"
|
|
||||||
return old, nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
dbLeader.waitForWAL()
|
|
||||||
dbFollower.waitForWAL()
|
|
||||||
|
|
||||||
dbLeader, colLeader = newDB(root1, true)
|
|
||||||
dbLeader.Start()
|
|
||||||
dbFollower, colFollower := newDB(root2, false)
|
|
||||||
dbFollower.Start()
|
|
||||||
|
|
||||||
m1 := colLeader.items.m
|
|
||||||
m2 := colFollower.items.m
|
|
||||||
|
|
||||||
if len(m1) != len(m2) {
|
|
||||||
t.Fatal(m1, m2)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
Reference in New Issue