Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/pullkey_test.go
T

175 lines
6.2 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 TestAVerdictForTheKeysHeadAndTrunkIsWrittenAndOneForAnotherIsNot(t *testing.T) {
w := newWorld(t)
pulls := newFakePulls()
w.h.Pulls = pulls
w.h.KeepPull(announcedAt(readyHead, "t1t1t1t1t1t1"))
old := judgedAt("novox/app", 7, pushed, "pass", "t1t1t1t1t1t1")
w.h.KeepVerdict(old)
if pulls.get(appKey).Verdict != "" {
t.Fatal("a verdict for another head was written")
}
other := judgedAt("novox/app", 7, readyHead, "pass", "t9t9t9t9t9t9")
w.h.KeepVerdict(other)
if pulls.get(appKey).Verdict != "" {
t.Fatal("a verdict for another trunk commit was written")
}
w.h.KeepVerdict(judgedAt("novox/app", 7, readyHead, "pass", "t1t1t1t1t1t1"))
if got := pulls.get(appKey); got.Verdict != "pass" {
t.Fatalf("the key holds %+v", got)
}
}
func TestANewTrunkCommitClearsTheVerdictAndANewHeadReplacesTheKey(t *testing.T) {
w := newWorld(t)
pulls := newFakePulls()
w.h.Pulls = pulls
w.h.KeepPull(announcedAt(readyHead, "t1t1t1t1t1t1"))
w.h.KeepVerdict(judgedAt("novox/app", 7, readyHead, "pass", "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 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)
}
}
// A check asked for a head whose Approve is asked is on the ready stream (ADR 0406 §3, ADR 0421 §10); one for a head
// with no Approve is asked as before.
func TestAPullRequestWithItsApproveAskedHasItsCheckAskedOnTheReadyStream(t *testing.T) {
w := newWorld(t)
w.op.auto = false
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
w.h.Checked(judgedAt("novox/app", 7, readyHead, "pass", "t1t1t1t1t1t1"))
w.settleAll()
w.h.PullMerged(merged("novox/app", 9, "9999999999999999", "t2t2t2t2t2t2"))
w.settleAll()
if len(w.ctl.checkReady) == 0 || !w.ctl.checkReady[0] {
t.Fatalf("the check of a pull request whose Approve is asked was asked with ready %v", w.ctl.checkReady)
}
// One with no Approve asked is asked as before.
w2 := newWorld(t)
w2.h.PullUpdated(pr("novox/app", 8, pushed, "feat/y", "src/a.go"))
w2.settleAll()
if len(w2.ctl.checkReady) == 0 || w2.ctl.checkReady[0] {
t.Fatalf("the check of a pull request with no Approve asked was asked with ready %v", w2.ctl.checkReady)
}
}
func TestATrunkLessVerdictWritesNothingToTheKey(t *testing.T) {
w := newWorld(t)
pulls := newFakePulls()
w.h.Pulls = pulls
w.h.KeepPull(announcedAt(readyHead, "t1t1t1t1t1t1"))
w.h.KeepVerdict(judgedAt("novox/app", 7, readyHead, "pass", ""))
if got := pulls.get(appKey); got.Verdict != "" {
t.Fatalf("a verdict naming no trunk commit was written: %+v", got)
}
}
// 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)
}
}