dehub/repo_test.go
mediocregopher 1c2bc11fc3 refactor how commits are created, as well as how reference following is done
---
type: change
message: |-
  refactor how commits are created, as well as how reference following is done

  In light of the upcoming combine commit feature, it will be necessary to have
  more fine-grained control over how commits are created and stored. This change
  attempts to address that, by adding a CommitBare method, which the Commit method
  then uses. CommitBare merely creates and stores a commit, without updating HEAD.

  Since Commit now has to figure out the parent commit of the HEAD, and deal with
  the case of HEAD not being set (a new repo, or an orphan branch), the logic for
  reference traversal had to be refactored some as well.
change_hash: AKwHmb4T5APrl3EL7Ox27ABGWrjqjlOGvBD1cOycgPcN
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl53+qwACgkQlcRvpqQRSKzXuQ/9HBnrwB3zYSsLsQrNTlZ6MW5DaH/y1pypba6eD57fC6Tl3ipuDvTLrjiRMdPkS8LFvPOkT8FUFRZH7farBZBZGRnoAL88ELPB/h5Ip7FaUu+syfuTsHnBvWPs2hPN/sEhceIy+io3P03vIKSYCPMAhhigkWhoZZa+SJNQyTTJ/WzbGmuopCYhe3/4JDfpLNIvfOzJb40jVXqmqZ4Lf82uqf9DIh8LjNRhhOBeVFOVyQ4FOHh6a/T7D4qNSMPi/RRFg9VSXlNV5tp2ykdQdzyS6vdBBxV5j8pz8DyTDZYxlqkHyb5Blkl4vcndoJOYjyacVqVuXMgvjf0a8cmuhDEmC1vRDepx4jKZQwKyl1HQ9X0+KodMAxrx671+m7Zga9OcyCdNdyaH+Tl2W4c6apaEv2lcx5DalSLW2uoDbo5B4uutBOFrL12ML2tnnyF93tRSZkcNibo69/88qjFjaYEAjOhupgNtrbU+92Dsj/8UGYKFQgfx7DSHGXhs8fzphzPvZifWoiGV49iXvOauNlUzolX3h4bmxcXAnl/+tC4F1a19TGl7TG7HAC8kajXAf01pSD+WxJGMN+gerOYHOCrlQVx+sWhQBh22yfsaAJXPLl4R/2bUKqSTXjHHp3/WTqK4HdRRN/L728YhOxBCy6Ue0CjAZKpi+cQWQUUxpCKQiXs=
  account: mediocregopher
2020-03-22 17:54:25 -06:00

301 lines
7.2 KiB
Go

package dehub
import (
"bytes"
"dehub/sigcred"
"errors"
"io"
"math/rand"
"path/filepath"
"runtime/debug"
"testing"
"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
sig sigcred.SignifierInterface
}
func newHarness(t *testing.T) *harness {
rand := rand.New(rand.NewSource(0xb4eadb01))
sig, pubKeyBody := sigcred.SignifierPGPTmp("root", rand)
pubKeyPath := filepath.Join(DehubDir, "root.asc")
cfg := &Config{
Accounts: []Account{{
ID: "root",
Signifiers: []sigcred.Signifier{{PGPPublicKeyFile: &sigcred.SignifierPGPFile{
Path: pubKeyPath,
}}},
}},
}
cfgBody, err := yaml.Marshal(cfg)
if err != nil {
t.Fatal(err)
}
h := &harness{
t: t,
rand: rand,
repo: InitMemRepo(),
cfg: cfg,
sig: sig,
}
h.stage(map[string]string{
ConfigPath: string(cfgBody),
pubKeyPath: string(pubKeyBody),
})
return h
}
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) checkout(branch plumbing.ReferenceName) {
w, err := h.repo.GitRepo.Worktree()
if err != nil {
h.t.Fatal(err)
}
head, err := h.repo.GetGitHead()
if err != nil {
h.t.Fatal(err)
}
_, err = h.repo.GitRepo.Branch(branch.Short())
if errors.Is(err, git.ErrBranchNotFound) {
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)
}
}
func (h *harness) tryCommit(
shouldSucceed bool,
commit Commit,
accountID string, 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, accountID)
if err != nil {
h.t.Fatalf("failed to commit ChangeCommit: %v", err)
}
branch, err := h.repo.ReferenceToBranchName(plumbing.HEAD)
if err != nil {
h.t.Fatalf("determining checked out branch: %v", err)
}
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)
}
if gitCommit.GitCommit.NumParents() == 0 {
h.t.Fatalf("unverifiable commit %q has no parents, but it should", gitCommit.GitCommit.Hash)
}
h.reset(gitCommit.GitCommit.ParentHashes[0], git.HardReset)
return gitCommit
}
func (h *harness) changeCommit(
msg string,
accountID string,
sig sigcred.SignifierInterface,
) GitCommit {
commit, err := h.repo.NewCommitChange(msg)
if err != nil {
h.t.Fatalf("creating ChangeCommit: %v", err)
}
return h.tryCommit(true, commit, accountID, sig)
}
func TestHasStagedChanges(t *testing.T) {
harness := newHarness(t)
assertHasStaged := func(expHasStaged bool) {
hasStaged, err := harness.repo.HasStagedChanges()
if err != nil {
debug.PrintStack()
t.Fatalf("error calling HasStagedChanges: %v", err)
} else if hasStaged != expHasStaged {
debug.PrintStack()
t.Fatalf("expected HasStagedChanges to return %v", expHasStaged)
}
}
// the harness starts with some staged changes
assertHasStaged(true)
harness.stage(map[string]string{"foo": "bar"})
assertHasStaged(true)
harness.changeCommit("first commit", "root", harness.sig)
assertHasStaged(false)
harness.stage(map[string]string{"foo": ""}) // delete foo
assertHasStaged(true)
harness.changeCommit("second commit", "root", harness.sig)
assertHasStaged(false)
}
// TestOldConfig tests that having an older, now malformed, Config doesn't mess
// with the current parsing, as long as the default access controls still work.
func TestOldConfig(t *testing.T) {
harness := newHarness(t)
// overwrite the currently staged config file with an older form
harness.stage(map[string]string{ConfigPath: `
---
accounts:
- id: root
signifiers:
- type: pgp_public_key_file
path: ".dehub/root.asc"
access_controls:
- pattern: "**"
condition:
type: signature
account_ids:
- root
count: 0
`})
// this commit should be created and verify fine
harness.changeCommit("first commit, this is going great", "root", harness.sig)
// this commit should not be verifiable, because toot isn't in accounts and
// the default access controls should be being used
harness.stage(map[string]string{"foo": "no rules!"})
badCommit, err := harness.repo.NewCommitChange("ain't no laws")
if err != nil {
t.Fatalf("creating CommitChange: %v", err)
}
harness.tryCommit(false, badCommit, "toot", nil)
// make a commit fixing the config. everything should still be fine.
harness.stage(map[string]string{ConfigPath: `
---
accounts:
- id: root
signifiers:
- type: pgp_public_key_file
path: ".dehub/root.asc"
`})
harness.changeCommit("Fix the config!", "root", harness.sig)
}
// 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)
}
}