247 lines
10 KiB
Go
247 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
sdk "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// One check per delivery (novox/hq ADR 0548), the gate's part: nothing merges into a repository while a check of other heads
|
|
// judges it (§2); an approved merge holds a turn on its repositories (§3); a held merge past its bound is a stalled line, read
|
|
// and handed by two tools (§4); an approved delivery whose trunk moved merges on its carried verdict with no second ask (§8).
|
|
|
|
const otherHead = "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"
|
|
|
|
// approvedLone is a lone delivery into oneRepo whose check passed, approved by the operator (the world approves at once).
|
|
func approvedLone(w *world) string {
|
|
w.t.Helper()
|
|
p := pr(oneRepo, 1, oneHead, "feat/one")
|
|
requiresCheck(w, p)
|
|
w.h.PullUpdated(p)
|
|
putCheckerVerdict(w, "pass", headOf(oneRepo, 1, oneHead))
|
|
w.h.CommitStatus(checkStatus(oneRepo, oneHead, "success", oneTrunk))
|
|
return IDOf(oneRepo, oneHead)
|
|
}
|
|
|
|
// memTurns is the bucket turns in memory; it logs each write with the merges the fake bridge had made by then.
|
|
type memTurns struct {
|
|
mu sync.Mutex
|
|
m map[string]sdk.Turn
|
|
log []string
|
|
merges func() int
|
|
}
|
|
|
|
func newMemTurns(w *world) *memTurns {
|
|
return &memTurns{m: map[string]sdk.Turn{}, merges: func() int { return len(w.forge.merges) }}
|
|
}
|
|
|
|
func (f *memTurns) All() (map[string]sdk.Turn, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
out := map[string]sdk.Turn{}
|
|
for k, v := range f.m {
|
|
out[k] = v
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (f *memTurns) Put(t sdk.Turn) error {
|
|
if _, err := sdk.EncodeTurn(t); err != nil {
|
|
return err
|
|
}
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.m[t.Repository] = t
|
|
f.log = append(f.log, fmt.Sprintf("put %s after %d merges", t.Repository, f.merges()))
|
|
return nil
|
|
}
|
|
|
|
func (f *memTurns) Delete(repository string) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
delete(f.m, repository)
|
|
f.log = append(f.log, fmt.Sprintf("delete %s after %d merges", repository, f.merges()))
|
|
return nil
|
|
}
|
|
|
|
func (f *memTurns) said() string { f.mu.Lock(); defer f.mu.Unlock(); return strings.Join(f.log, "; ") }
|
|
|
|
func TestOneCheckPerDeliveryAtTheGate(t *testing.T) {
|
|
for name, run := range map[string]func(t *testing.T){
|
|
// §2: a merge into a repository a running verdict names is not written to merges, and is written on that verdict's pass.
|
|
"an approved merge waits for a running check on its repository and merges on its end": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
putCheckerVerdict(w, "running", headOf(oneRepo, 9, otherHead))
|
|
id := approvedLone(w)
|
|
w.settleAll()
|
|
if w.op.count() != 1 || len(w.forge.merges) != 0 {
|
|
t.Fatalf("asked %d times, merges %v: want the Approve asked and the merge held", w.op.count(), w.forge.merges)
|
|
}
|
|
d := w.delivery(id)
|
|
if d.Ready == nil || !strings.Contains(d.Ready.Waits, "waits for the check of g") || !strings.Contains(d.Ready.Waits, oneRepo) ||
|
|
d.Ready.HeldSince == nil {
|
|
t.Fatalf("the merge does not say what it waits for: %+v", d.Ready)
|
|
}
|
|
// §9: deliveries says what it waits for.
|
|
for _, l := range w.h.Deliveries("", "", "", false) {
|
|
if l.ID == id && !strings.Contains(l.Waits, "approved; waits for the check of g") {
|
|
t.Errorf("deliveries says it waits for %q", l.Waits)
|
|
}
|
|
}
|
|
// §4: past 30 minutes the held merge is a stalled line naming the check it waits for and since when.
|
|
for _, l := range w.h.Stalled() {
|
|
if l.ID == id {
|
|
t.Fatalf("a stalled line within the bound: %+v", l)
|
|
}
|
|
}
|
|
w.later(turnBound + time.Minute)
|
|
w.settleAll()
|
|
var line *StalledLine
|
|
for _, l := range w.h.Stalled() {
|
|
if l.ID == id {
|
|
line = &l
|
|
}
|
|
}
|
|
if line == nil || line.Bound != "30m0s" || !strings.Contains(line.Says, "waits for the check of g") || !strings.Contains(line.Says, "held since") ||
|
|
!strings.Contains(line.Says, "checker_status") || !strings.Contains(line.H2, "delivery_hand_now") {
|
|
t.Fatalf("the stalled line of the held merge = %+v", line)
|
|
}
|
|
putCheckerVerdict(w, "pass", headOf(oneRepo, 9, otherHead))
|
|
w.settleAll()
|
|
if len(w.forge.merges) != 1 || w.op.count() != 1 {
|
|
t.Fatalf("after the check's end: merges %v, asked %d times; want merged with no second ask", w.forge.merges, w.op.count())
|
|
}
|
|
},
|
|
"a running check on another repository holds nothing": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
putCheckerVerdict(w, "running", headOf(oneRepo2, 9, otherHead))
|
|
approvedLone(w)
|
|
w.settleAll()
|
|
if len(w.forge.merges) != 1 {
|
|
t.Fatalf("merges %v: a check of another repository held the merge (%s)", w.forge.merges, w.logs())
|
|
}
|
|
},
|
|
"the delivery's own running verdict does not hold it": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.op.auto = false
|
|
id := approvedLone(w)
|
|
w.settleAll()
|
|
putCheckerVerdict(w, "running", headOf(oneRepo, 1, oneHead)) // its own heads judged again (a carry)
|
|
for _, k := range w.op.open() {
|
|
w.op.answer(k, OptionApprove)
|
|
}
|
|
w.settleAll()
|
|
if d := w.delivery(id); len(w.forge.merges) != 1 || d.Ready == nil || d.Ready.HeldSince != nil {
|
|
t.Fatalf("merges %v, run %+v: its own check held it", w.forge.merges, d.Ready)
|
|
}
|
|
},
|
|
// §3: the turn is written before the verdicts are read for the merge, and deleted after the last merge.
|
|
"a lone merge's turn is written before it is handed and deleted once it merged": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
turns := newMemTurns(w)
|
|
w.h.Turns = turns
|
|
approvedLone(w)
|
|
w.settleAll()
|
|
if len(w.forge.merges) != 1 || turns.said() != "put "+oneRepo+" after 0 merges; delete "+oneRepo+" after 1 merges" {
|
|
t.Fatalf("merges %v; the turns were %q", w.forge.merges, turns.said())
|
|
}
|
|
},
|
|
"a group's turns stand on every member's repository until its last member merged": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
turns := newMemTurns(w)
|
|
w.h.Turns = turns
|
|
a, b := pr(oneRepo, 1, oneHead, "feat/two"), pr(oneRepo2, 2, oneHead2, "feat/two")
|
|
requiresCheck(w, a, b)
|
|
w.h.PullUpdated(a)
|
|
w.h.PullUpdated(b)
|
|
w.h.wantForge()
|
|
putCheckerVerdict(w, "pass", headOf(oneRepo, 1, oneHead), headOf(oneRepo2, 2, oneHead2))
|
|
w.h.CommitStatus(checkStatus(oneRepo, oneHead, "success", oneTrunk))
|
|
w.h.CommitStatus(checkStatus(oneRepo2, oneHead2, "success", oneTrunk))
|
|
for range 3 {
|
|
w.settleAll()
|
|
}
|
|
want := "put " + oneRepo2 + " after 0 merges; put " + oneRepo + " after 0 merges; delete " + oneRepo2 + " after 2 merges; delete " + oneRepo + " after 2 merges"
|
|
if len(w.forge.merges) != 2 || turns.said() != want {
|
|
t.Fatalf("merges %v; the turns were %q, want %q", w.forge.merges, turns.said(), want)
|
|
}
|
|
},
|
|
// §8: an approved delivery whose trunk moved goes back to proposed, waits for its carried verdict rather than being
|
|
// handed on the old one, and merges on it with no second ask.
|
|
"an approved merge whose trunk moved merges on its carried verdict with no second ask": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
putCheckerVerdict(w, "running", headOf(oneRepo, 9, otherHead)) // holds the approved merge (§2)
|
|
id := approvedLone(w)
|
|
w.settleAll()
|
|
if w.op.count() != 1 || len(w.forge.merges) != 0 {
|
|
t.Fatalf("asked %d times, merges %v; want approved and held", w.op.count(), w.forge.merges)
|
|
}
|
|
w.h.PullMerged(merged(oneRepo, 5, otherHead, oneTrunk2)) // a merge at the forge moves the trunk
|
|
putCheckerVerdict(w, "pass", headOf(oneRepo, 9, otherHead))
|
|
w.settleAll()
|
|
if len(w.forge.merges) != 0 {
|
|
t.Fatalf("merges %v: handed on a verdict judged on the old trunk", w.forge.merges)
|
|
}
|
|
d := w.delivery(id)
|
|
if d.Ready == nil || !strings.Contains(d.Ready.Waits, "being carried") {
|
|
t.Fatalf("after the trunk moved it waits for %q, want its verdict carried", d.Ready.Waits)
|
|
}
|
|
// The checker's carried line: the head on the new trunk, carried from the old one.
|
|
carried := checkStatus(oneRepo, oneHead, "success", oneTrunk2)
|
|
carried.Description = "pass " + oneHead[:12] + " on " + oneTrunk2[:12] + " | carried from " + oneTrunk[:12] + " | unit 0 run, 4 kept"
|
|
if head, trunk := checkLine(carried.Description); head != oneHead[:12] || trunk != oneTrunk2[:12] {
|
|
t.Fatalf("the carried line reads as %s on %s", head, trunk)
|
|
}
|
|
if head, trunk := checkLine("failed: a test"); head != "" || trunk != "" {
|
|
t.Fatalf("a line that names no commits gave %q %q", head, trunk)
|
|
}
|
|
putCheckerVerdict(w, "pass", headOf(oneRepo, 1, oneHead))
|
|
w.h.CommitStatus(carried)
|
|
w.settleAll()
|
|
if w.op.count() != 1 {
|
|
t.Errorf("the Approve was asked %d times, want once: it stands through the carry", w.op.count())
|
|
}
|
|
if d := w.delivery(id); d.Check == nil || d.Check.Trunk != oneTrunk2[:12] || len(w.forge.merges) != 1 {
|
|
t.Errorf("after the carry: verdict %+v, merges %v; want merged on the carried verdict", d.Check, w.forge.merges)
|
|
}
|
|
},
|
|
// §4: delivery_turns reads every turn with the check it waits for; delivery_hand_now hands a held merge at once on the
|
|
// operator's word, and refuses one no check holds.
|
|
"the turns are read, and a held merge is handed now on the operator's word": func(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.h.Turns = newMemTurns(w)
|
|
putCheckerVerdict(w, "running", headOf(oneRepo, 9, otherHead))
|
|
id := approvedLone(w)
|
|
w.settleAll()
|
|
read := w.h.TurnsRead()
|
|
lines := read["turns"].([]TurnLine)
|
|
if len(lines) != 1 || lines[0].Repository != oneRepo || len(lines[0].Turn.Waiting) != 1 || lines[0].Turn.Waiting[0].WaitsFor == "" ||
|
|
!strings.Contains(lines[0].Waits["feat/one"].(string), "the check g.") || read["written"] != true {
|
|
t.Fatalf("delivery_turns = %+v", read)
|
|
}
|
|
if _, err := w.h.HandNow(id, ""); err == nil || !strings.Contains(err.Error(), "why") {
|
|
t.Errorf("a hand without the operator's why: %v", err)
|
|
}
|
|
if _, err := w.h.HandNow(IDOf(oneRepo2, oneHead2), "x"); err == nil {
|
|
t.Error("an unknown delivery was handed")
|
|
}
|
|
if _, err := w.h.HandNow(id, "the operator, on telegram: merge it now"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.settleAll()
|
|
if len(w.forge.merges) != 1 || w.delivery(id).Ready.HandNow == "" {
|
|
t.Fatalf("merges %v after the hand", w.forge.merges)
|
|
}
|
|
if _, err := w.h.HandNow(id, "again"); err == nil {
|
|
t.Error("a merged delivery was handed again")
|
|
}
|
|
},
|
|
} {
|
|
t.Run(name, run)
|
|
}
|
|
}
|