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
|
||||
|
||||
* mapindex_test.go
|
||||
* ~~TestFullMapIndex~~
|
||||
* btreeindex_test.go
|
||||
* TestPartialBTreeIndex
|
||||
* btreeiterator_test.go (?)
|
||||
* collection
|
||||
* database
|
||||
* WAL shipping
|
||||
* WAL shipping with network disconnects
|
||||
|
||||
* database: test for concurrent writers
|
||||
* Create writers in different ID ranges of Users and Accounts
|
||||
* Check results at end.
|
||||
* database: WAL shipping
|
||||
* database: WAL shipping with network disconnects
|
||||
* BTreeIndex:
|
||||
* Should insert panic if item is replaced?
|
||||
|
|
|
@ -118,6 +118,7 @@ func (t *BTreeIndex[T]) Len() int {
|
|||
func (t *BTreeIndex[T]) insert(item *T) {
|
||||
if t.include == nil || t.include(item) {
|
||||
t.modify(func(bt *btree.BTreeG[*T]) {
|
||||
// TODO: Panic if replaces.
|
||||
bt.ReplaceOrInsert(item)
|
||||
})
|
||||
}
|
||||
|
@ -129,6 +130,7 @@ func (t *BTreeIndex[T]) update(old, new *T) {
|
|||
bt.Delete(old)
|
||||
}
|
||||
if t.include == nil || t.include(new) {
|
||||
// TODO: Panic if replaces.
|
||||
bt.ReplaceOrInsert(new)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -175,15 +175,8 @@ func (c Collection[T]) Get(id uint64) (t T, ok bool) {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (c *Collection[T]) loadData() {
|
||||
toRemove := []int{}
|
||||
for i, idx := range c.indices {
|
||||
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:]...)
|
||||
for _, idx := range c.indices {
|
||||
must(idx.load(c.items.m))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package mdb
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"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
|
||||
// Delete not found
|
||||
// Get found
|
||||
// Get not found
|
||||
err := c.Update(user.ID, func(u User) (User, error) {
|
||||
u.Name = ""
|
||||
return u, nil
|
||||
})
|
||||
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)) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
root, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
must(err)
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
db := OpenDB(root, true)
|
||||
|
|
|
@ -1,76 +1,32 @@
|
|||
package mdb
|
||||
|
||||
/*
|
||||
func TestLogShip(t *testing.T) {
|
||||
type Item struct {
|
||||
ID uint64
|
||||
Name string
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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]) {
|
||||
var db *Database
|
||||
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
|
||||
run("simple", func(t *testing.T, db1, db2 *DB, network *testconn.Network) {
|
||||
// TODO
|
||||
})
|
||||
|
||||
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