aboutsummaryrefslogtreecommitdiff
path: root/reachability/reachability_unit_test.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-03 22:49:24 +0800
committerGravatar Runxi Yu2026-03-03 22:52:47 +0800
commit6378da9dcf8d991a00ee410bb5408231861d90c0 (patch)
tree5427fbc11b79a683598c02cdcdc81048423c92f2 /reachability/reachability_unit_test.go
parentconfig: Fix lints (diff)
reachability: Refactor v0.1.41
Diffstat (limited to 'reachability/reachability_unit_test.go')
-rw-r--r--reachability/reachability_unit_test.go422
1 files changed, 0 insertions, 422 deletions
diff --git a/reachability/reachability_unit_test.go b/reachability/reachability_unit_test.go
deleted file mode 100644
index ec177938..00000000
--- a/reachability/reachability_unit_test.go
+++ /dev/null
@@ -1,422 +0,0 @@
-package reachability_test
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "maps"
- "slices"
- "testing"
-
- "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/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
- readBytesByObjectID map[objectid.ObjectID]int
-}
-
-func newMemStore(algo objectid.Algorithm) *memStore {
- return &memStore{
- algo: algo,
- objects: make(map[objectid.ObjectID]storeObject),
- 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
-}
-
-func commitBody(tree objectid.ObjectID, parents ...objectid.ObjectID) []byte {
- buf := fmt.Appendf(nil, "tree %s\n", tree.String())
- for _, parent := range parents {
- buf = append(buf, fmt.Appendf(nil, "parent %s\n", parent.String())...)
- }
- buf = append(buf, []byte("\nmsg\n")...)
- return buf
-}
-
-func tagBody(target objectid.ObjectID, targetType objecttype.Type) []byte {
- targetName, ok := objecttype.Name(targetType)
- if !ok {
- panic("invalid tag target type")
- }
- return fmt.Appendf(nil, "object %s\ntype %s\ntag t\n\nmsg\n", target.String(), targetName)
-}
-
-func collectSeq(seq func(func(objectid.ObjectID) bool)) []objectid.ObjectID {
- var out []objectid.ObjectID
- seq(func(id objectid.ObjectID) bool {
- out = append(out, id)
- return true
- })
- return out
-}
-
-func toSet(ids []objectid.ObjectID) map[objectid.ObjectID]struct{} {
- set := make(map[objectid.ObjectID]struct{}, len(ids))
- for _, id := range ids {
- set[id] = struct{}{}
- }
- return set
-}
-
-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{{
- 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))
-
- r := reachability.New(store)
- walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag2: {}})
- got := collectSeq(walk.Seq())
- if err := walk.Err(); err != nil {
- t.Fatalf("walk.Err(): %v", err)
- }
-
- gotSet := toSet(got)
- wantSet := map[objectid.ObjectID]struct{}{tag2: {}, tag1: {}, commit2: {}, commit1: {}}
- if !maps.Equal(gotSet, wantSet) {
- t.Fatalf("walk output mismatch: got %v, want %v", slices.Collect(maps.Keys(gotSet)), slices.Collect(maps.Keys(wantSet)))
- }
- })
-}
-
-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{{
- Mode: object.FileModeRegular,
- Name: []byte("f"),
- ID: blob,
- }}}))
- 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: {}})
- got := collectSeq(walk.Seq())
- if err := walk.Err(); err != nil {
- t.Fatalf("walk.Err(): %v", err)
- }
- if len(got) != 0 {
- t.Fatalf("expected empty output, got %v", got)
- }
- })
-}
-
-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{{
- Mode: object.FileModeRegular,
- Name: []byte("f"),
- ID: blob,
- }}}))
- tag := store.addObject(objecttype.TypeTag, tagBody(tree, objecttype.TypeTree))
-
- r := reachability.New(store)
- walk := r.Walk(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{tag: {}})
- _ = collectSeq(walk.Seq())
- err := walk.Err()
- if err == nil {
- t.Fatal("expected error")
- }
- var typeErr *reachability.ErrObjectType
- if !errors.As(err, &typeErr) {
- t.Fatalf("expected ErrObjectType, got %T (%v)", err, err)
- }
- if typeErr.Got != objecttype.TypeTree || typeErr.Want != objecttype.TypeCommit {
- t.Fatalf("unexpected type error: %+v", typeErr)
- }
- })
-}
-
-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{{
- 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))
-
- r := reachability.New(store)
- walk := r.Walk(
- reachability.DomainCommits,
- map[objectid.ObjectID]struct{}{tag1: {}},
- map[objectid.ObjectID]struct{}{tag2: {}},
- )
- got := collectSeq(walk.Seq())
- if err := walk.Err(); err != nil {
- t.Fatalf("walk.Err(): %v", err)
- }
-
- gotSet := toSet(got)
- wantSet := map[objectid.ObjectID]struct{}{tag2: {}}
- if !maps.Equal(gotSet, wantSet) {
- t.Fatalf("walk output mismatch: got %v, want %v", slices.Collect(maps.Keys(gotSet)), slices.Collect(maps.Keys(wantSet)))
- }
- })
-}
-
-func TestWalkDomainObjectsRecursesTreesAndSkipsBlobContentReads(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- store := newMemStore(algo)
-
- blob1 := store.addObject(objecttype.TypeBlob, []byte("b1\n"))
- blob2 := store.addObject(objecttype.TypeBlob, []byte("b2\n"))
- gitlinkTarget := store.algo.Sum([]byte("external-submodule"))
-
- 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{
- {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))
-
- r := reachability.New(store)
- walk := r.Walk(reachability.DomainObjects, nil, map[objectid.ObjectID]struct{}{commit: {}})
- got := collectSeq(walk.Seq())
- if err := walk.Err(); err != nil {
- t.Fatalf("walk.Err(): %v", err)
- }
-
- gotSet := toSet(got)
- wantSet := map[objectid.ObjectID]struct{}{commit: {}, rootTree: {}, subtree: {}, blob1: {}, blob2: {}}
- if !maps.Equal(gotSet, wantSet) {
- t.Fatalf("walk output mismatch: got %v, want %v", slices.Collect(maps.Keys(gotSet)), slices.Collect(maps.Keys(wantSet)))
- }
- if store.readBytesByObjectID[blob1] != 0 || store.readBytesByObjectID[blob2] != 0 {
- t.Fatalf("blob contents should not be read; counts: blob1=%d blob2=%d", store.readBytesByObjectID[blob1], store.readBytesByObjectID[blob2])
- }
- })
-}
-
-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{{
- Mode: object.FileModeRegular,
- Name: []byte("f"),
- ID: blob,
- }}}))
- missingParent := store.algo.Sum([]byte("missing-parent"))
- commit := store.addObject(objecttype.TypeCommit, commitBody(tree, missingParent))
-
- r := reachability.New(store)
- err := r.CheckConnected(reachability.DomainCommits, nil, map[objectid.ObjectID]struct{}{commit: {}})
- if err == nil {
- t.Fatal("expected error")
- }
- var missing *reachability.ErrObjectMissing
- if !errors.As(err, &missing) {
- t.Fatalf("expected ErrObjectMissing, got %T (%v)", err, err)
- }
- if missing.OID != missingParent {
- t.Fatalf("unexpected missing oid: got %s want %s", missing.OID, missingParent)
- }
- })
-}
-
-func TestWalkInvalidDomainReturnsPlainError(t *testing.T) {
- t.Parallel()
-
- testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
- r := reachability.New(newMemStore(algo))
- walk := r.Walk(reachability.Domain(99), nil, nil)
- _ = collectSeq(walk.Seq())
- if err := walk.Err(); err == nil {
- t.Fatal("expected error")
- }
- })
-}
-
-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.ErrObjectType
- if !errors.As(err, &typeErr) {
- t.Fatalf("expected ErrObjectType, got %T (%v)", err, err)
- }
- })
-}
-
-func mustSerializeTree(tb testing.TB, tree *object.Tree) []byte {
- tb.Helper()
- body, err := tree.SerializeWithoutHeader()
- if err != nil {
- tb.Fatalf("SerializeWithoutHeader: %v", err)
- }
- 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
-}