dehub/project_test.go
mediocregopher b01fe1524a Completely refactor naming of everything, in light of new SPEC
---
type: change
description: |-
  Completely refactor naming of everything, in light of new SPEC

  Writing the SPEC shed some light on just how weakly a lot of concepts, like
  "commit", had been defined, and prompted the delineation of a lot of things
  along specific lines (commit vs payload, repo vs project). This commit makes the
  code reflect the SPEC much better in quite a few ways:

  * Repo is now Project
  * Commit is now Payload
  * GitCommit is now just Commit
  * Hash is now Fingerprint
  * A lot of minor fields got renamed
  * All the XXXInterface types are now just XXX, and their old XXX type is now
    XXXUnion.

  More than likely there's still some comments and variable names that have
  slipped passed, but overall I feel like I got most of the changes.
fingerprint: AKkDC5BKhKbfXzZQ/F4KquHeMgVvcNxgLmkZFz/nP/tY
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6l7aYACgkQlcRvpqQRSKxFrA//VQ+f8B6pwGS3ORB4VVBnHvvJTGZvAYTvB0fHuHJx2EreR4FwjhaNakk5ClkwbO7WFMq++2OV4xIkvzwswLdbXZF0IHx3wScQM59v4vIkR4V9Lj5p1aGGhQna52uIKugF2gTqKdU4tqYzmBjDND/c2XDwCN5CwTwwnAHXUSSsHxviiPUYPWV5wzFP7uyRW0ZeK8Isv7QECKRXlsDjcSJa+g+jc091FG/jG9Dkai8fbDbW8YXj7W3ALaXgXWEBJMrgQxZcJJRjgCvLY72FIIrUBquu3FepiyzMtZ0yaIvi4NmGCsYqIv00NcMvMtD7iwhOCZn10Sku4wvaKJ8YBMRduhqC99fnr/ZDW0/HvTNcL7GKx11GjwtmzkJgwsHFPy3zX+kMdF4m3WgtoeI0GwEsBXXZE2C49yAk3Mb/3puegl3a1PPMvOabTzo7Xm6xpWkI6gISChI7My71H3EuKZWhkb+IubPmMvJJXIdVxHnsHPz2dl/BZXLgpfVdEgQa2qWeXtYI4NNm37pLl3gv92V4kka+Kr4gfdoq8mJ7aqvc9was35baJbHg4+fEVJG2Wj+2AQU+ncx3nAFzgYyMxwo9K8VuC4QdfRF4ImyxTnWkuokEn9H6JRrbkBDKIELj6vzdPmsjOUEQ4nsYX66/zSibFD7UvhQmdXFs8Gp8/Qq6g4M=
  account: mediocregopher
2020-04-26 14:23:10 -06:00

290 lines
6.9 KiB
Go

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
proj *Project
cfg *Config
}
func newHarness(t *testing.T) *harness {
rand := rand.New(rand.NewSource(0xb4eadb01))
return &harness{
t: t,
rand: rand,
proj: InitMemProject(),
cfg: new(Config),
}
}
func (h *harness) stage(tree map[string]string) {
w, err := h.proj.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("removing %q: %v", path, err)
}
continue
}
dir := filepath.Dir(path)
if err := fs.MkdirAll(dir, 0666); err != nil {
h.t.Fatalf("making directory %q: %v", dir, err)
}
f, err := fs.Create(path)
if err != nil {
h.t.Fatalf("creating file %q: %v", path, err)
} else if _, err := io.Copy(f, bytes.NewBufferString(content)); err != nil {
h.t.Fatalf("writing to file %q: %v", path, err)
} else if err := f.Close(); err != nil {
h.t.Fatalf("closing file %q: %v", path, err)
} else if _, err := w.Add(path); err != nil {
h.t.Fatalf("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.Signifier {
sig, pubKeyBody := sigcred.TestSignifierPGP(accountID, anon, h.rand)
if !anon {
h.cfg.Accounts = append(h.cfg.Accounts, Account{
ID: accountID,
Signifiers: []sigcred.SignifierUnion{{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.proj.GitRepo.Worktree()
if err != nil {
h.t.Fatal(err)
}
head, err := h.proj.GetHeadCommit()
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.proj.GitRepo.Storer.SetReference(ref); err != nil {
h.t.Fatal(err)
}
return
} else if err != nil {
h.t.Fatal(err)
}
_, err = h.proj.GitRepo.Storer.Reference(branch)
if errors.Is(err, plumbing.ErrReferenceNotFound) {
err = w.Checkout(&git.CheckoutOptions{
Hash: head.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.proj.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,
payUn PayloadUnion,
accountSig sigcred.Signifier,
) Commit {
if accountSig != nil {
var err error
if payUn, err = h.proj.AccreditPayload(payUn, accountSig); err != nil {
h.t.Fatalf("accrediting payload: %v", err)
}
}
commit, err := h.proj.Commit(payUn)
if err != nil {
h.t.Fatalf("committing PayloadChange: %v", err)
} else if verifyExp == verifySkip {
return commit
}
branch, err := h.proj.ReferenceToBranchName(plumbing.HEAD)
if err != nil {
h.t.Fatalf("determining checked out branch: %v", err)
}
shouldSucceed := verifyExp > 0
err = h.proj.VerifyCommits(branch, []Commit{commit})
if shouldSucceed && err != nil {
h.t.Fatalf("verifying commit %q: %v", commit.Hash, err)
} else if shouldSucceed {
return commit
} else if !shouldSucceed && err == nil {
h.t.Fatalf("verifying commit %q should have failed", commit.Hash)
}
var parentHash plumbing.Hash
if commit.Object.NumParents() > 0 {
parentHash = commit.Object.ParentHashes[0]
}
h.reset(parentHash, git.HardReset)
return commit
}
func (h *harness) assertCommitChange(
verifyExp verifyExpectation,
msg string,
sig sigcred.Signifier,
) Commit {
payUn, err := h.proj.NewPayloadChange(msg)
if err != nil {
h.t.Fatalf("creating PayloadChange: %v", err)
}
return h.tryCommit(verifyExp, payUn, sig)
}
func TestHasStagedChanges(t *testing.T) {
h := newHarness(t)
rootSig := h.stageNewAccount("root", false)
assertHasStaged := func(expHasStaged bool) {
hasStaged, err := h.proj.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)
}
// TestThisProjectStillVerifies opens this actual project and ensures that all
// commits in it still verify.
func TestThisProjectStillVerifies(t *testing.T) {
proj, err := OpenProject(".")
if err != nil {
t.Fatalf("error opening repo: %v", err)
}
headCommit, err := proj.GetHeadCommit()
if err != nil {
t.Fatalf("getting repo head: %v", err)
}
allCommits, err := proj.GetCommitRange(plumbing.ZeroHash, headCommit.Hash)
if err != nil {
t.Fatalf("getting all commits (up to %q): %v", headCommit.Hash, err)
}
checkedOutBranch, err := proj.ReferenceToBranchName(plumbing.HEAD)
if err != nil {
t.Fatalf("error determining checked out branch: %v", err)
}
if err := proj.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).Hash
hashStr := hash.String()
t.Log(hashStr)
for i := 2; i < len(hashStr); i++ {
gotCommit, err := h.proj.GetCommitByRevision(plumbing.Revision(hashStr[:i]))
if err != nil {
t.Fatalf("resolving %q: %v", hashStr[:i], err)
} else if gotCommit.Hash != hash {
t.Fatalf("expected hash %q but got %q",
gotCommit.Hash, hash)
}
}
}