Browse Source

improve tests, clean up some inadequately tested code

Aneurin Barker Snook 11 tháng trước cách đây
mục cha
commit
f26b55327f
12 tập tin đã thay đổi với 428 bổ sung370 xóa
  1. 0 1
      README.md
  2. 0 22
      bytes.go
  3. 0 272
      collection_test.go
  4. 192 0
      collection_tester.go
  5. 24 1
      errors.go
  6. 4 0
      interfaces.go
  7. 0 43
      json_test.go
  8. 34 1
      leveldb.go
  9. 64 15
      leveldb_test.go
  10. 39 1
      memory.go
  11. 12 8
      memory_iter.go
  12. 59 6
      memory_test.go

+ 0 - 1
README.md

@@ -55,7 +55,6 @@ Some database backends require marshaling and unmarshaling data. The `DocumentMa
 
 The following marshalers are included in EZ DB:
 
-- `Bytes` allows you to write `[]byte` directly to a database that requires `[]byte`
 - `JSON[T]` marshals your data `T` to `[]byte` using [encoding/json](https://pkg.go.dev/encoding/json)
 
 ## Supported databases

+ 0 - 22
bytes.go

@@ -1,22 +0,0 @@
-package ezdb
-
-// BytesMarshaler is a DocumentMarshaler that simply passes along bytes.
-type BytesMarshaler struct{}
-
-func (m *BytesMarshaler) Factory() []byte {
-	return []byte{}
-}
-
-func (m *BytesMarshaler) Marshal(src []byte) ([]byte, error) {
-	return src, nil
-}
-
-func (m *BytesMarshaler) Unmarshal(src []byte, dest []byte) error {
-	dest = src
-	return nil
-}
-
-// Bytes creates a DocumentMarshaler that simply passes along bytes.
-func Bytes() *BytesMarshaler {
-	return &BytesMarshaler{}
-}

+ 0 - 272
collection_test.go

@@ -1,272 +0,0 @@
-package ezdb
-
-import (
-	"errors"
-	"fmt"
-	"testing"
-)
-
-// Basic struct for testing.
-type Student struct {
-	Name string `json:"name"`
-	Age  int    `json:"age"`
-}
-
-var invalidStudents = map[string]*Student{
-	"": {},
-}
-
-var nonexistentStudentKey = "nonexistent"
-
-// Basic marshaler for testing.
-var studentMarshaler = JSON(func() *Student {
-	return &Student{}
-})
-
-// Sample data.
-var students = map[string]*Student{
-	"annie": {Name: "Annie", Age: 32},
-	"ben":   {Name: "Ben", Age: 50},
-	"clive": {Name: "Clive", Age: 21},
-}
-
-// Sample data (marshaled).
-var studentsMarshaled = map[string][]byte{
-	"annie": []byte("{\"name\":\"Annie\",\"age\":32}"),
-	"ben":   []byte("{\"name\":\"Ben\",\"age\":50}"),
-	"clive": []byte("{\"name\":\"Clive\",\"age\":21}"),
-}
-
-type CollectionTest struct {
-	C Collection[*Student]
-	T *testing.T
-
-	F map[string]func() error
-}
-
-func (c *CollectionTest) open() error {
-	if err := c.C.Open(); err != nil {
-		c.T.Errorf("(open) failed to open collection: %v", err)
-		return err
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) put() error {
-	// Test collection can store all students
-	for key, value := range students {
-		if err := c.C.Put(key, value); err != nil {
-			c.T.Errorf("(put) failed to put student '%s': %v", key, err)
-			return err
-		}
-		c.T.Logf("(put) put student '%s'", key)
-	}
-
-	// Test collection does not accept invalid keys
-	for key, value := range invalidStudents {
-		if err := c.C.Put(key, value); err == nil {
-			c.T.Errorf("(put) should not have put invalid student '%s'", key)
-			return err
-		}
-		c.T.Logf("(put) skipped invalid student '%s'", key)
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) has() error {
-	// Test collection has all students
-	for key := range students {
-		has, err := c.C.Has(key)
-		if err != nil {
-			c.T.Errorf("(has) failed to test whether collection has student '%s': %v", key, err)
-			return err
-		} else if !has {
-			c.T.Errorf("(has) expected collection to have student '%s'", key)
-			return err
-		}
-		c.T.Logf("(has) found student '%s'", key)
-	}
-
-	// Test collection does claim to have a student that doesn't exist
-	has, err := c.C.Has(nonexistentStudentKey)
-	if err != nil {
-		c.T.Errorf("(has) failed to test whether collection has nonexistent student: %v", err)
-	} else if has {
-		c.T.Error("(has) expected collection not to have nonexistent student")
-	} else {
-		c.T.Logf("(has) collection does not have nonexistent student")
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) get() error {
-	// Test collection can retrieve all students
-	for key, expected := range students {
-		actual, err := c.C.Get(key)
-		if err != nil {
-			c.T.Errorf("(get) failed to get student '%s': %v", key, err)
-			continue
-		} else if err := compareStudent(key, expected, actual); err != nil {
-			c.T.Errorf("(get) %v", err)
-		} else {
-			c.T.Logf("(get) correctly got student '%s'", key)
-		}
-	}
-
-	// Test collection does not retrieve a nonexistent student
-	_, err := c.C.Get(nonexistentStudentKey)
-	if err == nil {
-		c.T.Error("(get) expected collection to return an error for nonexistent student")
-	} else {
-		c.T.Log("(get) collection did not get a nonexistent student")
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) delete() error {
-	if err := c.C.Delete("annie"); err != nil {
-		c.T.Errorf("(delete) failed to delete student '%s': %v", "annie", err)
-		return err
-	}
-
-	// Confirm student has been deleted
-	has, err := c.C.Has("annie")
-	if err != nil {
-		c.T.Errorf("(delete) failed to test whether collection has deleted student 'annie': %v", err)
-		return err
-	} else if has {
-		c.T.Error("(delete) expected collection not to have deleted student 'annie'")
-		return err
-	} else {
-		c.T.Log("(delete) collection did not get the deleted student 'annie'")
-	}
-
-	// Reinsert deleted student
-	if err := c.C.Put("annie", students["annie"]); err != nil {
-		c.T.Errorf("(delete) failed to reinsert student 'annie': %v", err)
-		return err
-	} else {
-		c.T.Log("(delete) reinserted student 'annie'")
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) iterCount() error {
-	iter := c.C.Iter()
-	defer iter.Release()
-
-	expected := len(students)
-	actual := iter.Count()
-	if expected != actual {
-		c.T.Errorf("(iterCount) incorrect count of students (expected %d, got %d)", expected, actual)
-		return errors.New("incorrect count")
-	} else {
-		c.T.Logf("(iterCount) correct count of students (expected %d, got %d)", expected, actual)
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) iterFirst() error {
-	iter := c.C.Iter().SortKeys(func(a, b string) bool {
-		return a < b
-	})
-	defer iter.Release()
-
-	expectedKey := "annie"
-	iter.First()
-	actualKey := iter.Key()
-	if actualKey != expectedKey {
-		c.T.Errorf("(iterFirst) incorrect student (expected '%s', got '%s')", expectedKey, actualKey)
-		return nil
-	}
-
-	expected := students["annie"]
-	actual, err := iter.Value()
-	if err != nil {
-		c.T.Errorf("(iterFirst) failed to get student '%s': %v", actualKey, err)
-		return err
-	}
-	if err := compareStudent(expectedKey, expected, actual); err != nil {
-		c.T.Errorf("(iterFirst) %v", err)
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) iterLast() error {
-	iter := c.C.Iter().SortKeys(func(a, b string) bool {
-		return a < b
-	})
-	defer iter.Release()
-
-	expectedKey := "clive"
-	iter.Last()
-	actualKey := iter.Key()
-	if actualKey != expectedKey {
-		c.T.Errorf("(iterFirst) incorrect student (expected '%s', got '%s')", expectedKey, actualKey)
-		return nil
-	}
-
-	expected := students["clive"]
-	actual, err := iter.Value()
-	if err != nil {
-		c.T.Errorf("(iterFirst) failed to get student '%s': %v", actualKey, err)
-		return err
-	}
-	if err := compareStudent(expectedKey, expected, actual); err != nil {
-		c.T.Errorf("(iterFirst) %v", err)
-	}
-
-	return nil
-}
-
-func (c *CollectionTest) close() error {
-	if c.F["close"] != nil {
-		if err := c.F["close"](); err != nil {
-			c.T.Errorf("(close) failed to close collection: %v", err)
-			return err
-		}
-	} else if err := c.C.Close(); err != nil {
-		c.T.Errorf("(close) failed to close collection: %v", err)
-		return err
-	}
-
-	c.T.Log("(close) closed database")
-
-	return nil
-}
-
-func (c *CollectionTest) Run() {
-	tests := []func() error{
-		c.open,
-		c.put,
-		c.has,
-		c.get,
-		c.delete,
-		c.iterCount,
-		c.iterFirst,
-		c.iterLast,
-		c.close,
-	}
-
-	for _, test := range tests {
-		if err := test(); err != nil {
-			return
-		}
-	}
-}
-
-func compareStudent(expectedKey string, expected, actual *Student) error {
-	if actual.Name != expected.Name {
-		return fmt.Errorf("student '%s' has wrong name (expected '%s', got '%s')", expectedKey, expected.Name, actual.Name)
-	} else if actual.Age != expected.Age {
-		return fmt.Errorf("student '%s' has wrong age (expected '%s', got '%s')", expectedKey, expected.Name, actual.Name)
-	}
-	return nil
-}

+ 192 - 0
collection_tester.go

@@ -0,0 +1,192 @@
+package ezdb
+
+import (
+	"fmt"
+	"math/rand"
+	"slices"
+	"sort"
+)
+
+// CollectionTester is a convenience type to help build out collection testing with less boilerplate.
+type CollectionTester[T any] struct {
+	C Collection[T]
+
+	Cmp  func(a, b T) error // Comparison function to assert equivalence between two documents.
+	Data map[string]T       // Test data.
+}
+
+func (t *CollectionTester[T]) DeleteAll() error {
+	if err := t.C.DeleteAll(); err != nil {
+		return fmt.Errorf("failed to delete all documents: %v", err)
+	}
+
+	return nil
+}
+
+// DeleteOne tests that the collection safely deletes a single document.
+// The document is not reinserted after deletion.
+func (t *CollectionTester[T]) DeleteOne() error {
+	// Select a random key.
+	// This selects from the collection rather than test data to allow the function to be reused, so long as the collection has one document
+	keys := t.C.GetAllKeys()
+	if len(keys) == 0 {
+		return fmt.Errorf("no documents")
+	}
+	n := rand.Intn(len(keys) - 1)
+	key := keys[n]
+
+	if err := t.C.Delete(key); err != nil {
+		return fmt.Errorf("failed to delete document '%s': %v", key, err)
+	}
+
+	// Confirm document has been deleted
+	has, err := t.C.Has(key)
+	if err != nil {
+		return fmt.Errorf("failed to test whether collection has deleted document '%s': %v", key, err)
+	} else if has {
+		return fmt.Errorf("expected collection not to have deleted document '%s'", key)
+	}
+
+	return nil
+}
+
+// Get tests that the collection retrieves all documents.
+// The collection must be initialised.
+func (t *CollectionTester[T]) Get() error {
+	errs := Errors{}
+
+	for key, data := range t.Data {
+		actual, err := t.C.Get(key)
+		if err != nil {
+			errs = append(errs, fmt.Errorf("failed to get document '%s': %v", key, err))
+		} else if err := t.Cmp(data, actual); err != nil {
+			errs = append(errs, fmt.Errorf("comparison failed for document '%s': %v", key, err))
+		}
+	}
+
+	return errs.Resolve()
+}
+
+func (t *CollectionTester[T]) GetAll() error {
+	all, err := t.C.GetAll()
+	if err != nil {
+		return fmt.Errorf("failed to get all documents: %v", err)
+	}
+
+	errs := Errors{}
+	for key, actual := range all {
+		expected := t.Data[key]
+		if err := t.Cmp(expected, actual); err != nil {
+			errs = append(errs, fmt.Errorf("comparison failed for document '%s': %v", key, err))
+		}
+	}
+
+	return errs.Resolve()
+}
+
+// Has tests that the collection has all documents.
+// The collection must be initialised.
+func (t *CollectionTester[T]) Has() error {
+	errs := Errors{}
+
+	for key := range t.Data {
+		has, err := t.C.Has(key)
+		if err != nil {
+			return fmt.Errorf("failed to test whether collection has document '%s': %v", key, err)
+		} else if !has {
+			errs = append(errs, fmt.Errorf("expected collection to have document '%s'", key))
+		}
+	}
+
+	return errs.Resolve()
+}
+
+// Init tests that the collection is correctly initialised.
+// This opens the collection, deletes any existing data, and reinserts the test data.
+func (t *CollectionTester[T]) Init() error {
+	fs := []func() error{t.Open, t.DeleteAll, t.Put}
+	for _, f := range fs {
+		if err := f(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// IterCount tests that the iterator counts documents correctly.
+// The collection must be initialised.
+func (t *CollectionTester[T]) IterCount() error {
+	iter := t.C.Iter()
+	actual := iter.Count()
+	iter.Release()
+
+	expected := len(t.Data)
+	if expected != actual {
+		return fmt.Errorf("incorrect document count (expected %d, got %d)", expected, actual)
+	}
+
+	return nil
+}
+
+// IterSortKeys tests that the iterator sorts correctly by key.
+// The collection must be initialised.
+func (t *CollectionTester[T]) IterSortKeys() error {
+	ks := &keySort{
+		a: []string{},
+		f: func(a, b string) bool {
+			return a < b
+		},
+	}
+	for key := range t.Data {
+		ks.a = append(ks.a, key)
+	}
+
+	sort.Stable(ks)
+	sortedKeys := ks.Result()
+
+	iter := t.C.Iter().SortKeys(ks.f)
+	defer iter.Release()
+
+	errs := Errors{}
+	actual := 0
+	for iter.Next() {
+		key := iter.Key()
+		if key != sortedKeys[actual] {
+			expected := slices.Index(sortedKeys, key)
+			errs = append(errs, fmt.Errorf("incorrect sort position of key '%s' (expected %d, got %d)", key, expected, actual))
+		}
+		actual++
+	}
+
+	return errs.Resolve()
+}
+
+// Open tests that the collection is opened.
+func (t *CollectionTester[T]) Open() error {
+	if err := t.C.Open(); err != nil {
+		return fmt.Errorf("failed to open collection: %v", err)
+	}
+	return nil
+}
+
+// Put tests that the collection can store all documents.
+func (t *CollectionTester[T]) Put() error {
+	errs := Errors{}
+
+	for key, data := range t.Data {
+		if err := t.C.Put(key, data); err != nil {
+			errs = append(errs, fmt.Errorf("failed to put document '%s': %v", key, err))
+		}
+	}
+
+	return errs.Resolve()
+}
+
+func (t *CollectionTester[T]) Close() error {
+	if err := t.C.Close(); err != nil {
+		return fmt.Errorf("failed to close collection: %v", err)
+	}
+
+	return nil
+}

+ 24 - 1
errors.go

@@ -1,6 +1,9 @@
 package ezdb
 
-import "errors"
+import (
+	"errors"
+	"strings"
+)
 
 // High-level EZ DB error.
 // These are not exhaustive and your chosen implementation of Collection may produce its own errors.
@@ -10,3 +13,23 @@ var (
 	ErrNotFound   = errors.New("not found")
 	ErrReleased   = errors.New("iterator has been released")
 )
+
+// Errors collates one or more errors.
+// It is mainly used for testing.
+type Errors []error
+
+func (e Errors) Error() string {
+	strs := []string{}
+	for _, err := range e {
+		strs = append(strs, err.Error())
+	}
+	return strings.Join(strs, "\n")
+}
+
+// Resolve a Errors to an error or nil, if it is empty.
+func (e Errors) Resolve() error {
+	if len(e) > 0 {
+		return e
+	}
+	return nil
+}

+ 4 - 0
interfaces.go

@@ -5,8 +5,12 @@ type Collection[T any] interface {
 	Open() error  // Open the collection.
 	Close() error // Close the collection.
 
+	Count() int                           // Count the number of documents in the collection.
 	Delete(key string) error              // Delete a document by key.
+	DeleteAll() error                     // Delete all documents in the collection.
 	Get(key string) (value T, err error)  // Get a document by key.
+	GetAll() (map[string]T, error)        // Get all documents in the collection.
+	GetAllKeys() []string                 // Get all keys in the document.
 	Has(key string) (has bool, err error) // Check whether a document exists by key.
 	Put(key string, value T) error        // Put a document into the collection.
 

+ 0 - 43
json_test.go

@@ -1,43 +0,0 @@
-package ezdb
-
-import (
-	"bytes"
-	"testing"
-)
-
-func TestJSONFactory(t *testing.T) {
-	t.Logf("creating empty student")
-	var value any = studentMarshaler.Factory()
-	if _, ok := value.(*Student); !ok {
-		t.Errorf("factory did not create correct value type (expected '*Student', got '%T')", value)
-	}
-}
-
-func TestJSONMarshal(t *testing.T) {
-	for key, value := range students {
-		t.Logf("marshaling student '%s'", key)
-		b, err := studentMarshaler.Marshal(value)
-		if err != nil {
-			t.Errorf("failed to marshal student '%s' (%q)", key, err)
-		} else if !bytes.Equal(b, studentsMarshaled[key]) {
-			t.Errorf("student '%s' incorrectly marshaled (expected '%s', got '%s')", key, studentsMarshaled[key], b)
-		}
-	}
-}
-
-func TestJSONUnmarshal(t *testing.T) {
-	for key, b := range studentsMarshaled {
-		t.Logf("unmarshaling student '%s'", key)
-		value := studentMarshaler.Factory()
-		if err := studentMarshaler.Unmarshal(b, value); err != nil {
-			t.Errorf("failed to unmarshal student \"%s\" (%q)", key, err)
-		} else {
-			if value.Name != students[key].Name {
-				t.Errorf("student '%s' name incorrectly unmarshaled (expected '%s', got '%s')", key, students[key].Name, value.Name)
-			}
-			if value.Age != students[key].Age {
-				t.Errorf("student '%s' age incorrectly unmarshaled (expected '%d', got '%d')", key, students[key].Age, value.Age)
-			}
-		}
-	}
-}

+ 34 - 1
leveldb.go

@@ -30,10 +30,30 @@ func (c *LevelDBCollection[T]) Close() error {
 	return nil
 }
 
+func (c *LevelDBCollection[T]) Count() int {
+	iter := c.Iter()
+	defer iter.Release()
+	return iter.Count()
+}
+
 func (c *LevelDBCollection[T]) Delete(key string) error {
 	return c.db.Delete([]byte(key), c.optWrite)
 }
 
+func (c *LevelDBCollection[T]) DeleteAll() error {
+	iter := c.Iter()
+	defer iter.Release()
+
+	keys := iter.GetAllKeys()
+	for _, key := range keys {
+		if err := c.Delete(key); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 // Destroy the database completely, removing it from disk.
 func (c *LevelDBCollection[T]) Destroy() error {
 	if err := c.Close(); err != nil {
@@ -56,6 +76,19 @@ func (c *LevelDBCollection[T]) Get(key string) (T, error) {
 	return dest, err
 }
 
+func (c *LevelDBCollection[T]) GetAll() (map[string]T, error) {
+	iter := c.Iter()
+	defer iter.Release()
+	return iter.GetAll()
+}
+
+func (c *LevelDBCollection[T]) GetAllKeys() []string {
+	iter := c.Iter()
+	defer iter.Release()
+
+	return iter.GetAllKeys()
+}
+
 func (c *LevelDBCollection[T]) Has(key string) (bool, error) {
 	return c.db.Has([]byte(key), c.optRead)
 }
@@ -102,7 +135,7 @@ func LevelDB[T any](path string, m DocumentMarshaler[T, []byte], o *LevelDBOptio
 
 		m: m,
 
-		// Unpack options now to reduce nil checks
+		// Unpack options
 		optOpen:  o.GetOpen(),
 		optRead:  o.GetRead(),
 		optWrite: o.GetWrite(),

+ 64 - 15
leveldb_test.go

@@ -1,27 +1,76 @@
 package ezdb
 
-import "testing"
+import (
+	"fmt"
+	"testing"
+)
 
 func TestLevelDB(t *testing.T) {
-	path := ".leveldb/leveldb_test"
-	c := LevelDB[*Student](path, studentMarshaler, nil)
-
-	fixture := &CollectionTest{
-		C: c,
-		T: t,
-		F: map[string]func() error{},
+	type Student struct {
+		Name string `json:"name"`
+		Age  int    `json:"age"`
 	}
 
-	fixture.F["close"] = func() error {
-		if err := c.Close(); err != nil {
-			return err
+	path := ".leveldb/test_json"
+
+	marshaler := JSON(func() *Student {
+		return &Student{}
+	})
+
+	collection := LevelDB(path, marshaler, nil)
+
+	cmp := func(expected, actual *Student) error {
+		if expected == nil && actual != nil {
+			return fmt.Errorf("expected no student (got %v)", actual)
+		} else if expected != nil && actual == nil {
+			return fmt.Errorf("expected student %v (got nil)", expected)
 		}
-		if err := c.Destroy(); err != nil {
-			return err
+		if actual.Name != expected.Name {
+			return fmt.Errorf("student has wrong name (expected '%s', got '%s')", expected.Name, actual.Name)
+		} else if actual.Age != expected.Age {
+			return fmt.Errorf("student has wrong age (expected '%d', got '%d')", expected.Age, actual.Age)
 		}
-		t.Logf("(leveldb) deleted data at %s", path)
 		return nil
 	}
 
-	fixture.Run()
+	tester := &CollectionTester[*Student]{
+		C: collection,
+
+		Cmp: cmp,
+		Data: map[string]*Student{
+			"annie": {Name: "Annie", Age: 32},
+			"ben":   {Name: "Ben", Age: 50},
+			"clive": {Name: "Clive", Age: 21},
+		},
+	}
+
+	named := func(name string, do func() error) func() {
+		return func() {
+			if err := do(); err != nil {
+				t.Errorf("failed test %q: %v", name, err)
+			} else {
+				t.Logf("passed test %q", name)
+			}
+		}
+	}
+
+	sequence := []func(){
+		named("init 1", tester.Init),
+		named("has", tester.Has),
+		named("get", tester.Get),
+		named("getAll 1", tester.GetAll),
+		named("deleteOne 1", tester.DeleteOne),
+		named("deleteOne 2", tester.DeleteOne),
+		named("init 2", tester.Init),
+		named("close 1", tester.Close),
+		named("open", tester.Open),
+		named("getAll 2", tester.GetAll),
+		named("iterCount", tester.IterCount),
+		named("iterSortKeys", tester.IterSortKeys),
+		named("deleteAll", tester.DeleteAll),
+		named("close", tester.Close),
+	}
+	for _, do := range sequence {
+		do()
+	}
 }

+ 39 - 1
memory.go

@@ -1,5 +1,7 @@
 package ezdb
 
+import "fmt"
+
 type MemoryCollection[T any] struct {
 	c Collection[T]
 	m map[string]T
@@ -18,6 +20,12 @@ func (c *MemoryCollection[T]) Close() error {
 	return nil
 }
 
+func (c *MemoryCollection[T]) Count() int {
+	iter := c.Iter()
+	defer iter.Release()
+	return iter.Count()
+}
+
 func (c *MemoryCollection[T]) Delete(key string) error {
 	if !c.open {
 		return ErrClosed
@@ -34,6 +42,20 @@ func (c *MemoryCollection[T]) Delete(key string) error {
 	return nil
 }
 
+func (c *MemoryCollection[T]) DeleteAll() error {
+	iter := c.Iter()
+	defer iter.Release()
+
+	keys := iter.GetAllKeys()
+	for _, key := range keys {
+		if err := c.Delete(key); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 func (c *MemoryCollection[T]) Get(key string) (T, error) {
 	if !c.open {
 		return c.m[""], ErrClosed
@@ -46,6 +68,20 @@ func (c *MemoryCollection[T]) Get(key string) (T, error) {
 	return c.m[""], ErrNotFound
 }
 
+func (c *MemoryCollection[T]) GetAll() (map[string]T, error) {
+	iter := c.Iter()
+	defer iter.Release()
+
+	return iter.GetAll()
+}
+
+func (c *MemoryCollection[T]) GetAllKeys() []string {
+	iter := c.Iter()
+	defer iter.Release()
+
+	return iter.GetAllKeys()
+}
+
 func (c *MemoryCollection[T]) Has(key string) (bool, error) {
 	if !c.open {
 		return false, ErrClosed
@@ -77,6 +113,7 @@ func (c *MemoryCollection[T]) Open() error {
 		if err != nil {
 			return err
 		}
+		fmt.Println(all)
 
 		c.m = all
 	} else {
@@ -112,7 +149,8 @@ func (c *MemoryCollection[T]) Put(key string, value T) error {
 //
 // If the collection c is non-nil, it will be used as a persistence backend.
 //
-// If T is a pointer type, the same pointer will be used whenever a document is read from this collection, so you should take care to treat documents as immutable.
+// If T is a pointer type, the same pointer will be used whenever a document is read from this collection.
+// Take care not to modify documents retrieved from a memory collection.
 func Memory[T any](c Collection[T]) *MemoryCollection[T] {
 	return &MemoryCollection[T]{
 		c: c,

+ 12 - 8
memory_iter.go

@@ -23,7 +23,7 @@ func (i *MemoryIterator[T]) Filter(f FilterFunc[T]) Iterator[T] {
 
 	k := []string{}
 	m := map[string]T{}
-	i.reset()
+	i.Reset()
 	for i.Next() {
 		key, value, _ := i.Get()
 		if f(key, value) {
@@ -57,7 +57,7 @@ func (i *MemoryIterator[T]) GetAll() (map[string]T, error) {
 		return m, ErrReleased
 	}
 
-	i.reset()
+	i.Reset()
 	for i.Next() {
 		key, value, _ := i.Get()
 		m[key] = value
@@ -71,7 +71,7 @@ func (i *MemoryIterator[T]) GetAllKeys() []string {
 		return keys
 	}
 
-	i.reset()
+	i.Reset()
 	for i.Next() {
 		keys = append(keys, i.Key())
 	}
@@ -98,7 +98,7 @@ func (i *MemoryIterator[T]) Next() bool {
 		return false
 	}
 
-	hasNext := i.pos+1 <= i.Count()
+	hasNext := i.pos+1 < i.Count()
 	if hasNext {
 		i.pos++
 	}
@@ -127,6 +127,10 @@ func (i *MemoryIterator[T]) Release() {
 	}
 }
 
+func (i *MemoryIterator[T]) Reset() {
+	i.pos = -1
+}
+
 func (i *MemoryIterator[T]) Sort(f SortFunc[T]) Iterator[T] {
 	if i.released {
 		return i
@@ -162,10 +166,10 @@ func (i *MemoryIterator[T]) Value() (T, error) {
 	return i.m[key], nil
 }
 
-func (i *MemoryIterator[T]) reset() {
-	i.pos = -1
-}
-
+// MemoryIter creates an in-memory iterator for any data.
+// k must have a direct relation to m, representing the sorting of keys in the map, otherwise results may be unpredictable.
+//
+// If the iterator prev is non-nil, it will be released when this iterator is released.
 func MemoryIter[T any](m map[string]T, k []string, prev Iterator[T]) *MemoryIterator[T] {
 	i := &MemoryIterator[T]{
 		k: []string{},

+ 59 - 6
memory_test.go

@@ -1,14 +1,67 @@
 package ezdb
 
-import "testing"
+import (
+	"fmt"
+	"testing"
+)
 
 func TestMemory(t *testing.T) {
-	c := Memory[*Student](nil)
+	type Student struct {
+		Name string `json:"name"`
+		Age  int    `json:"age"`
+	}
+
+	collection := Memory[*Student](nil)
+
+	cmp := func(expected, actual *Student) error {
+		if expected == nil && actual != nil {
+			return fmt.Errorf("expected no student (got %v)", actual)
+		} else if expected != nil && actual == nil {
+			return fmt.Errorf("expected student %v (got nil)", expected)
+		}
+		if actual.Name != expected.Name {
+			return fmt.Errorf("student has wrong name (expected '%s', got '%s')", expected.Name, actual.Name)
+		} else if actual.Age != expected.Age {
+			return fmt.Errorf("student has wrong age (expected '%d', got '%d')", expected.Age, actual.Age)
+		}
+		return nil
+	}
+
+	tester := &CollectionTester[*Student]{
+		C: collection,
 
-	fixture := &CollectionTest{
-		C: c,
-		T: t,
+		Cmp: cmp,
+		Data: map[string]*Student{
+			"annie": {Name: "Annie", Age: 32},
+			"ben":   {Name: "Ben", Age: 50},
+			"clive": {Name: "Clive", Age: 21},
+		},
 	}
 
-	fixture.Run()
+	named := func(name string, do func() error) func() {
+		return func() {
+			if err := do(); err != nil {
+				t.Errorf("failed test %q: %v", name, err)
+			} else {
+				t.Logf("passed test %q", name)
+			}
+		}
+	}
+
+	sequence := []func(){
+		named("init 1", tester.Init),
+		named("has", tester.Has),
+		named("get", tester.Get),
+		named("getAll", tester.GetAll),
+		named("deleteOne 1", tester.DeleteOne),
+		named("deleteOne 2", tester.DeleteOne),
+		named("init 2", tester.Init),
+		named("iterCount", tester.IterCount),
+		named("iterSortKeys", tester.IterSortKeys),
+		named("deleteAll", tester.DeleteAll),
+		named("close", tester.Close),
+	}
+	for _, do := range sequence {
+		do()
+	}
 }