entity: readAll and more testing

Michael MurΓ© created

Change summary

bug/bug_actions.go                |   3 
bug/bug_actions_test.go           |   8 +-
cache/repo_cache_test.go          |   2 
entity/dag/common_test.go         |  38 +++++++++--
entity/dag/entity.go              |  48 ++++++++++++++
entity/dag/entity_actions.go      |   8 -
entity/dag/entity_actions_test.go | 110 +++++++++++++++++++++++++++++++++
repository/mock_repo.go           |   4 
repository/repo.go                |   1 
repository/repo_testing.go        |   3 
10 files changed, 203 insertions(+), 22 deletions(-)

Detailed changes

bug/bug_actions.go πŸ”—

@@ -4,10 +4,11 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/pkg/errors"
+
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/repository"
-	"github.com/pkg/errors"
 )
 
 // Fetch retrieve updates from a remote

bug/bug_actions_test.go πŸ”—

@@ -12,7 +12,7 @@ import (
 )
 
 func TestPushPull(t *testing.T) {
-	repoA, repoB, remote := repository.SetupReposAndRemote()
+	repoA, repoB, remote := repository.SetupGoGitReposAndRemote()
 	defer repository.CleanupTestRepos(repoA, repoB, remote)
 
 	reneA, err := identity.NewIdentity(repoA, "RenΓ© Descartes", "rene@descartes.fr")
@@ -90,7 +90,7 @@ func BenchmarkRebaseTheirs(b *testing.B) {
 }
 
 func _RebaseTheirs(t testing.TB) {
-	repoA, repoB, remote := repository.SetupReposAndRemote()
+	repoA, repoB, remote := repository.SetupGoGitReposAndRemote()
 	defer repository.CleanupTestRepos(repoA, repoB, remote)
 
 	reneA, err := identity.NewIdentity(repoA, "RenΓ© Descartes", "rene@descartes.fr")
@@ -171,7 +171,7 @@ func BenchmarkRebaseOurs(b *testing.B) {
 }
 
 func _RebaseOurs(t testing.TB) {
-	repoA, repoB, remote := repository.SetupReposAndRemote()
+	repoA, repoB, remote := repository.SetupGoGitReposAndRemote()
 	defer repository.CleanupTestRepos(repoA, repoB, remote)
 
 	reneA, err := identity.NewIdentity(repoA, "RenΓ© Descartes", "rene@descartes.fr")
@@ -263,7 +263,7 @@ func BenchmarkRebaseConflict(b *testing.B) {
 }
 
 func _RebaseConflict(t testing.TB) {
-	repoA, repoB, remote := repository.SetupReposAndRemote()
+	repoA, repoB, remote := repository.SetupGoGitReposAndRemote()
 	defer repository.CleanupTestRepos(repoA, repoB, remote)
 
 	reneA, err := identity.NewIdentity(repoA, "RenΓ© Descartes", "rene@descartes.fr")

cache/repo_cache_test.go πŸ”—

@@ -109,7 +109,7 @@ func TestCache(t *testing.T) {
 }
 
 func TestPushPull(t *testing.T) {
-	repoA, repoB, remote := repository.SetupReposAndRemote()
+	repoA, repoB, remote := repository.SetupGoGitReposAndRemote()
 	defer repository.CleanupTestRepos(repoA, repoB, remote)
 
 	cacheA, err := NewRepoCache(repoA)

entity/dag/common_test.go πŸ”—

@@ -26,16 +26,16 @@ func newOp1(author identity.Interface, field1 string) *op1 {
 	return &op1{author: author, OperationType: 1, Field1: field1}
 }
 
-func (o op1) Id() entity.Id {
+func (o *op1) Id() entity.Id {
 	data, _ := json.Marshal(o)
 	return entity.DeriveId(data)
 }
 
-func (o op1) Author() identity.Interface {
+func (o *op1) Author() identity.Interface {
 	return o.author
 }
 
-func (o op1) Validate() error { return nil }
+func (o *op1) Validate() error { return nil }
 
 type op2 struct {
 	author identity.Interface
@@ -48,16 +48,16 @@ func newOp2(author identity.Interface, field2 string) *op2 {
 	return &op2{author: author, OperationType: 2, Field2: field2}
 }
 
-func (o op2) Id() entity.Id {
+func (o *op2) Id() entity.Id {
 	data, _ := json.Marshal(o)
 	return entity.DeriveId(data)
 }
 
-func (o op2) Author() identity.Interface {
+func (o *op2) Author() identity.Interface {
 	return o.author
 }
 
-func (o op2) Validate() error { return nil }
+func (o *op2) Validate() error { return nil }
 
 func unmarshaler(author identity.Interface, raw json.RawMessage) (Operation, error) {
 	var t struct {
@@ -90,7 +90,31 @@ func unmarshaler(author identity.Interface, raw json.RawMessage) (Operation, err
 
 func makeTestContext() (repository.ClockedRepo, identity.Interface, identity.Interface, Definition) {
 	repo := repository.NewMockRepo()
+	id1, id2, def := makeTestContextInternal(repo)
+	return repo, id1, id2, def
+}
+
+func makeTestContextRemote() (repository.ClockedRepo, repository.ClockedRepo, repository.ClockedRepo, identity.Interface, identity.Interface, Definition) {
+	repoA := repository.CreateGoGitTestRepo(false)
+	repoB := repository.CreateGoGitTestRepo(false)
+	remote := repository.CreateGoGitTestRepo(true)
+
+	err := repoA.AddRemote("origin", remote.GetLocalRemote())
+	if err != nil {
+		panic(err)
+	}
 
+	err = repoB.AddRemote("origin", remote.GetLocalRemote())
+	if err != nil {
+		panic(err)
+	}
+
+	id1, id2, def := makeTestContextInternal(repoA)
+
+	return repoA, repoB, remote, id1, id2, def
+}
+
+func makeTestContextInternal(repo repository.ClockedRepo) (identity.Interface, identity.Interface, Definition) {
 	id1, err := identity.NewIdentity(repo, "name1", "email1")
 	if err != nil {
 		panic(err)
@@ -127,7 +151,7 @@ func makeTestContext() (repository.ClockedRepo, identity.Interface, identity.Int
 		formatVersion:        1,
 	}
 
-	return repo, id1, id2, def
+	return id1, id2, def
 }
 
 type identityResolverFunc func(id entity.Id) (identity.Interface, error)

entity/dag/entity.go πŸ”—

@@ -58,7 +58,7 @@ func New(definition Definition) *Entity {
 	}
 }
 
-// Read will read and decode a stored Entity from a repository
+// Read will read and decode a stored local Entity from a repository
 func Read(def Definition, repo repository.ClockedRepo, id entity.Id) (*Entity, error) {
 	if err := id.Validate(); err != nil {
 		return nil, errors.Wrap(err, "invalid id")
@@ -69,6 +69,17 @@ func Read(def Definition, repo repository.ClockedRepo, id entity.Id) (*Entity, e
 	return read(def, repo, ref)
 }
 
+// readRemote will read and decode a stored remote Entity from a repository
+func readRemote(def Definition, repo repository.ClockedRepo, remote string, id entity.Id) (*Entity, error) {
+	if err := id.Validate(); err != nil {
+		return nil, errors.Wrap(err, "invalid id")
+	}
+
+	ref := fmt.Sprintf("refs/remotes/%s/%s/%s", def.namespace, remote, id.String())
+
+	return read(def, repo, ref)
+}
+
 // read fetch from git and decode an Entity at an arbitrary git reference.
 func read(def Definition, repo repository.ClockedRepo, ref string) (*Entity, error) {
 	rootHash, err := repo.ResolveRef(ref)
@@ -232,6 +243,41 @@ func read(def Definition, repo repository.ClockedRepo, ref string) (*Entity, err
 	}, nil
 }
 
+type StreamedEntity struct {
+	Entity *Entity
+	Err    error
+}
+
+// ReadAll read and parse all local Entity
+func ReadAll(def Definition, repo repository.ClockedRepo) <-chan StreamedEntity {
+	out := make(chan StreamedEntity)
+
+	go func() {
+		defer close(out)
+
+		refPrefix := fmt.Sprintf("refs/%s/", def.namespace)
+
+		refs, err := repo.ListRefs(refPrefix)
+		if err != nil {
+			out <- StreamedEntity{Err: err}
+			return
+		}
+
+		for _, ref := range refs {
+			e, err := read(def, repo, ref)
+
+			if err != nil {
+				out <- StreamedEntity{Err: err}
+				return
+			}
+
+			out <- StreamedEntity{Entity: e}
+		}
+	}()
+
+	return out
+}
+
 // Id return the Entity identifier
 func (e *Entity) Id() entity.Id {
 	// id is the id of the first operation

entity/dag/entity_actions.go πŸ”—

@@ -10,8 +10,8 @@ import (
 )
 
 // ListLocalIds list all the available local Entity's Id
-func ListLocalIds(typename string, repo repository.RepoData) ([]entity.Id, error) {
-	refs, err := repo.ListRefs(fmt.Sprintf("refs/%s/", typename))
+func ListLocalIds(def Definition, repo repository.RepoData) ([]entity.Id, error) {
+	refs, err := repo.ListRefs(fmt.Sprintf("refs/%s/", def.namespace))
 	if err != nil {
 		return nil, err
 	}
@@ -75,10 +75,6 @@ func Pull(def Definition, repo repository.ClockedRepo, remote string) error {
 func MergeAll(def Definition, repo repository.ClockedRepo, remote string) <-chan entity.MergeResult {
 	out := make(chan entity.MergeResult)
 
-	// no caching for the merge, we load everything from git even if that means multiple
-	// copy of the same entity in memory. The cache layer will intercept the results to
-	// invalidate entities if necessary.
-
 	go func() {
 		defer close(out)
 

entity/dag/entity_actions_test.go πŸ”—

@@ -0,0 +1,110 @@
+package dag
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/MichaelMure/git-bug/identity"
+	"github.com/MichaelMure/git-bug/repository"
+)
+
+func allEntities(t testing.TB, bugs <-chan StreamedEntity) []*Entity {
+	var result []*Entity
+	for streamed := range bugs {
+		if streamed.Err != nil {
+			t.Fatal(streamed.Err)
+		}
+		result = append(result, streamed.Entity)
+	}
+	return result
+}
+
+func TestPushPull(t *testing.T) {
+	repoA, repoB, remote, id1, id2, def := makeTestContextRemote()
+	defer repository.CleanupTestRepos(repoA, repoB, remote)
+
+	// distribute the identities
+	_, err := identity.Push(repoA, "origin")
+	require.NoError(t, err)
+	err = identity.Pull(repoB, "origin")
+	require.NoError(t, err)
+
+	// A --> remote --> B
+	entity := New(def)
+	entity.Append(newOp1(id1, "foo"))
+
+	err = entity.Commit(repoA)
+	require.NoError(t, err)
+
+	_, err = Push(def, repoA, "origin")
+	require.NoError(t, err)
+
+	err = Pull(def, repoB, "origin")
+	require.NoError(t, err)
+
+	entities := allEntities(t, ReadAll(def, repoB))
+	require.Len(t, entities, 1)
+
+	// B --> remote --> A
+	entity = New(def)
+	entity.Append(newOp2(id2, "bar"))
+
+	err = entity.Commit(repoB)
+	require.NoError(t, err)
+
+	_, err = Push(def, repoB, "origin")
+	require.NoError(t, err)
+
+	err = Pull(def, repoA, "origin")
+	require.NoError(t, err)
+
+	entities = allEntities(t, ReadAll(def, repoB))
+	require.Len(t, entities, 2)
+}
+
+func TestListLocalIds(t *testing.T) {
+	repoA, repoB, remote, id1, id2, def := makeTestContextRemote()
+	defer repository.CleanupTestRepos(repoA, repoB, remote)
+
+	// distribute the identities
+	_, err := identity.Push(repoA, "origin")
+	require.NoError(t, err)
+	err = identity.Pull(repoB, "origin")
+	require.NoError(t, err)
+
+	// A --> remote --> B
+	entity := New(def)
+	entity.Append(newOp1(id1, "foo"))
+	err = entity.Commit(repoA)
+	require.NoError(t, err)
+
+	entity = New(def)
+	entity.Append(newOp2(id2, "bar"))
+	err = entity.Commit(repoA)
+	require.NoError(t, err)
+
+	listLocalIds(t, def, repoA, 2)
+	listLocalIds(t, def, repoB, 0)
+
+	_, err = Push(def, repoA, "origin")
+	require.NoError(t, err)
+
+	_, err = Fetch(def, repoB, "origin")
+	require.NoError(t, err)
+
+	listLocalIds(t, def, repoA, 2)
+	listLocalIds(t, def, repoB, 0)
+
+	err = Pull(def, repoB, "origin")
+	require.NoError(t, err)
+
+	listLocalIds(t, def, repoA, 2)
+	listLocalIds(t, def, repoB, 2)
+}
+
+func listLocalIds(t *testing.T, def Definition, repo repository.RepoData, expectedCount int) {
+	ids, err := ListLocalIds(def, repo)
+	require.NoError(t, err)
+	require.Len(t, ids, expectedCount)
+}

repository/mock_repo.go πŸ”—

@@ -202,12 +202,12 @@ func NewMockRepoData() *mockRepoData {
 }
 
 func (r *mockRepoData) FetchRefs(remote string, refSpec string) (string, error) {
-	return "", nil
+	panic("implement me")
 }
 
 // PushRefs push git refs to a remote
 func (r *mockRepoData) PushRefs(remote string, refSpec string) (string, error) {
-	return "", nil
+	panic("implement me")
 }
 
 func (r *mockRepoData) StoreData(data []byte) (Hash, error) {

repository/repo.go πŸ”—

@@ -130,6 +130,7 @@ type RepoData interface {
 	ReadCommit(hash Hash) (Commit, error)
 
 	// GetTreeHash return the git tree hash referenced in a commit
+	// Deprecated
 	GetTreeHash(commit Hash) (Hash, error)
 
 	// ResolveRef returns the hash of the target commit of the given ref

repository/repo_testing.go πŸ”—

@@ -10,6 +10,9 @@ import (
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
 
+// TODO: add tests for RepoBleve
+// TODO: add tests for RepoStorage
+
 func CleanupTestRepos(repos ...Repo) {
 	var firstErr error
 	for _, repo := range repos {