diff options
Diffstat (limited to 'reachability/unit_test.go')
| -rw-r--r-- | reachability/unit_test.go | 246 |
1 files changed, 46 insertions, 200 deletions
diff --git a/reachability/unit_test.go b/reachability/unit_test.go index 2fef2b48..dea6d38b 100644 --- a/reachability/unit_test.go +++ b/reachability/unit_test.go @@ -1,109 +1,40 @@ package reachability_test import ( - "bytes" "errors" "fmt" - "io" "maps" "slices" "testing" + giterrors "codeberg.org/lindenii/furgit/errors" "codeberg.org/lindenii/furgit/internal/testgit" "codeberg.org/lindenii/furgit/object" - "codeberg.org/lindenii/furgit/objectheader" "codeberg.org/lindenii/furgit/objectid" - "codeberg.org/lindenii/furgit/objectstore" + "codeberg.org/lindenii/furgit/objectstore/memory" "codeberg.org/lindenii/furgit/objecttype" "codeberg.org/lindenii/furgit/reachability" ) -type storeObject struct { - ty objecttype.Type - content []byte -} - type memStore struct { - algo objectid.Algorithm - objects map[objectid.ObjectID]storeObject + *memory.Store + readBytesByObjectID map[objectid.ObjectID]int } -func newMemStore(algo objectid.Algorithm) *memStore { +// newCountingMemStore builds one in-memory store that records content-read +// counts by object ID. +func newCountingMemStore(algo objectid.Algorithm) *memStore { return &memStore{ - algo: algo, - objects: make(map[objectid.ObjectID]storeObject), + Store: memory.New(algo), readBytesByObjectID: make(map[objectid.ObjectID]int), } } -func (store *memStore) ReadBytesFull(id objectid.ObjectID) ([]byte, error) { - obj, ok := store.objects[id] - if !ok { - return nil, objectstore.ErrObjectNotFound - } - - header, ok := objectheader.Encode(obj.ty, int64(len(obj.content))) - if !ok { - panic("failed to encode object header") - } - - raw := make([]byte, len(header)+len(obj.content)) - copy(raw, header) - copy(raw[len(header):], obj.content) - - return raw, nil -} - func (store *memStore) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) { - obj, ok := store.objects[id] - if !ok { - return objecttype.TypeInvalid, nil, objectstore.ErrObjectNotFound - } - store.readBytesByObjectID[id]++ - return obj.ty, append([]byte(nil), obj.content...), nil -} - -func (store *memStore) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) { - raw, err := store.ReadBytesFull(id) - if err != nil { - return nil, err - } - - return io.NopCloser(bytes.NewReader(raw)), nil -} - -func (store *memStore) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) { - ty, content, err := store.ReadBytesContent(id) - if err != nil { - return objecttype.TypeInvalid, 0, nil, err - } - - return ty, int64(len(content)), io.NopCloser(bytes.NewReader(content)), nil -} - -func (store *memStore) ReadSize(id objectid.ObjectID) (int64, error) { - _, size, err := store.ReadHeader(id) - if err != nil { - return 0, err - } - - return size, nil -} - -func (store *memStore) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) { - obj, ok := store.objects[id] - if !ok { - return objecttype.TypeInvalid, 0, objectstore.ErrObjectNotFound - } - - return obj.ty, int64(len(obj.content)), nil -} - -func (store *memStore) Close() error { - return nil + return store.Store.ReadBytesContent(id) } func commitBody(tree objectid.ObjectID, parents ...objectid.ObjectID) []byte { @@ -151,17 +82,17 @@ func TestWalkDomainCommitsIncludesTagNodes(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + store := newCountingMemStore(algo) + blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n")) + tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("f"), ID: blob, }}})) - commit1 := store.addObject(objecttype.TypeCommit, commitBody(tree)) - commit2 := store.addObject(objecttype.TypeCommit, commitBody(tree, commit1)) - tag1 := store.addObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit)) - tag2 := store.addObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag)) + commit1 := store.AddObject(objecttype.TypeCommit, commitBody(tree)) + commit2 := store.AddObject(objecttype.TypeCommit, commitBody(tree, commit1)) + tag1 := store.AddObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit)) + tag2 := store.AddObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag)) r := reachability.New(store) walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag2: {}}) @@ -186,14 +117,14 @@ func TestWalkExcludesHavesCompletely(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + store := newCountingMemStore(algo) + blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n")) + tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("f"), ID: blob, }}})) - commit := store.addObject(objecttype.TypeCommit, commitBody(tree)) + commit := store.AddObject(objecttype.TypeCommit, commitBody(tree)) r := reachability.New(store) walk := r.Walk(reachability.DomainCommits, map[objectid.ObjectID]struct{}{commit: {}}, map[objectid.ObjectID]struct{}{commit: {}}) @@ -215,14 +146,14 @@ func TestWalkDomainCommitsRejectsNonCommitRootAfterPeel(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + store := newCountingMemStore(algo) + blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n")) + tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("f"), ID: blob, }}})) - tag := store.addObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree)) + tag := store.AddObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree)) r := reachability.New(store) walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag: {}}) @@ -233,7 +164,7 @@ func TestWalkDomainCommitsRejectsNonCommitRootAfterPeel(t *testing.T) { t.Fatal("expected error") } - var typeErr *reachability.ObjectTypeError + var typeErr *giterrors.ObjectTypeError if !errors.As(err, &typeErr) { t.Fatalf("expected ObjectTypeError, got %T (%v)", err, err) } @@ -248,17 +179,17 @@ func TestWalkDomainCommitsHaveTagStopsTraversal(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + store := newCountingMemStore(algo) + blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n")) + tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("f"), ID: blob, }}})) - commit1 := store.addObject(objecttype.TypeCommit, commitBody(tree)) - commit2 := store.addObject(objecttype.TypeCommit, commitBody(tree, commit1)) - tag1 := store.addObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit)) - tag2 := store.addObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag)) + commit1 := store.AddObject(objecttype.TypeCommit, commitBody(tree)) + commit2 := store.AddObject(objecttype.TypeCommit, commitBody(tree, commit1)) + tag1 := store.AddObject(objecttype.TypeTag, tagBody(commit2, objecttype.TypeCommit)) + tag2 := store.AddObject(objecttype.TypeTag, tagBody(tag1, objecttype.TypeTag)) r := reachability.New(store) walk := r.Walk( @@ -287,23 +218,23 @@ func TestWalkDomainObjectsRecursesTreesAndSkipsBlobContentReads(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) + store := newCountingMemStore(algo) - blob1 := store.addObject(objecttype.TypeBlob, []byte("b1\n")) - blob2 := store.addObject(objecttype.TypeBlob, []byte("b2\n")) - gitlinkTarget := store.algo.Sum([]byte("external-submodule")) + blob1 := store.AddObject(objecttype.TypeBlob, []byte("b1\n")) + blob2 := store.AddObject(objecttype.TypeBlob, []byte("b2\n")) + gitlinkTarget := store.Algorithm().Sum([]byte("external-submodule")) - subtree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + subtree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("nested"), ID: blob2, }}})) - rootTree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{ + rootTree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{ {Mode: object.FileModeRegular, Name: []byte("a"), ID: blob1}, {Mode: object.FileModeDir, Name: []byte("dir"), ID: subtree}, {Mode: object.FileModeGitlink, Name: []byte("submodule"), ID: gitlinkTarget}, }})) - commit := store.addObject(objecttype.TypeCommit, commitBody(rootTree)) + commit := store.AddObject(objecttype.TypeCommit, commitBody(rootTree)) r := reachability.New(store) walk := r.Walk(reachability.DomainObjects, nil, map[objectid.ObjectID]struct{}{commit: {}}) @@ -332,15 +263,15 @@ func TestCheckConnectedReturnsConcreteMissingObject(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ + store := newCountingMemStore(algo) + blob := store.AddObject(objecttype.TypeBlob, []byte("blob\n")) + tree := store.AddObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ Mode: object.FileModeRegular, Name: []byte("f"), ID: blob, }}})) - missingParent := store.algo.Sum([]byte("missing-parent")) - commit := store.addObject(objecttype.TypeCommit, commitBody(tree, missingParent)) + missingParent := store.Algorithm().Sum([]byte("missing-parent")) + commit := store.AddObject(objecttype.TypeCommit, commitBody(tree, missingParent)) r := reachability.New(store) @@ -349,7 +280,7 @@ func TestCheckConnectedReturnsConcreteMissingObject(t *testing.T) { t.Fatal("expected error") } - var missing *reachability.ObjectMissingError + var missing *giterrors.ObjectMissingError if !errors.As(err, &missing) { t.Fatalf("expected ObjectMissingError, got %T (%v)", err, err) } @@ -364,7 +295,7 @@ func TestWalkInvalidDomainReturnsPlainError(t *testing.T) { t.Parallel() testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - r := reachability.New(newMemStore(algo)) + r := reachability.New(newCountingMemStore(algo)) walk := r.Walk(reachability.Domain(99), nil, nil) _ = collectSeq(walk.Seq()) @@ -376,78 +307,6 @@ func TestWalkInvalidDomainReturnsPlainError(t *testing.T) { }) } -func TestIsAncestor(t *testing.T) { - t.Parallel() - - testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ - Mode: object.FileModeRegular, - Name: []byte("f"), - ID: blob, - }}})) - c1 := store.addObject(objecttype.TypeCommit, commitBody(tree)) - c2 := store.addObject(objecttype.TypeCommit, commitBody(tree, c1)) - otherBlob := store.addObject(objecttype.TypeBlob, []byte("other-blob\n")) - otherTree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ - Mode: object.FileModeRegular, - Name: []byte("g"), - ID: otherBlob, - }}})) - c3 := store.addObject(objecttype.TypeCommit, commitBody(otherTree)) - tag := store.addObject(objecttype.TypeTag, tagBody(c2, objecttype.TypeCommit)) - - r := reachability.New(store) - - ok, err := r.IsAncestor(c1, tag) - if err != nil { - t.Fatalf("IsAncestor(c1, tag): %v", err) - } - - if !ok { - t.Fatal("expected c1 to be ancestor of tag->c2") - } - - ok, err = r.IsAncestor(c3, c2) - if err != nil { - t.Fatalf("IsAncestor(c3, c2): %v", err) - } - - if ok { - t.Fatal("did not expect c3 to be ancestor of c2") - } - }) -} - -func TestIsAncestorRejectsNonCommitAfterPeel(t *testing.T) { - t.Parallel() - - testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper - store := newMemStore(algo) - blob := store.addObject(objecttype.TypeBlob, []byte("blob\n")) - tree := store.addObject(objecttype.TypeTree, mustSerializeTree(t, &object.Tree{Entries: []object.TreeEntry{{ - Mode: object.FileModeRegular, - Name: []byte("f"), - ID: blob, - }}})) - commit := store.addObject(objecttype.TypeCommit, commitBody(tree)) - tagToTree := store.addObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree)) - - r := reachability.New(store) - - _, err := r.IsAncestor(commit, tagToTree) - if err == nil { - t.Fatal("expected error") - } - - var typeErr *reachability.ObjectTypeError - if !errors.As(err, &typeErr) { - t.Fatalf("expected ObjectTypeError, got %T (%v)", err, err) - } - }) -} - func mustSerializeTree(tb testing.TB, tree *object.Tree) []byte { tb.Helper() @@ -458,16 +317,3 @@ func mustSerializeTree(tb testing.TB, tree *object.Tree) []byte { return body } - -func (store *memStore) addObject(ty objecttype.Type, body []byte) objectid.ObjectID { - header, ok := objectheader.Encode(ty, int64(len(body))) - if !ok { - panic("failed to encode object header") - } - - raw := append(append([]byte(nil), header...), body...) - id := store.algo.Sum(raw) - store.objects[id] = storeObject{ty: ty, content: append([]byte(nil), body...)} - - return id -} |
