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
105 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|