mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1792777718506798022 (novox/mesh-catalog at e8300455 into main,…
mesh/delivery-group group feat/r4g merged: every member is merged
mesh/check checking 454fb8a918ad on 3bec357968da | novox/mesh-catalog#464 in group feat/r4g
166 lines
5.9 KiB
Go
166 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// **The pull request's key** (novox/hq ADR 0406 §4, issue 975): one key per pull request on the bus, holding its head,
|
|
// the trunk commit its current check is asked against and its verdict, written by compare-and-set. A head the forge
|
|
// announces writes head and trunk; a verdict for that head and trunk writes the verdict; nothing else writes.
|
|
|
|
type fakePulls struct {
|
|
mu sync.Mutex
|
|
keys map[string]PullKey
|
|
writes int
|
|
// lose makes the first n writes lose to another writer, which sets rival.
|
|
lose int
|
|
rival PullKey
|
|
}
|
|
|
|
func newFakePulls() *fakePulls { return &fakePulls{keys: map[string]PullKey{}} }
|
|
|
|
func (f *fakePulls) Change(key string, apply func(PullKey) (PullKey, bool)) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
for tries := 0; tries < 5; tries++ {
|
|
next, write := apply(f.keys[key])
|
|
if !write {
|
|
return nil
|
|
}
|
|
if f.lose > 0 { // another writer was first: read again and apply again
|
|
f.lose--
|
|
f.keys[key] = f.rival
|
|
continue
|
|
}
|
|
f.keys[key] = next
|
|
f.writes++
|
|
return nil
|
|
}
|
|
return errors.New("lost the race too often")
|
|
}
|
|
|
|
func (f *fakePulls) get(key string) PullKey {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.keys[key]
|
|
}
|
|
|
|
const appKey = "novox.app.7"
|
|
|
|
func announcedAt(head, trunk string) PullEvent {
|
|
p := pr("novox/app", 7, head, "feat/x", "src/a.go")
|
|
p.BaseSHA = trunk
|
|
return p
|
|
}
|
|
|
|
func TestAnAnnouncedHeadWritesTheKeyAndTheSameAnnouncementAgainWritesNothing(t *testing.T) {
|
|
w := newWorld(t)
|
|
pulls := newFakePulls()
|
|
w.h.Pulls = pulls
|
|
p := announcedAt(readyHead, "t1t1t1t1t1t1")
|
|
w.h.PullUpdated(p)
|
|
w.h.KeepPull(p)
|
|
if got := pulls.get(appKey); got.Head != readyHead || got.Trunk != "t1t1t1t1t1t1" || got.Verdict != "" {
|
|
t.Fatalf("the key holds %+v", got)
|
|
}
|
|
w.h.KeepPull(p)
|
|
if pulls.writes != 1 {
|
|
t.Fatalf("the same announcement wrote %d times", pulls.writes)
|
|
}
|
|
}
|
|
|
|
func TestANewTrunkCommitClearsTheVerdictAndANewHeadReplacesTheKey(t *testing.T) {
|
|
w := newWorld(t)
|
|
pulls := newFakePulls()
|
|
w.h.Pulls = pulls
|
|
w.h.KeepPull(announcedAt(readyHead, "t1t1t1t1t1t1"))
|
|
w.h.KeepPull(announcedAt(readyHead, "t2t2t2t2t2t2"))
|
|
if got := pulls.get(appKey); got.Trunk != "t2t2t2t2t2t2" || got.Verdict != "" {
|
|
t.Fatalf("the check asked again against a new trunk left %+v", got)
|
|
}
|
|
w.h.KeepPull(announcedAt(pushed, "t2t2t2t2t2t2"))
|
|
if got := pulls.get(appKey); got.Head != pushed {
|
|
t.Fatalf("a new head left %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestAMeshCheckForTheKeysHeadAndTrunkIsWrittenAndOneForAnotherIsNot(t *testing.T) {
|
|
w := newWorld(t)
|
|
pulls := newFakePulls()
|
|
w.h.Pulls = pulls
|
|
w.h.PullUpdated(announcedAt(readyHead, "a1a1a1a1a1a1"))
|
|
w.h.KeepPull(announcedAt(readyHead, "a1a1a1a1a1a1"))
|
|
status := func(head, trunk, state string) ForgeStatusEvent {
|
|
return ForgeStatusEvent{Owner: "novox", Repo: "app", SHA: head, Context: ctxCheck, State: state,
|
|
Description: "pass " + head[:12] + " on " + trunk + " | unit 3 run"}
|
|
}
|
|
w.h.KeepCheckVerdict(status(pushed, "a1a1a1a1a1a1", "success"))
|
|
if pulls.get(appKey).Verdict != "" {
|
|
t.Fatal("a verdict for another head was written")
|
|
}
|
|
w.h.KeepCheckVerdict(status(readyHead, "b2b2b2b2b2b2", "success"))
|
|
if pulls.get(appKey).Verdict != "" {
|
|
t.Fatal("a verdict for another trunk commit was written")
|
|
}
|
|
w.h.KeepCheckVerdict(status(readyHead, "a1a1a1a1a1a1", "pending"))
|
|
if pulls.get(appKey).Verdict != "" {
|
|
t.Fatal("a pending check was written as a verdict")
|
|
}
|
|
w.h.KeepCheckVerdict(status(readyHead, "a1a1a1a1a1a1", "success"))
|
|
if got := pulls.get(appKey); got.Verdict != "pass" {
|
|
t.Fatalf("the key holds %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestALostRaceReadsTheKeyAgainAndAppliesTheChangeToWhatItFound(t *testing.T) {
|
|
w := newWorld(t)
|
|
pulls := newFakePulls()
|
|
pulls.lose, pulls.rival = 1, PullKey{Head: readyHead, Trunk: "t1t1t1t1t1t1", Verdict: "pass"}
|
|
w.h.Pulls = pulls
|
|
// The rival writer already holds this very head and trunk with a verdict: the change finds nothing to write.
|
|
w.h.KeepPull(announcedAt(readyHead, "t1t1t1t1t1t1"))
|
|
if got := pulls.get(appKey); got.Verdict != "pass" || pulls.writes != 0 {
|
|
t.Fatalf("after the lost race the key holds %+v after %d writes", got, pulls.writes)
|
|
}
|
|
}
|
|
|
|
// The forge's catch-up after a restart can announce a merge again past the bus's de-duplication window (issue 975):
|
|
// heard again, it changes nothing, and moves no trunk back.
|
|
func TestTheSameMergeHeardAgainAfterALaterMergeMovesNoTrunkBack(t *testing.T) {
|
|
w := newWorld(t)
|
|
a, b := "aaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbb"
|
|
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
|
|
w.h.PullUpdated(pr("novox/app", 8, a, "feat/y", "src/a.go"))
|
|
w.h.PullUpdated(pr("novox/app", 9, b, "feat/z", "src/a.go"))
|
|
w.h.Checked(judgedAt("novox/app", 7, readyHead, "pass", "m8m8m8m8m8m8"))
|
|
w.h.PullMerged(merged("novox/app", 8, a, "m8m8m8m8m8m8"))
|
|
w.h.PullMerged(merged("novox/app", 9, b, "m9m9m9m9m9m9"))
|
|
id := IDOf("novox/app", readyHead)
|
|
before := w.delivery(id)
|
|
w.h.PullMerged(merged("novox/app", 8, a, "m8m8m8m8m8m8")) // announced again, past the window
|
|
after := w.delivery(id)
|
|
if after.TrunkNow != before.TrunkNow || after.TrunkNow != "m9m9m9m9m9m9" {
|
|
t.Fatalf("the trunk moved from %q to %q on a merge heard again; wanted it to stay m9m9m9m9m9m9", before.TrunkNow, after.TrunkNow)
|
|
}
|
|
}
|
|
|
|
// The forge's mesh/check event handled wholly between the announcement's PullUpdated and its KeepPull (the two events are
|
|
// taken on goroutines of their own): the verdict must still reach the pull request's key.
|
|
func TestAVerdictHeardBeforeTheKeyIsWrittenReachesTheKey(t *testing.T) {
|
|
w := newWorld(t)
|
|
pulls := newFakePulls()
|
|
w.h.Pulls = pulls
|
|
p := announcedAt(readyHead, "a1a1a1a1a1a1")
|
|
w.h.PullUpdated(p)
|
|
s := ForgeStatusEvent{Owner: "novox", Repo: "app", SHA: readyHead, Context: ctxCheck, State: "success",
|
|
Description: "pass " + readyHead[:12] + " on a1a1a1a1a1a1 | unit 3 run"}
|
|
w.h.CommitStatus(s)
|
|
w.h.KeepCheckVerdict(s)
|
|
w.h.KeepPull(p)
|
|
if got := pulls.get(appKey); got.Head != readyHead || got.Verdict != "pass" {
|
|
t.Fatalf("the key holds %+v, want the head with the verdict pass", got)
|
|
}
|
|
}
|