Files
mesh-sdk/go/delivery/assigner_test.go
T
jschoubben d6cef5829b Have a want name its composition and a backup answer; give the node its own want, the seventh rule, the held phase and the backup and record asks (issue 1943, S3)
The node agent applies only the composition its want names, and the assigner writes a want only once that
composition exists (ADR 0539 clauses 1, 6, 14 and 16).
2026-10-24 16:29:24 +02:00

266 lines
10 KiB
Go

package delivery
import (
"errors"
"strings"
"testing"
)
// 1297/B1a: a want key round-trips through its parser; a held key, a have key and a node's own key are refused,
// naming the key.
func TestWantKey(t *testing.T) {
key, err := WantKey("ace", "mesh-mail")
if err != nil || key != "want.ace.mesh-mail" {
t.Fatalf("WantKey = %q, %v", key, err)
}
node, module, err := ParseWantKey(key)
if err != nil || node != "ace" || module != "mesh-mail" {
t.Errorf("ParseWantKey = %q, %q, %v", node, module, err)
}
for _, n := range [][2]string{{"ace", "node"}, {"a.b", "m"}, {"ace", ""}, {"", "m"}, {"ace", "m*"}} {
if _, err := WantKey(n[0], n[1]); err == nil {
t.Errorf("WantKey(%q, %q) accepted", n[0], n[1])
}
}
held, _ := HeldKey("mesh-mail", fp2of('a'))
for _, bad := range []string{"ace.mesh-mail", "want.ace.node", "want.ace", "want.ace.m.x", held, "wants.ace.m", ""} {
if _, _, err := ParseWantKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseWantKey(%q) = %v, want a refusal naming it", bad, err)
}
}
}
// 1297/B1a: a held key round-trips; a want key, a builder build key and a fingerprint not of the new form are refused.
func TestHeldKey(t *testing.T) {
fp := fp2of('b')
key, err := HeldKey("mesh-mail", fp)
if err != nil || key != "held.mesh-mail."+strings.Repeat("b", 64) {
t.Fatalf("HeldKey = %q, %v", key, err)
}
module, back, err := ParseHeldKey(key)
if err != nil || module != "mesh-mail" || back != fp {
t.Errorf("ParseHeldKey = %q, %q, %v", module, back, err)
}
src1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"})
if _, err := HeldKey("mesh-mail", src1); err == nil {
t.Errorf("a src1 fingerprint was held")
}
build, _ := BuilderBuildKey("mesh-mail", fp)
for _, bad := range []string{build, "want.ace.mesh-mail", "held.mesh-mail.abc", "held." + strings.Repeat("b", 64), ""} {
if _, _, err := ParseHeldKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseHeldKey(%q) = %v, want a refusal naming it", bad, err)
}
}
}
// 1297/B1a: a difference key round-trips; a want or held key and a node's own key are refused.
func TestDifferenceKey(t *testing.T) {
key, err := DifferenceKey("g14", "mesh-mail")
if err != nil || key != "g14.mesh-mail" {
t.Fatalf("DifferenceKey = %q, %v", key, err)
}
node, module, err := ParseDifferenceKey(key)
if err != nil || node != "g14" || module != "mesh-mail" {
t.Errorf("ParseDifferenceKey = %q, %q, %v", node, module, err)
}
held, _ := HeldKey("mesh-mail", fp2of('a'))
for _, bad := range []string{"want.g14.mesh-mail", held, "g14.node", "g14", ""} {
if _, _, err := ParseDifferenceKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseDifferenceKey(%q) = %v, want a refusal naming it", bad, err)
}
}
}
// 1297/B1a: wants, holds and differences round-trip through their encoders unchanged, at Version.
func TestAssignerValuesRoundTrip(t *testing.T) {
wants := []Want{
{FP: fp2of('a'), Composed: hashOf('1')},
{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleCanary, Words: "the canary ace does not run it healthy yet"},
{Removed: true, Words: "the module left g14"},
}
for _, w := range wants {
data, err := EncodeWant(w)
if err != nil {
t.Fatalf("EncodeWant(%+v): %v", w, err)
}
got, err := DecodeWant(data)
w.V = Version
if err != nil || !SameWant(got, w) {
t.Errorf("want %+v came back %+v, %v", w, got, err)
}
}
h := Held{Node: "ace", Words: "its health check failed: connection refused"}
data, err := EncodeHeld(h)
if err != nil {
t.Fatal(err)
}
gotH, err := DecodeHeld(data)
h.V = Version
if err != nil || !SameHeld(gotH, h) {
t.Errorf("held came back %+v, %v", gotH, err)
}
diffs := []Difference{
{Want: fp2of('a'), Sent: fp2of('b'), Rule: RuleCanary},
{Sent: fp2of('b'), Rule: RuleBuildDone},
{WantRemoved: true, Sent: fp2of('b'), Unexplained: true},
{SentBuild: "build-17", Rule: RuleGrant},
}
for _, d := range diffs {
data, err := EncodeDifference(d)
if err != nil {
t.Fatalf("EncodeDifference(%+v): %v", d, err)
}
got, err := DecodeDifference(data)
d.V = Version
if err != nil || !SameDifference(got, d) {
t.Errorf("difference %+v came back %+v, %v", d, got, err)
}
}
}
// 1297/B1a: what a want, a hold and a difference may not say is refused on encode and on decode.
func TestAssignerValuesRefused(t *testing.T) {
src1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"})
for _, w := range []Want{
{},
{FP: src1},
{Removed: true, FP: fp2of('a')},
{FP: fp2of('a'), Newest: fp2of('b')},
{FP: fp2of('a'), Newest: fp2of('a'), Rule: 3},
{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 8},
} {
if _, err := EncodeWant(w); err == nil {
t.Errorf("EncodeWant(%+v) accepted", w)
}
}
if _, err := EncodeHeld(Held{Node: "a.b"}); err == nil {
t.Errorf("a held naming a dotted node was accepted")
}
for _, d := range []Difference{
{Want: fp2of('a'), Sent: fp2of('b')},
{Want: fp2of('a'), Sent: fp2of('b'), Rule: 3, Unexplained: true},
{Want: fp2of('a'), Sent: fp2of('a'), Rule: 3},
{},
{Want: fp2of('a'), Sent: fp2of('b'), Rule: 9},
{WantRemoved: true, Want: fp2of('a'), Rule: 1},
} {
if _, err := EncodeDifference(d); err == nil {
t.Errorf("EncodeDifference(%+v) accepted", d)
}
}
if _, err := DecodeWant([]byte(`{"v":2,"fp":"` + fp2of('a') + `"}`)); !errors.Is(err, ErrUnknownVersion) {
t.Errorf("a want of version 2 was read: %v", err)
}
if _, err := DecodeDifference([]byte(`{"v":1,"want":"` + fp2of('a') + `","sent":"` + fp2of('b') + `"}`)); err == nil {
t.Errorf("a difference naming no rule was read")
}
}
// 1297/B1b: a composition key is the checker's verdict key of the same check, and round-trips; a want, held or
// difference key is refused, naming it.
func TestCompositionKey(t *testing.T) {
key, err := CompositionKey("feat/r4g", "0123456789ab")
verdict, _ := CheckerVerdictKey("feat/r4g", "0123456789ab")
if err != nil || key != verdict {
t.Fatalf("CompositionKey = %q, %v; the checker's key is %q", key, err, verdict)
}
group, heads, err := ParseCompositionKey(key)
if err != nil || group != "feat/r4g" || heads != "0123456789ab" {
t.Errorf("ParseCompositionKey = %q, %q, %v", group, heads, err)
}
held, _ := HeldKey("mesh-mail", fp2of('a'))
for _, bad := range []string{"want.ace.mesh-mail", held, "ace.mesh-mail", "g.0123456789AB", ""} {
if _, _, err := ParseCompositionKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseCompositionKey(%q) = %v, want a refusal naming it", bad, err)
}
}
}
// 1297/B1b: a composition in each state round-trips; the checker's composition of a pass or fail carries its state,
// words, mesh-sdk commit and machines, and a wait gives none.
func TestCompositionVerdictRoundTrip(t *testing.T) {
for _, c := range []CompositionVerdict{
{Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "7aacbc1", Machines: []string{"ace"}, Took: 120},
{Group: "g", Heads: "0123456789ab", State: CompositionFail, SDK: "7aacbc1", Machines: []string{"ace", "g14"}, Machine: "g14",
Words: "g14 runs mesh-mail, which the change removes from its source", Warnings: []string{"the merge rebuilds 13 modules"}},
{Group: "g", Heads: "0123456789ab", State: CompositionWaits, Words: "the indexer holds no candidates for the group yet"},
} {
data, err := EncodeCompositionVerdict(c)
if err != nil {
t.Fatalf("EncodeCompositionVerdict(%+v): %v", c, err)
}
got, err := DecodeCompositionVerdict(data)
c.V = Version
if err != nil || !SameCompositionVerdict(got, c) {
t.Errorf("composition %+v came back %+v, %v", c, got, err)
}
comp := got.Composition()
if c.State == CompositionWaits {
if comp != nil {
t.Errorf("a wait gave the checker a composition %+v", comp)
}
continue
}
if comp == nil || comp.State != c.State || comp.SDK != c.SDK || comp.Words != c.Words || len(comp.Machines) != len(c.Machines) {
t.Errorf("the checker's composition of %+v is %+v", c, comp)
}
if err := comp.check(); err != nil {
t.Errorf("the checker's composition of %+v is refused by the verdict: %v", c, err)
}
}
}
// 1297/B1b: what a composition may not say is refused.
func TestCompositionVerdictRefused(t *testing.T) {
for _, c := range []CompositionVerdict{
{Heads: "0123456789ab", State: CompositionPass, SDK: "x"},
{Group: "g", Heads: "0123", State: CompositionPass, SDK: "x"},
{Group: "g", Heads: "0123456789ab", State: CompositionPass},
{Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "x", Machine: "ace"},
{Group: "g", Heads: "0123456789ab", State: CompositionFail, SDK: "x"},
{Group: "g", Heads: "0123456789ab", State: CompositionFail, Words: "w"},
{Group: "g", Heads: "0123456789ab", State: CompositionWaits},
{Group: "g", Heads: "0123456789ab", State: CompositionWaits, Words: "w", Machines: []string{"ace"}},
{Group: "g", Heads: "0123456789ab", State: CompositionNotRun},
{Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "x", Took: -1},
} {
if _, err := EncodeCompositionVerdict(c); err == nil {
t.Errorf("EncodeCompositionVerdict(%+v) accepted", c)
}
}
}
// 1394/B1b: a grant key round-trips; a want or held key is refused, naming it; a grant round-trips and one naming an
// empty account is refused.
func TestControllerGrant(t *testing.T) {
fp := fp2of('c')
key, err := ControllerGrantKey("mesh-delivery-assigner", fp)
if err != nil || key != "mesh-delivery-assigner."+strings.Repeat("c", 64) {
t.Fatalf("ControllerGrantKey = %q, %v", key, err)
}
module, back, err := ParseControllerGrantKey(key)
if err != nil || module != "mesh-delivery-assigner" || back != fp {
t.Errorf("ParseControllerGrantKey = %q, %q, %v", module, back, err)
}
held, _ := HeldKey("mesh-mail", fp)
for _, bad := range []string{"want.ace.mesh-mail", held, "ace.mesh-mail", ""} {
if _, _, err := ParseControllerGrantKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseControllerGrantKey(%q) = %v, want a refusal naming it", bad, err)
}
}
for _, g := range []ControllerGrant{{}, {Accounts: []string{"UABC", "UDEF"}}} {
data, err := EncodeControllerGrant(g)
if err != nil {
t.Fatal(err)
}
got, err := DecodeControllerGrant(data)
g.V = Version
if err != nil || !SameControllerGrant(got, g) {
t.Errorf("grant %+v came back %+v, %v", g, got, err)
}
}
if _, err := EncodeControllerGrant(ControllerGrant{Accounts: []string{""}}); err == nil {
t.Errorf("a grant naming an empty account was accepted")
}
}