Files
mesh-sdk/go/delivery/toolchains_test.go
T
jschoubben a6c6d936d5 Agree a toolchain by strict majority and name the nodes that sit out (ADR 0584, issue 1578)
One build node on another version stopped every build of the language. The agreed toolchain is now the strict majority's; a tie still waits and names the versions.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 15:20:26 +01:00

105 lines
3.7 KiB
Go

package delivery
import (
"strings"
"testing"
"time"
)
func tcEntry(node, lang, version, recipe string) NodeToolchain {
return NodeToolchain{Node: node, Toolchain: Toolchain{Language: lang, Version: version, Recipe: recipe, At: time.Unix(100, 0).UTC()}}
}
// 1443/AC1
func TestToolchainKeyAndValue(t *testing.T) {
key, err := ToolchainKey("ace", "go")
if err != nil || key != "ace.go" {
t.Fatalf("%q %v", key, err)
}
node, lang, err := ParseToolchainKey(key)
if err != nil || node != "ace" || lang != "go" {
t.Errorf("%q %q %v", node, lang, err)
}
for _, bad := range []string{"", "ace", "ace.rust", ".go", "a.b.go", "ace."} {
if _, _, err := ParseToolchainKey(bad); err == nil {
t.Errorf("ParseToolchainKey(%q) passed", bad)
}
}
if _, err := ToolchainKey("ace", "python"); err == nil {
t.Error("language python keyed")
}
want := Toolchain{Language: "go", Version: "1.26.1", Recipe: "3", At: time.Unix(100, 0).UTC()}
data, err := EncodeToolchain(want)
if err != nil {
t.Fatal(err)
}
got, err := DecodeToolchain(data)
want.V = Version
if err != nil || got != want {
t.Errorf("%+v %v", got, err)
}
for name, bad := range map[string]Toolchain{
"python": {Language: "python", Version: "3", Recipe: "1"},
"no version": {Language: "go", Recipe: "1"},
"no recipe": {Language: "typescript", Version: "5"},
} {
if _, err := EncodeToolchain(bad); err == nil {
t.Errorf("%s was encoded", name)
}
if _, err := DecodeToolchain([]byte(`{"v":1,"language":"` + bad.Language + `","version":"` + bad.Version + `","recipe":"` + bad.Recipe + `"}`)); err == nil {
t.Errorf("%s was decoded", name)
}
}
if ToolchainsBucket("go-build") != "go-build_toolchains" {
t.Error(ToolchainsBucket("go-build"))
}
}
// 1443/AC2-4, ADR 0584: the agreed toolchain is the strict majority's; a minority node sits out; a tie waits.
func TestToolchainAgreement(t *testing.T) {
e := tcEntry
for _, c := range []struct {
name string
in []NodeToolchain
version string
out []SittingOut
wait []string // "" wait means agreed
}{
{"all agree", []NodeToolchain{e("ace", "go", "1.26.1", "3"), e("novox", "go", "1.26.1", "3")}, "1.26.1", nil, nil},
{"three against one", []NodeToolchain{e("ace", "typescript", "26.12.1", "3"), e("g14", "typescript", "26.13.0", "3"), e("novox", "typescript", "26.13.0", "3"), e("shanks", "typescript", "26.13.0", "3")},
"26.13.0", []SittingOut{{"ace", "26.12.1", "3"}}, nil},
{"two against two", []NodeToolchain{e("ace", "go", "1.26.1", "3"), e("novox", "go", "1.26.2", "3"), e("g14", "go", "1.26.1", "3"), e("shanks", "go", "1.26.2", "3")},
"", nil, []string{"builders disagree", "1.26.1 recipe 3 on ace, g14", "1.26.2 recipe 3 on novox, shanks"}},
{"one node alone", []NodeToolchain{e("ace", "go", "1.26.1", "3")}, "1.26.1", nil, nil},
{"no report", nil, "", nil, []string{ToolchainNoReport}},
} {
a := ToolchainAgreement(c.in)
rev := make([]NodeToolchain, len(c.in))
for i, x := range c.in {
rev[len(c.in)-1-i] = x
}
if b := ToolchainAgreement(rev); b.Wait != a.Wait || b.Toolchain != a.Toolchain {
t.Errorf("%s: order changed the answer: %+v vs %+v", c.name, a, b)
}
if a.Toolchain.Version != c.version || len(a.SittingOut) != len(c.out) {
t.Errorf("%s: %+v", c.name, a)
}
for i := range c.out {
if a.SittingOut[i] != c.out[i] {
t.Errorf("%s: sitting out %+v, want %+v", c.name, a.SittingOut, c.out)
}
}
if (len(c.wait) == 0) != (a.Wait == "") {
t.Errorf("%s: wait %q", c.name, a.Wait)
}
for _, w := range c.wait {
if !strings.Contains(a.Wait, w) {
t.Errorf("%s: %q does not name %q", c.name, a.Wait, w)
}
}
if tc, none := AgreedToolchain(c.in); tc != a.Toolchain || none != a.Wait {
t.Errorf("%s: AgreedToolchain %+v %q", c.name, tc, none)
}
}
}