142 lines
4.9 KiB
Go
142 lines
4.9 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
func TestAReleaseSendsEveryMachineAtOnce(t *testing.T) {
|
|
judgeLeavesAsTheCore(t)
|
|
b := aBacklogOnThree(t)
|
|
ctx := t.Context()
|
|
said := events(t)
|
|
for i := 0; i < 12; i++ {
|
|
advancePlans(ctx, b.open)
|
|
}
|
|
p := b.release(t)
|
|
if p.State != inventory.PlanDone || len(p.Release.Done) != 3 {
|
|
t.Fatalf("the release is %s: %s %+v", p.State, p.Note, p.Release)
|
|
}
|
|
for _, n := range []string{"desk", "laptop", "anchor"} {
|
|
if !slices.Contains(*said, "send "+n) {
|
|
t.Fatalf("%s was not sent: %v", n, *said)
|
|
}
|
|
}
|
|
if slices.Contains(*said, "judge") {
|
|
t.Fatalf("a machine was judged by its health: %v", *said)
|
|
}
|
|
if v, found, err := b.open.inventory.GateOf(ctx, "build-late-c2"); err != nil || !found || v.Verdict != inventory.GatePassed {
|
|
t.Fatalf("late's pass on laptop was not kept: %+v %v %v", v, found, err)
|
|
}
|
|
}
|
|
|
|
func TestAMachineThatFailsItsApplyIsNamedAndNothingIsPutBack(t *testing.T) {
|
|
judgeLeavesAsTheCore(t)
|
|
b := aBacklogOnThree(t)
|
|
ctx := t.Context()
|
|
inv := b.open.inventory
|
|
b.broken["desk"] = true // desk fails what it is sent
|
|
advancePlans(ctx, b.open)
|
|
for i := 0; i < 12 && b.release(t).Open(); i++ {
|
|
advancePlans(ctx, b.open)
|
|
}
|
|
p := b.release(t)
|
|
if p.State != inventory.PlanFailed || !slices.Equal(p.Release.Failed, []string{"desk"}) {
|
|
t.Fatalf("the release is %s: %s %+v", p.State, p.Note, p.Release)
|
|
}
|
|
if !slices.Contains(p.Release.Done, "laptop") || !slices.Contains(p.Release.Done, "anchor") {
|
|
t.Fatalf("the others have no verdict of their own: %+v", p.Release)
|
|
}
|
|
if v, found, err := inv.GateOf(ctx, "build-late-c2"); err != nil || !found || v.Verdict != inventory.GatePassed {
|
|
t.Fatalf("late's pass on laptop was not kept: %+v %v %v", v, found, err)
|
|
}
|
|
if sent, _, _ := inv.SentBuilds(ctx, "desk"); sent["app"] != "c2" {
|
|
t.Fatalf("desk was put back: %v", sent)
|
|
}
|
|
}
|
|
|
|
func TestAReleaseSendKeptAndMadeIsNotMadeAgainAtOnce(t *testing.T) {
|
|
judgeLeavesAsTheCore(t)
|
|
b := aBacklog(t)
|
|
ctx := t.Context()
|
|
inv := b.open.inventory
|
|
p, err := releaseBacklog(ctx, b.open, "")
|
|
if err != nil || p == nil {
|
|
t.Fatalf("no release opened: %v", err)
|
|
}
|
|
if _, err := advanceRelease(ctx, b.open, p); err != nil || p.Release.Gates["anchor"] == nil || !p.Release.Gates["anchor"].Pending {
|
|
t.Fatalf("the send was not kept before it was made: %+v %v", p.Release, err)
|
|
}
|
|
if err := inv.SavePlan(ctx, p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The send is made, and the controller is replaced before it keeps it.
|
|
if _, err := sendRollout(ctx, b.open, []string{"anchor"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
advancePlans(ctx, b.open)
|
|
if n := len(slices.DeleteFunc(slices.Clone(b.sent), func(s []string) bool { return !slices.Equal(s, []string{"anchor"}) })); n != 1 {
|
|
t.Fatalf("anchor was sent again: %v", b.sent)
|
|
}
|
|
g := b.release(t).Release.Gates["anchor"]
|
|
if g == nil || g.Pending || g.Since == nil || len(g.Carried) == 0 {
|
|
t.Fatalf("anchor is not judged on the send made: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestAMachineSilentPastItsBoundIsLeftAndSaidAtOnce(t *testing.T) {
|
|
judgeLeavesAsTheCore(t)
|
|
b := aBacklog(t)
|
|
ctx := t.Context()
|
|
inv := b.open.inventory
|
|
reporting := sendRollout
|
|
sendRollout = func(ctx context.Context, open *stores, names []string) ([]string, error) {
|
|
if slices.Equal(names, []string{"laptop"}) { // laptop sleeps: sent, and says nothing
|
|
b.sent = append(b.sent, names)
|
|
current, _ := open.inventory.CurrentBuilds(ctx)
|
|
return names, open.inventory.RecordSent(ctx, nodeID(t, open, "laptop"), "d-laptop-asleep",
|
|
map[string]string{"app": current["app"].Commit, "late": current["late"].Commit, "same": current["same"].Commit})
|
|
}
|
|
return reporting(ctx, open, names)
|
|
}
|
|
t.Cleanup(func() { sendRollout = reporting })
|
|
for i := 0; i < 8; i++ {
|
|
advancePlans(ctx, b.open)
|
|
}
|
|
p := b.release(t)
|
|
g := p.Release.Gates["laptop"]
|
|
if !p.Open() || g == nil || g.Verdict != "" || g.Silent {
|
|
t.Fatalf("laptop is judged before fifteen minutes: %s %s %+v", p.State, p.Note, g)
|
|
}
|
|
since := time.Now().Add(-inventory.ApplySilentAfter - time.Minute)
|
|
g.Since = &since
|
|
if err := inv.SavePlan(ctx, &p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
advancePlans(ctx, b.open)
|
|
p = b.release(t)
|
|
// anchor applied in the same step, so the release is done, laptop named silent (novox/hq ADR 0360 §7).
|
|
if p.State != inventory.PlanDone || !slices.Equal(p.Release.Silent, []string{"laptop"}) || len(p.Release.Failed) > 0 {
|
|
t.Fatalf("the release is %s: %s %+v", p.State, p.Note, p.Release)
|
|
}
|
|
if failed, _ := inv.GateFailed(ctx, "build-late-c2"); failed {
|
|
t.Fatal("a silent machine's build was marked failed")
|
|
}
|
|
if sent, _, _ := inv.SentBuilds(ctx, "laptop"); sent["late"] != "c2" {
|
|
t.Fatalf("laptop was put back: %v", sent)
|
|
}
|
|
obs, err := silentObservation(ctx, inv)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(obs) != 1 || obs[0].Kind != "release-silent" || !slices.Equal(obs[0].Also, []string{"laptop"}) {
|
|
t.Fatalf("the silence is not said: %+v", obs)
|
|
}
|
|
}
|