aboutsummaryrefslogblamecommitdiffstats
path: root/repository/repo_testing.go
blob: 3dd702f458876c90c8562468573639cc2eb61283 (plain) (tree)
1
2
3
4
5
6
7
8
9


                  
                   
            

                 
                                                 
                                             

                                                     

 
                                                         

                                       
                                                  




                                                
                                                
 

                                                          
                                                              
                          
 

                                                            

                          



                                                             



                                                           
                                                            




                                                      
 



                                                    
 











                                                      


                               














                                                                                                  








                                                               













                                                  


                                            


















                                                      
 


















                                                  
         








                                                  


                                            





                                                   

                                                            


                                          




                                                        




                                                                                          


                                              












                                                       



                                                   












                                                                                  
                                                           
 

                                                          
 

                                                                
 
                  


                                              



                                              






























                                                                        
                                                                                                     

                               
                                                                                                     







                                                                                     
                                                                                                     
                               
 
                                                                                                     
                             

 









































                                                                          

                                                  



                                          





                                                       
                                               








                                                        






                                                  
 








                                                                                
package repository

import (
	"math/rand"
	"os"
	"testing"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/stretchr/testify/require"

	"github.com/MichaelMure/git-bug/util/lamport"
)

type RepoCreator func(t testing.TB, bare bool) TestedRepo

// Test suite for a Repo implementation
func RepoTest(t *testing.T, creator RepoCreator) {
	for bare, name := range map[bool]string{
		false: "Plain",
		true:  "Bare",
	} {
		t.Run(name, func(t *testing.T) {
			repo := creator(t, bare)

			t.Run("Data", func(t *testing.T) {
				RepoDataTest(t, repo)
				RepoDataSignatureTest(t, repo)
			})

			t.Run("Config", func(t *testing.T) {
				RepoConfigTest(t, repo)
			})

			t.Run("Storage", func(t *testing.T) {
				RepoStorageTest(t, repo)
			})

			t.Run("Index", func(t *testing.T) {
				RepoIndexTest(t, repo)
			})

			t.Run("Clocks", func(t *testing.T) {
				RepoClockTest(t, repo)
			})
		})
	}
}

// helper to test a RepoConfig
func RepoConfigTest(t *testing.T, repo RepoConfig) {
	testConfig(t, repo.LocalConfig())
}

func RepoStorageTest(t *testing.T, repo RepoStorage) {
	storage := repo.LocalStorage()

	err := storage.MkdirAll("foo/bar", 0755)
	require.NoError(t, err)

	f, err := storage.Create("foo/bar/foofoo")
	require.NoError(t, err)

	_, err = f.Write([]byte("hello"))
	require.NoError(t, err)

	err = f.Close()
	require.NoError(t, err)

	// remove all
	err = storage.RemoveAll(".")
	require.NoError(t, err)

	fi, err := storage.ReadDir(".")
	// a real FS would remove the root directory with RemoveAll and subsequent call would fail
	// a memory FS would still have a virtual root and subsequent call would succeed
	// not ideal, but will do for now
	if err == nil {
		require.Empty(t, fi)
	} else {
		require.True(t, os.IsNotExist(err))
	}
}

func randomHash() Hash {
	var letterRunes = "abcdef0123456789"
	b := make([]byte, idLengthSHA256)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return Hash(b)
}

// helper to test a RepoData
func RepoDataTest(t *testing.T, repo RepoData) {
	// Blob

	data := randomData()

	blobHash1, err := repo.StoreData(data)
	require.NoError(t, err)
	require.True(t, blobHash1.IsValid())

	blob1Read, err := repo.ReadData(blobHash1)
	require.NoError(t, err)
	require.Equal(t, data, blob1Read)

	_, err = repo.ReadData(randomHash())
	require.ErrorIs(t, err, ErrNotFound)

	// Tree

	blobHash2, err := repo.StoreData(randomData())
	require.NoError(t, err)
	blobHash3, err := repo.StoreData(randomData())
	require.NoError(t, err)

	tree1 := []TreeEntry{
		{
			ObjectType: Blob,
			Hash:       blobHash1,
			Name:       "blob1",
		},
		{
			ObjectType: Blob,
			Hash:       blobHash2,
			Name:       "blob2",
		},
	}

	treeHash1, err := repo.StoreTree(tree1)
	require.NoError(t, err)
	require.True(t, treeHash1.IsValid())

	tree1Read, err := repo.ReadTree(treeHash1)
	require.NoError(t, err)
	require.ElementsMatch(t, tree1, tree1Read)

	tree2 := []TreeEntry{
		{
			ObjectType: Tree,
			Hash:       treeHash1,
			Name:       "tree1",
		},
		{
			ObjectType: Blob,
			Hash:       blobHash3,
			Name:       "blob3",
		},
	}

	treeHash2, err := repo.StoreTree(tree2)
	require.NoError(t, err)
	require.True(t, treeHash2.IsValid())

	tree2Read, err := repo.ReadTree(treeHash2)
	require.NoError(t, err)
	require.ElementsMatch(t, tree2, tree2Read)

	_, err = repo.ReadTree(randomHash())
	require.ErrorIs(t, err, ErrNotFound)

	// Commit

	commit1, err := repo.StoreCommit(treeHash1)
	require.NoError(t, err)
	require.True(t, commit1.IsValid())

	// commit with a parent
	commit2, err := repo.StoreCommit(treeHash2, commit1)
	require.NoError(t, err)
	require.True(t, commit2.IsValid())

	// ReadTree should accept tree and commit hashes
	tree1read, err := repo.ReadTree(commit1)
	require.NoError(t, err)
	require.Equal(t, tree1read, tree1)

	c2, err := repo.ReadCommit(commit2)
	require.NoError(t, err)
	c2expected := Commit{Hash: commit2, Parents: []Hash{commit1}, TreeHash: treeHash2}
	require.Equal(t, c2expected, c2)

	_, err = repo.ReadCommit(randomHash())
	require.ErrorIs(t, err, ErrNotFound)

	// Ref

	exist1, err := repo.RefExist("refs/bugs/ref1")
	require.NoError(t, err)
	require.False(t, exist1)

	err = repo.UpdateRef("refs/bugs/ref1", commit2)
	require.NoError(t, err)

	exist1, err = repo.RefExist("refs/bugs/ref1")
	require.NoError(t, err)
	require.True(t, exist1)

	h, err := repo.ResolveRef("refs/bugs/ref1")
	require.NoError(t, err)
	require.Equal(t, commit2, h)

	ls, err := repo.ListRefs("refs/bugs")
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls)

	err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2")
	require.NoError(t, err)

	ls, err = repo.ListRefs("refs/bugs")
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls)

	commits, err := repo.ListCommits("refs/bugs/ref2")
	require.NoError(t, err)
	require.Equal(t, []Hash{commit1, commit2}, commits)

	_, err = repo.ResolveRef("/refs/bugs/refnotexist")
	require.ErrorIs(t, err, ErrNotFound)

	err = repo.CopyRef("/refs/bugs/refnotexist", "refs/foo")
	require.ErrorIs(t, err, ErrNotFound)

	// Cleanup

	err = repo.RemoveRef("refs/bugs/ref1")
	require.NoError(t, err)

	// RemoveRef is idempotent
	err = repo.RemoveRef("refs/bugs/ref1")
	require.NoError(t, err)
}

