Skip to content
quarry Artifact [f673eff11e]
Login

Artifact [f673eff11e]

Artifact f673eff11ecd736a6cc66ff7ec6eda1afa2f612a263b21ae7c7b8891bc2438c1:


package api

import (
	"strings"
	"sync"
	"testing"

	"craftdesign.group/quarry/internal/zitcli"
)

// mergeChecks requires "ci" on every branch, tested as the merge in
// quarry#290's non-blocking mode.
type mergeChecks struct{}

func (mergeChecks) RequiredChecks(string, string) []string { return []string{"ci"} }
func (mergeChecks) PremergeMode(string, string) bool       { return false }
func (mergeChecks) MergeMode(string, string) bool          { return true }

// mergeRuns is pairRuns that also answers the newest pair per tip.
type mergeRuns struct {
	*pairRuns
	mu    sync.Mutex
	order []string
}

func (m *mergeRuns) green(branchTip, targetTip string) {
	m.pairRuns.green(branchTip, targetTip)
	m.mu.Lock()
	m.order = append(m.order, "pair:"+branchTip+":"+targetTip)
	m.mu.Unlock()
}

func (m *mergeRuns) LatestPairFor(_, tip string) (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	for i := len(m.order) - 1; i >= 0; i-- {
		if strings.HasPrefix(m.order[i], "pair:"+tip+":") {
			return m.order[i], nil
		}
	}
	return "", nil
}

// IN MERGE MODE A MOVED TARGET DOES NOT BLOCK THE NEXT ACCEPT.
//
// Both landings' merges with the root trunk are green. Accepting the
// first moves trunk; the second's verdict is now about an older trunk.
// Strict premerge refuses that (accepttarget_test.go). Merge mode lets
// it land, reports which trunk was tested and how far trunk moved, and
// says so in the merge's own message.
func TestMergeModeAcceptsOverAMovedTargetAndSaysSo(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	ci.green(ztip(t, dir, "two"), root)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}

	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept"); err != nil {
		t.Fatalf("FAIL: the first accept of a green merge failed: %v", err)
	}
	two, found, err := ix.LandingByPrefix(ids[1])
	if err != nil || !found {
		t.Fatalf("no landing two: %v", err)
	}
	subject, pair := d.ciSubject("test", two)
	if !pair || PairTarget(subject) != root {
		t.Fatalf("FAIL: the second landing's checks are about %q, want its pair with the root trunk", subject)
	}
	repo, _ := d.Repos.Get("test")
	if n := targetMovedBy(repo, "trunk", root); n < 1 {
		t.Fatalf("FAIL: trunk moved %d check-ins since the root, want at least 1", n)
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[1], Message: "land two"}, "accept"); err != nil {
		t.Fatalf("FAIL: merge mode refused an accept over a moved target: %v", err)
	}
	if out := zrun(t, dir, "artifact", ztip(t, dir, "trunk")); !strings.Contains(out, "has moved since") || !strings.Contains(out, root[:12]) {
		t.Errorf("FAIL: the merge's message does not say trunk moved since its checks:\n%s", out)
	}
	t.Log("ok: the stale-but-green merge lands, and its message says which trunk was tested")
}

// With no merge tested yet the accept waits, as it does for a tip with
// no run: merge mode relaxes a MOVED target, never a missing verdict.
func TestMergeModeWaitsForTheFirstMergeVerdict(t *testing.T) {
	_, ids, ix, pipeSync := premergeStore(t)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}},
		Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Sync: pipeSync}})}
	_, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept")
	if err == nil || !strings.Contains(err.Error(), "required checks not green") {
		t.Fatalf("FAIL: an accept with no merge tested answered %v, want the required-checks refusal", err)
	}
	t.Log("ok: no merge verdict, no accept")
}