This repository has been archived on 2022-07-30. You can view files and clone it, but cannot push or open issues/pull-requests.
mdb/collection_test.go

417 lines
8.5 KiB
Go

package mdb
/*
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 {
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{
ID: c.NextID(),
Name: "Hello",
ExtID: "123"})
if !errors.Is(err, ErrInvalidExtID) {
t.Fatal(item, err)
}
})
run("insert duplicate ID", func(t *testing.T, c *Collection[Item]) {
item, err := c.Insert(Item{
ID: c.NextID(),
Name: "Hello",
})
if err != nil {
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{
ID: c.NextID(),
Name: "Hello",
ExtID: "x1",
})
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 {
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 {
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
})
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)
}
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)
}
})
}
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?")
}
}
*/