From d7f9e92fc436d2ac80a8e0b699219c55b05380c5 Mon Sep 17 00:00:00 2001 From: jdl Date: Wed, 27 Jul 2022 13:45:35 +0200 Subject: [PATCH] testing --- btreeindex_test.go | 129 +++++++++---- collection_test.go | 439 ++++++--------------------------------------- 2 files changed, 147 insertions(+), 421 deletions(-) diff --git a/btreeindex_test.go b/btreeindex_test.go index 0de492c..7e0e47d 100644 --- a/btreeindex_test.go +++ b/btreeindex_test.go @@ -119,64 +119,89 @@ func TestFullBTreeIndex(t *testing.T) { func TestPartialBTreeIndex(t *testing.T) { - // Test against the name index. + // Test against the extID btree index. run := func(name string, inner func(t *testing.T, db *DB) []*User) { testWithDB(t, name, func(t *testing.T, db *DB) { expected := inner(t, db) - if err := db.Users.nameBTree.EqualsList(expected); err != nil { + if err := db.Users.extIDBTree.EqualsList(expected); err != nil { t.Fatal(err) } db.Close() db = OpenDB(db.root, true) - if err := db.Users.nameBTree.EqualsList(expected); err != nil { + if err := db.Users.extIDBTree.EqualsList(expected); err != nil { t.Fatal(err) } }) } - run("empty", func(t *testing.T, db *DB) []*User { + run("insert out", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "xxx"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ggg"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "aaa"}, + } + + for _, u := range users { + if _, err := db.Users.c.Insert(*u); err != nil { + t.Fatal(err) + } + } + return []*User{} }) - run("insert", func(t *testing.T, db *DB) (users []*User) { - users = append(users, - &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "xxx"}, - &User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ggg"}, - &User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "aaa"}) + run("insert in", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "xxx"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ggg", ExtID: "x"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "aaa"}, + } for _, u := range users { if _, err := db.Users.c.Insert(*u); err != nil { t.Fatal(err) } } - users[0], users[2] = users[2], users[0] - return users + return []*User{users[1]} }) - run("insert duplicates", func(t *testing.T, db *DB) (users []*User) { - users = append(users, - &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "xxx"}, - &User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ggg"}, - &User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "xxx"}) + run("update out to out", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc", ExtID: "A"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"}, + {ID: db.Users.c.NextID(), Email: "g@h.com", Name: "ggg", ExtID: "B"}, + } for _, u := range users { if _, err := db.Users.c.Insert(*u); err != nil { t.Fatal(err) } } - users = []*User{users[1], users[0], users[2]} - return users + + err := db.Users.c.Update(users[0].ID, func(u User) (User, error) { + u.Name = "axa" + users[0].Name = "axa" + return u, nil + }) + if err != nil { + t.Fatal(err) + } + + return []*User{users[1], users[3]} }) - run("update", func(t *testing.T, db *DB) (users []*User) { - users = append(users, - &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "ccc"}, - &User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "aaa"}, - &User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "bbb"}) + run("update in to in", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc", ExtID: "A"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"}, + {ID: db.Users.c.NextID(), Email: "g@h.com", Name: "ggg", ExtID: "B"}, + } for _, u := range users { if _, err := db.Users.c.Insert(*u); err != nil { @@ -185,23 +210,24 @@ func TestPartialBTreeIndex(t *testing.T) { } err := db.Users.c.Update(users[1].ID, func(u User) (User, error) { - u.Name = "ccc" + u.ExtID = "C" + users[1].ExtID = "C" return u, nil }) if err != nil { t.Fatal(err) } - users[1].Name = "ccc" - users = []*User{users[2], users[0], users[1]} - return users + return []*User{users[3], users[1]} }) - run("update", func(t *testing.T, db *DB) (users []*User) { - users = append(users, - &User{ID: db.Users.c.NextID(), Email: "a@b.com", Name: "bbb"}, - &User{ID: db.Users.c.NextID(), Email: "c@d.com", Name: "aaa"}, - &User{ID: db.Users.c.NextID(), Email: "e@f.com", Name: "bbb"}) + run("update out to in", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc", ExtID: "A"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"}, + {ID: db.Users.c.NextID(), Email: "g@h.com", Name: "ggg", ExtID: "B"}, + } for _, u := range users { if _, err := db.Users.c.Insert(*u); err != nil { @@ -209,9 +235,42 @@ func TestPartialBTreeIndex(t *testing.T) { } } - db.Users.c.Delete(users[1].ID) - users = []*User{users[0], users[2]} - return users + err := db.Users.c.Update(users[2].ID, func(u User) (User, error) { + u.ExtID = "C" + users[2].ExtID = "C" + return u, nil + }) + if err != nil { + t.Fatal(err) + } + + return []*User{users[1], users[3], users[2]} + }) + + run("update in to out", func(t *testing.T, db *DB) []*User { + users := []*User{ + {ID: db.Users.c.NextID(), Email: "a@b.com", Name: "aaa"}, + {ID: db.Users.c.NextID(), Email: "c@d.com", Name: "ccc", ExtID: "A"}, + {ID: db.Users.c.NextID(), Email: "e@f.com", Name: "eee"}, + {ID: db.Users.c.NextID(), Email: "g@h.com", Name: "ggg", ExtID: "B"}, + } + + for _, u := range users { + if _, err := db.Users.c.Insert(*u); err != nil { + t.Fatal(err) + } + } + + err := db.Users.c.Update(users[1].ID, func(u User) (User, error) { + u.ExtID = "" + users[1].ExtID = "" + return u, nil + }) + if err != nil { + t.Fatal(err) + } + + return []*User{users[3]} }) } diff --git a/collection_test.go b/collection_test.go index 4f5e826..cbbfbd6 100644 --- a/collection_test.go +++ b/collection_test.go @@ -1,416 +1,83 @@ package mdb -/* +import ( + "errors" + "testing" +) + func TestCollection(t *testing.T) { - type Item struct { - ID uint64 - Name string // Full map. - ExtID string // Partial map. - } - - sanitize := func(item *Item) { - item.Name = strings.TrimSpace(item.Name) - item.ExtID = strings.TrimSpace(item.ExtID) - } - - ErrInvalidExtID := errors.New("InvalidExtID") - - validate := func(item *Item) error { - if len(item.ExtID) != 0 && !strings.HasPrefix(item.ExtID, "x") { - return ErrInvalidExtID - } - return nil - } - - run := func(name string, inner func(t *testing.T, c *Collection[Item])) { - - t.Run(name, func(t *testing.T) { - root := filepath.Join(os.TempDir(), randString()) - //defer os.RemoveAll(root) - - db := NewPrimary(root) - defer db.Close() - - c := NewCollection(db, randString(), func(i *Item) uint64 { return i.ID }) - c.SetSanitize(sanitize) - c.SetValidate(validate) - - NewMapIndex(c, - "Name", - func(i *Item) string { return i.Name }, - nil) - - NewMapIndex(c, - "ExtID", - func(i *Item) string { return i.ExtID }, - func(i *Item) bool { return i.ExtID != "" }) - - inner(t, c) - }) - } - - verifyCollectionOnce := func(c *Collection[Item], expected ...Item) error { - if len(c.items.m) != len(expected) { - return fmt.Errorf("Expected %d items, but got %d.", len(expected), len(c.items.m)) - } - - for _, item := range expected { - i, ok := c.Get(item.ID) - if !ok { - return fmt.Errorf("Missing expected item: %v", item) - } - if !reflect.DeepEqual(i, item) { - return fmt.Errorf("Items aren't equal: %v != %v", i, item) - } - } - - return nil - } - - verifyCollection := func(c *Collection[Item], expected ...Item) error { - if err := verifyCollectionOnce(c, expected...); err != nil { - return fmt.Errorf("%w: original", err) - } - - // Reload the collection and verify again. - c.db.Close() - - db := NewSecondary(c.db.root) - c2 := NewCollection(db, c.name, func(i *Item) uint64 { return i.ID }) - db.Start() - defer db.Close() - return verifyCollectionOnce(c2, expected...) - } - - run("empty", func(t *testing.T, c *Collection[Item]) { - err := verifyCollection(c) - if err != nil { + testWithDB(t, "insert on secondary", func(t *testing.T, db *DB) { + c := db.Users.c + c.primary = false + if _, err := c.Insert(User{}); !errors.Is(err, ErrReadOnly) { t.Fatal(err) } }) - run("check NextID", func(t *testing.T, c *Collection[Item]) { - id := c.NextID() - for i := 0; i < 100; i++ { - next := c.NextID() - if next <= id { - t.Fatal(next, id) - } - id = next - } - }) - - run("insert", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{1, "Name", "xid"}) - if err != nil { - t.Fatal(err) - } - err = verifyCollection(c, item) - if err != nil { - t.Fatal(err) - } - }) - - run("insert concurrent differnt items", func(t *testing.T, c *Collection[Item]) { - wg := sync.WaitGroup{} - for i := 0; i < 100; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - c.Insert(Item{ - ID: c.NextID(), - Name: fmt.Sprintf("Name.%03d", i), - ExtID: fmt.Sprintf("x.%03d", i), - }) - }(i) - } - - wg.Wait() - }) - - run("insert concurrent same item", func(t *testing.T, c *Collection[Item]) { - wg := sync.WaitGroup{} - for i := 0; i < 100; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - c.Insert(Item{ - ID: 1, - Name: "My name", - }) - }(i) - } - - wg.Wait() - - if err := verifyCollection(c, Item{1, "My name", ""}); err != nil { - t.Fatal(err) - } - }) - - run("insert invalid", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ + testWithDB(t, "insert validation error", func(t *testing.T, db *DB) { + c := db.Users.c + user := User{ ID: c.NextID(), - Name: "Hello", - ExtID: "123"}) - if !errors.Is(err, ErrInvalidExtID) { - t.Fatal(item, err) + Email: "a@b.com", } - }) - - run("insert duplicate ID", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ - ID: c.NextID(), - Name: "Hello", - }) - if err != nil { + if _, err := c.Insert(user); !errors.Is(err, ErrInvalidName) { t.Fatal(err) } - - item2, err := c.Insert(Item{ID: item.ID, Name: "Item"}) - if !errors.Is(err, ErrDuplicate) { - t.Fatal(err, item2) - } }) - run("insert duplicate name", func(t *testing.T, c *Collection[Item]) { - _, err := c.Insert(Item{ - ID: c.NextID(), - Name: "Hello", - }) - if err != nil { - t.Fatal(err) - } - - item2, err := c.Insert(Item{ID: c.NextID(), Name: "Hello"}) - if !errors.Is(err, ErrDuplicate) { - t.Fatal(err, item2) - } - }) - - run("insert duplicate ext ID", func(t *testing.T, c *Collection[Item]) { - _, err := c.Insert(Item{ + testWithDB(t, "insert duplicate", func(t *testing.T, db *DB) { + c := db.Users.c + user := User{ ID: c.NextID(), - Name: "Hello", - ExtID: "x1", + Name: "adsf", + Email: "a@b.com", + } + if _, err := c.Insert(user); err != nil { + t.Fatal(err) + } + if _, err := c.Insert(user); !errors.Is(err, ErrDuplicate) { + t.Fatal(err) + } + }) + + testWithDB(t, "update on secondary", 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.primary = false + + err := c.Update(user.ID, func(u User) (User, error) { + u.Name = "xxx" + return u, nil }) - if err != nil { - t.Fatal(err) - } - - item2, err := c.Insert(Item{ID: c.NextID(), Name: "name", ExtID: "x1"}) - if !errors.Is(err, ErrDuplicate) { - t.Fatal(err, item2) - } - }) - - run("get not found", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ - ID: c.NextID(), - Name: "Hello", - ExtID: "x1", - }) - if err != nil { - t.Fatal(err) - } - - if i, ok := c.Get(item.ID + 1); ok { - t.Fatal(i) - } - }) - - run("update", func(t *testing.T, c *Collection[Item]) { - item1, err := c.Insert(Item{ - ID: c.NextID(), - Name: "Hello", - ExtID: "x1", - }) - if err != nil { - t.Fatal(err) - } - - err = c.Update(item1.ID, func(item Item) (Item, error) { - item.Name = "name" - item.ExtID = "x88" - return item, nil - }) - if err != nil { - t.Fatal(err) - } - - err = verifyCollection(c, Item{ID: item1.ID, Name: "name", ExtID: "x88"}) - if err != nil { + if !errors.Is(err, ErrReadOnly) { t.Fatal(err) } }) - run("update concurrent different items", func(t *testing.T, c *Collection[Item]) { - items := make([]Item, 10) - for i := range items { - item, err := c.Insert(Item{ID: c.NextID(), Name: randString()}) - if err != nil { - t.Fatal(err) - } - items[i] = item - } - - wg := sync.WaitGroup{} - wg.Add(10) - for i := range items { - item := items[i] - go func() { - defer wg.Done() - for x := 0; x < 100; x++ { - err := c.Update(item.ID, func(i Item) (Item, error) { - i.Name = randString() - return i, nil - }) - if err != nil { - panic(err) - } - } - }() - } - - wg.Wait() - }) - - run("update concurrent same item", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ID: c.NextID(), Name: randString()}) - if err != nil { + testWithDB(t, "update 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) } - wg := sync.WaitGroup{} - wg.Add(10) - for i := 0; i < 10; i++ { - go func() { - defer wg.Done() - for x := 0; x < 100; x++ { - err := c.Update(item.ID, func(i Item) (Item, error) { - i.Name = randString() - return i, nil - }) - if err != nil { - panic(err) - } - } - }() - } - - wg.Wait() - }) - - run("update not found", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ID: c.NextID(), Name: randString()}) - if err != nil { - t.Fatal(err) - } - - err = c.Update(item.ID+1, func(i Item) (Item, error) { - i.Name = randString() - return i, nil + err := c.Update(user.ID+1, func(u User) (User, error) { + u.Name = "xxx" + return u, nil }) if !errors.Is(err, ErrNotFound) { t.Fatal(err) } }) - run("update mismatched IDs", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ID: c.NextID(), Name: randString()}) - if err != nil { - t.Fatal(err) - } + // Update w/ failed valiation - err = c.Update(item.ID, func(i Item) (Item, error) { - i.ID++ - return i, nil - }) - - if !errors.Is(err, ErrMismatchedIDs) { - t.Fatal(err) - } - }) - - run("update invalid", func(t *testing.T, c *Collection[Item]) { - item, err := c.Insert(Item{ID: c.NextID(), Name: randString()}) - if err != nil { - t.Fatal(err) - } - - err = c.Update(item.ID, func(i Item) (Item, error) { - i.ExtID = "a" - return i, nil - }) - if !errors.Is(err, ErrInvalidExtID) { - t.Fatal(err) - } - }) - - run("delete", func(t *testing.T, c *Collection[Item]) { - item1, err := c.Insert(Item{c.NextID(), "name1", "x1"}) - if err != nil { - t.Fatal(err) - } - item2, err := c.Insert(Item{c.NextID(), "name2", "x2"}) - if err != nil { - t.Fatal(err) - } - item3, err := c.Insert(Item{c.NextID(), "name3", "x3"}) - if err != nil { - t.Fatal(err) - } - - c.Delete(item2.ID) - if err := verifyCollection(c, item1, item3); err != nil { - t.Fatal(err) - } - }) - - run("delete not found", func(t *testing.T, c *Collection[Item]) { - item1, err := c.Insert(Item{c.NextID(), "name1", "x1"}) - if err != nil { - t.Fatal(err) - } - item2, err := c.Insert(Item{c.NextID(), "name2", "x2"}) - if err != nil { - t.Fatal(err) - } - item3, err := c.Insert(Item{c.NextID(), "name3", "x3"}) - if err != nil { - t.Fatal(err) - } - - c.Delete(c.NextID()) - if err := verifyCollection(c, item1, item2, item3); err != nil { - t.Fatal(err) - } - }) + // Delete on secondary + // Delete not found + // Get found + // Get not found } - -func BenchmarkLoad(b *testing.B) { - type Item struct { - ID uint64 - Name string - } - - root := filepath.Join("test-files", randString()) - db := NewPrimary(root) - getID := func(item *Item) uint64 { return item.ID } - - c := NewCollection(db, "items", getID) - - for i := 0; i < b.N; i++ { - item := Item{ID: c.NextID(), Name: fmt.Sprintf("Name %04d", i)} - c.Insert(item) - } - - b.ResetTimer() - - c2 := NewCollection(db, "items", getID) - log.Print(len(c2.items.m)) - if len(c2.items.m) != b.N { - panic("What?") - } -} -*/