Skip to content
quarry Artifact [25f6976b04]
Login

Artifact [25f6976b04]

Artifact 25f6976b04ecd9d5cd529db1a699f9ce4fab72a82122a0fa7e1c5ed3c87899ea:


package main

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/events"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ingest"
	"craftdesign.group/quarry/internal/lease"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/zitcli"
)

// closeRig is a real store with three open tickets, folded into a real
// index, and a registrar whose writes are counted.
type closeRig struct {
	t      *testing.T
	dir    string
	cli    *zitcli.Client
	ix     *index.Index
	db     *oltp.DB
	rg     *registrar
	sync   func()
	writes int
	ids    []string
}

func newCloseRig(t *testing.T) *closeRig {
	t.Helper()
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	r := &closeRig{t: t, dir: t.TempDir()}
	zitIn(t, r.dir, "init", ".")
	r.commit("f.txt", "first", "")
	r.cli = zitcli.New(r.dir)
	tok := lease.Unguarded("test: scratch store, no coordinator")
	for _, title := range []string{"a", "b", "c"} {
		id, _, err := r.cli.TicketNew(tok, "dan", map[string]string{"title": title, "status": "Open"})
		if err != nil {
			t.Fatal(err)
		}
		r.ids = append(r.ids, id)
	}
	ev, err := events.Start(filepath.Join(t.TempDir(), "nats"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(ev.Close)
	if r.ix, err = index.Open(filepath.Join(t.TempDir(), "index.db")); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { r.ix.Close() })
	pipe := ingest.NewPipeline(r.cli, ev, r.ix, "test", "n1")
	r.sync = func() {
		if _, err := pipe.Sync(); err != nil {
			t.Fatal(err)
		}
	}
	r.sync()
	if r.db, err = oltp.Open(filepath.Join(t.TempDir(), "o.db")); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { r.db.Close() })
	repos := api.NewRepos(map[string]*api.Repo{"test": {Index: r.ix, Zit: r.cli,
		Guard: func(fn func(lease.Token) error) error { r.writes++; return fn(tok) },
		Sync:  pipe.Sync}})
	r.rg = &registrar{odb: r.db, deps: &api.Deps{Repos: repos}}
	return r
}

func (r *closeRig) commit(file, comment, branch string) {
	r.t.Helper()
	if err := os.WriteFile(filepath.Join(r.dir, file), []byte(comment+"\n"), 0o644); err != nil {
		r.t.Fatal(err)
	}
	zitIn(r.t, r.dir, "add", file)
	args := []string{"commit", "-m", comment, "--user", "u"}
	if branch != "" {
		args = append(args, "--branch", branch)
	}
	zitIn(r.t, r.dir, args...)
}

func (r *closeRig) status(i int) string {
	r.t.Helper()
	tk, ok, err := r.ix.TicketByPrefixAll(r.ids[i])
	if err != nil || !ok {
		r.t.Fatalf("ticket %d: %v %v", i, ok, err)
	}
	for _, f := range tk.Fields {
		if f.Name == "status" {
			return f.Value
		}
	}
	return ""
}

// A check-in on the default branch that says it closes a ticket closes
// it; the same words pushed to another branch close nothing; the
// history the sweep first saw is never read as new.
func TestADefaultBranchCheckinClosesItsTicket(t *testing.T) {
	r := newCloseRig(t)
	r.rg.closeSweep("test", r.ix)
	if r.writes != 0 {
		t.Fatalf("FAIL: the first sweep wrote %d changes; it only baselines", r.writes)
	}
	r.commit("g.txt", "fix it, closes "+r.ids[0][:10], "")
	r.commit("h.txt", "on a side line, closes "+r.ids[1][:10], "side")
	r.sync()
	r.rg.closeSweep("test", r.ix)
	if r.writes != 1 {
		t.Fatalf("FAIL: %d ticket changes, want 1", r.writes)
	}
	if got := r.status(0); got != "Closed" {
		t.Errorf("FAIL: the named ticket is %q", got)
	}
	if got := r.status(1); got != "Open" {
		t.Errorf("FAIL: a push to another branch closed a ticket: %q", got)
	}
	t.Log("ok: default branch closes, other branches do not")
}

// A re-fold replays history the sweep already acted on. A ticket
// somebody reopened after its closing check-in stays reopened.
func TestACheckinClosesItsTicketOnce(t *testing.T) {
	r := newCloseRig(t)
	first := strings.Fields(zitIn(t, r.dir, "info", "-R", filepath.Join(r.dir, ".zit"), "--", "trunk"))[0]
	r.rg.closeSweep("test", r.ix)
	r.commit("g.txt", "closes "+r.ids[0][:10], "")
	r.sync()
	r.rg.closeSweep("test", r.ix)
	tok := lease.Unguarded("test")
	if _, err := r.cli.TicketSet(tok, r.ids[0], "dan", map[string]string{"status": "Open"}); err != nil {
		t.Fatal(err)
	}
	r.sync()
	if err := r.db.SetCloseWatch("test", first); err != nil {
		t.Fatal(err)
	}
	r.writes = 0
	r.rg.closeSweep("test", r.ix)
	if r.writes != 0 || r.status(0) != "Open" {
		t.Fatalf("FAIL: the replayed check-in closed again (%d writes, %s)", r.writes, r.status(0))
	}
	t.Log("ok: once per check-in")
}

// A landing's merge is the landing's: its accept already closed what
// the landing named, so the merge check-in's own comment is not read.
func TestALandingMergeIsLeftToTheLanding(t *testing.T) {
	r := newCloseRig(t)
	r.rg.closeSweep("test", r.ix)
	r.commit("g.txt", "feature work", "feat")
	tok := lease.Unguarded("test")
	id, err := r.cli.LandingOpen(tok, "dan", "feat", "trunk", "land feat", "")
	if err != nil {
		t.Fatal(err)
	}
	tip := strings.Fields(zitIn(t, r.dir, "info", "-R", filepath.Join(r.dir, ".zit"), "--", "feat"))[0]
	if _, err := r.cli.LandingAccept(tok, id, "dan", "land feat, closes "+r.ids[2][:10], "", tip); err != nil {
		t.Fatal(err)
	}
	r.sync()
	b, ok, err := r.ix.TipOf("trunk")
	if err != nil || !ok {
		t.Fatal(ok, err)
	}
	meta, err := r.ix.CheckinsMeta([]string{b.Tip})
	if err != nil || !strings.Contains(meta[b.Tip].Comment, r.ids[2][:10]) {
		t.Fatalf("the fixture's merge does not carry the closing words: %q %v", meta[b.Tip].Comment, err)
	}
	r.rg.closeSweep("test", r.ix)
	if r.writes != 0 || r.status(2) != "Open" {
		t.Fatalf("FAIL: the landing merge closed a ticket (%d writes, %s)", r.writes, r.status(2))
	}
	t.Log("ok: a landing merge is the landing's business")
}