A read-only clone of the dehub project, for until dehub.dev can be brought back online.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
dehub/repo_test.go

290 lines
7.1 KiB

package dehub
import (
"bytes"
"errors"
"io"
"math/rand"
"path/filepath"
"testing"
"dehub.dev/src/dehub.git/sigcred"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
yaml "gopkg.in/yaml.v2"
)
type harness struct {
t *testing.T
rand *rand.Rand
repo *Repo
cfg *Config
}
func newHarness(t *testing.T) *harness {
rand := rand.New(rand.NewSource(0xb4eadb01))
return &harness{
t: t,
rand: rand,
repo: InitMemRepo(),
cfg: new(Config),
}
}
func (h *harness) stage(tree map[string]string) {
w, err := h.repo.GitRepo.Worktree()
if err != nil {
h.t.Fatal(err)
}
fs := w.Filesystem
for path, content := range tree {
if content == "" {
if _, err := w.Remove(path); err != nil {
h.t.Fatalf("error removing %q: %v", path, err)
}
continue
}
dir := filepath.Dir(path)
if err := fs.MkdirAll(dir, 0666); err != nil {
h.t.Fatalf("error making directory %q: %v", dir, err)
}
f, err := fs.Create(path)
if err != nil {
h.t.Fatalf("error creating file %q: %v", path, err)
} else if _, err := io.Copy(f, bytes.NewBufferString(content)); err != nil {
h.t.Fatalf("error writing to file %q: %v", path, err)
} else if err := f.Close(); err != nil {
h.t.Fatalf("error closing file %q: %v", path, err)
} else if _, err := w.Add(path); err != nil {
h.t.Fatalf("error adding file %q to index: %v", path, err)
}
}
}
func (h *harness) stageCfg() {
cfgBody, err := yaml.Marshal(h.cfg)
if err != nil {
h.t.Fatal(err)
}
h.stage(map[string]string{ConfigPath: string(cfgBody)})
}
func (h *harness) stageNewAccount(accountID string, anon bool) sigcred.SignifierInterface {
sig, pubKeyBody := sigcred.TestSignifierPGP(accountID, anon, h.rand)
if !anon {
h.cfg.Accounts = append(h.cfg.Accounts, Account{
ID: accountID,
Signifiers: []sigcred.Signifier{{PGPPublicKey: &sigcred.SignifierPGP{
Body: string(pubKeyBody),
}}},
})
h.stageCfg()
}
return sig
}
func (h *harness) stageAccessControls(aclYAML string) {
if err := yaml.Unmarshal([]byte(aclYAML), &h.cfg.AccessControls); err != nil {
h.t.Fatal(err)
}
h.stageCfg()
}
func (h *harness) checkout(branch plumbing.ReferenceName) {
w, err := h.repo.GitRepo.Worktree()
if err != nil {
h.t.Fatal(err)
}
head, err := h.repo.GetGitHead()
if errors.Is(err, ErrHeadIsZero) {
// if HEAD is not resolvable to any hash than the Checkout method
// doesn't work, just set HEAD manually.
ref := plumbing.NewSymbolicReference(plumbing.HEAD, branch)
if err := h.repo.GitRepo.Storer.SetReference(ref); err != nil {
h.t.Fatal(err)
}
return
} else if err != nil {
h.t.Fatal(err)
}
_, err = h.repo.GitRepo.Storer.Reference(branch)
if errors.Is(err, plumbing.ErrReferenceNotFound) {
err = w.Checkout(&git.CheckoutOptions{
Hash: head.GitCommit.Hash,
Branch: branch,
Create: true,
})
} else if err != nil {
h.t.Fatalf("checking if branch already exists: %v", branch)
} else {
err = w.Checkout(&git.CheckoutOptions{
Branch: branch,
})
}
if err != nil {
h.t.Fatalf("checking out branch: %v", err)
}
}
func (h *harness) reset(to plumbing.Hash, mode git.ResetMode) {
w, err := h.repo.GitRepo.Worktree()
if err != nil {
h.t.Fatal(err)
}
err = w.Reset(&git.ResetOptions{
Commit: to,
Mode: mode,
})
if err != nil {
h.t.Fatal(err)
}
}
type verifyExpectation int
const (
verifyShouldSucceed verifyExpectation = 1
verifyShouldFail verifyExpectation = 0
verifySkip verifyExpectation = -1
)
func (h *harness) tryCommit(
verifyExp verifyExpectation,
commit Commit,
accountSig sigcred.SignifierInterface,
) GitCommit {
if accountSig != nil {
var err error
if commit, err = h.repo.AccreditCommit(commit, accountSig); err != nil {
h.t.Fatalf("accrediting commit: %v", err)
}
}
gitCommit, err := h.repo.Commit(commit)
if err != nil {
h.t.Fatalf("failed to commit ChangeCommit: %v", err)
} else if verifyExp == verifySkip {
return gitCommit
}
branch, err := h.repo.ReferenceToBranchName(plumbing.HEAD)
if err != nil {
h.t.Fatalf("determining checked out branch: %v", err)
}
shouldSucceed := verifyExp > 0
err = h.repo.VerifyCommits(branch, []GitCommit{gitCommit})
if shouldSucceed && err != nil {
h.t.Fatalf("verifying commit %q: %v", gitCommit.GitCommit.Hash, err)
} else if shouldSucceed {
return gitCommit
} else if !shouldSucceed && err == nil {
h.t.Fatalf("verifying commit %q should have failed", gitCommit.GitCommit.Hash)
}
var parentHash plumbing.Hash
if gitCommit.GitCommit.NumParents() > 0 {
parentHash = gitCommit.GitCommit.ParentHashes[0]
}
h.reset(parentHash, git.HardReset)
return gitCommit
}
func (h *harness) assertCommitChange(
verifyExp verifyExpectation,
msg string,
sig sigcred.SignifierInterface,
) GitCommit {
commit, err := h.repo.NewCommitChange(msg)
if err != nil {
h.t.Fatalf("creating ChangeCommit: %v", err)
}
return h.tryCommit(verifyExp, commit, sig)
}
func TestHasStagedChanges(t *testing.T) {
h := newHarness(t)
rootSig := h.stageNewAccount("root", false)
assertHasStaged := func(expHasStaged bool) {
hasStaged, err := h.repo.HasStagedChanges()
if err != nil {
t.Fatalf("error calling HasStagedChanges: %v", err)
} else if hasStaged != expHasStaged {
t.Fatalf("expected HasStagedChanges to return %v", expHasStaged)
}
}
// the harness starts with some staged changes
assertHasStaged(true)
h.stage(map[string]string{"foo": "bar"})
assertHasStaged(true)
h.assertCommitChange(verifyShouldSucceed, "first commit", rootSig)
assertHasStaged(false)
h.stage(map[string]string{"foo": ""}) // delete foo
assertHasStaged(true)
h.assertCommitChange(verifyShouldSucceed, "second commit", rootSig)
assertHasStaged(false)
}
// TestThisRepoStillVerifies opens this actual repository and ensures that all
// commits in it still verify, given this codebase.
func TestThisRepoStillVerifies(t *testing.T) {
repo, err := OpenRepo(".")
if err != nil {
t.Fatalf("error opening repo: %v", err)
}
headGitCommit, err := repo.GetGitHead()
if err != nil {
t.Fatalf("getting repo head: %v", err)
}
allCommits, err := repo.GetGitCommitRange(plumbing.ZeroHash, headGitCommit.GitCommit.Hash)
if err != nil {
t.Fatalf("getting all commits (up to %q): %v",
headGitCommit.GitCommit.Hash, err)
}
checkedOutBranch, err := repo.ReferenceToBranchName(plumbing.HEAD)
if err != nil {
t.Fatalf("error determining checked out branch: %v", err)
}
if err := repo.VerifyCommits(checkedOutBranch, allCommits); err != nil {
t.Fatal(err)
}
}
func TestShortHashResolving(t *testing.T) {
// TODO ideally this test would test that conflicting hashes are noticed,
// but that's hard...
h := newHarness(t)
rootSig := h.stageNewAccount("root", false)
hash := h.assertCommitChange(verifyShouldSucceed, "first commit", rootSig).GitCommit.Hash
hashStr := hash.String()
t.Log(hashStr)
for i := 2; i < len(hashStr); i++ {
gotCommit, err := h.repo.GetGitRevision(plumbing.Revision(hashStr[:i]))
if err != nil {
t.Fatalf("resolving %q: %v", hashStr[:i], err)
} else if gotCommit.GitCommit.Hash != hash {
t.Fatalf("expected hash %q but got %q",
gotCommit.GitCommit.Hash, hash)
}
}
}