func RepoDataSignatureTest(t *testing.T, repo RepoData) {
	data := randomData()

	blobHash, err := repo.StoreData(data)
	require.NoError(t, err)

	treeHash, err := repo.StoreTree([]TreeEntry{
		{
			ObjectType: Blob,
			Hash:       blobHash,
			Name:       "blob",
		},
	})
	require.NoError(t, err)

	pgpEntity1, err := openpgp.NewEntity("", "", "", nil)
	require.NoError(t, err)
	keyring1 := openpgp.EntityList{pgpEntity1}

	pgpEntity2, err := openpgp.NewEntity("", "", "", nil)
	require.NoError(t, err)
	keyring2 := openpgp.EntityList{pgpEntity2}

	commitHash1, err := repo.StoreSignedCommit(treeHash, pgpEntity1)
	require.NoError(t, err)

	commit1, err := repo.ReadCommit(commitHash1)
	require.NoError(t, err)

	_, err = openpgp.CheckDetachedSignature(keyring1, commit1.SignedData, commit1.Signature, nil)
	require.NoError(t, err)

	_, err = openpgp.CheckDetachedSignature(keyring2, commit1.SignedData, commit1.Signature, nil)
	require.Error(t, err)

	commitHash2, err := repo.StoreSignedCommit(treeHash, pgpEntity1, commitHash1)
	require.NoError(t, err)

	commit2, err := repo.ReadCommit(commitHash2)
	require.NoError(t, err)

	_, err = openpgp.CheckDetachedSignature(keyring1, commit2.SignedData, commit2.Signature, nil)
	require.NoError(t, err)

	_, err = openpgp.CheckDetachedSignature(keyring2, commit2.SignedData, commit2.Signature, nil)
	require.Error(t, err)
}

func RepoIndexTest(t *testing.T, repo RepoIndex) {
	idx, err := repo.GetIndex("a")
	require.NoError(t, err)

	// simple indexing
	err = idx.IndexOne("id1", []string{"foo", "bar", "foobar barfoo"})
	require.NoError(t, err)

	// batched indexing
	indexer, closer := idx.IndexBatch()
	err = indexer("id2", []string{"hello", "foo bar"})
	require.NoError(t, err)
	err = indexer("id3", []string{"Hola", "Esta bien"})
	require.NoError(t, err)
	err = closer()
	require.NoError(t, err)

	// search
	res, err := idx.Search([]string{"foobar"})
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"id1"}, res)

	res, err = idx.Search([]string{"foo"})
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"id1", "id2"}, res)

	// re-indexing an item replace previous versions
	err = idx.IndexOne("id2", []string{"hello"})
	require.NoError(t, err)

	res, err = idx.Search([]string{"foo"})
	require.NoError(t, err)
	require.ElementsMatch(t, []string{"id1"}, res)

	err = idx.Clear()
	require.NoError(t, err)

	res, err = idx.Search([]string{"foo"})
	require.NoError(t, err)
	require.Empty(t, res)
}

// helper to test a RepoClock
func RepoClockTest(t *testing.T, repo RepoClock) {
	allClocks, err := repo.AllClocks()
	require.NoError(t, err)
	require.Len(t, allClocks, 0)

	clock, err := repo.GetOrCreateClock("foo")
	require.NoError(t, err)
	require.Equal(t, lamport.Time(1), clock.Time())

	time, err := clock.Increment()
	require.NoError(t, err)
	require.Equal(t, lamport.Time(2), time)
	require.Equal(t, lamport.Time(2), clock.Time())

	clock2, err := repo.GetOrCreateClock("foo")
	require.NoError(t, err)
	require.Equal(t, lamport.Time(2), clock2.Time())

	clock3, err := repo.GetOrCreateClock("bar")
	require.NoError(t, err)
	require.Equal(t, lamport.Time(1), clock3.Time())

	allClocks, err = repo.AllClocks()
	require.NoError(t, err)
	require.Equal(t, map[string]lamport.Clock{
		"foo": clock,
		"bar": clock3,
	}, allClocks)
}

func randomData() []byte {
	var letterRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	b := make([]byte, 32)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return b
}