Преглед изворни кода

improve tests, clean up some inadequately tested code

Aneurin Barker Snook пре 11 месеци
родитељ
комит
f26b55327f
12 измењених фајлова са 428 додато и 370 уклоњено
  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()
+	}
 }