package build import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" ) func tcs(language, version string, nodes ...string) []delivery.NodeToolchain { var out []delivery.NodeToolchain for _, n := range nodes { out = append(out, delivery.NodeToolchain{Node: n, Toolchain: delivery.Toolchain{Language: language, Version: version, Recipe: "1"}}) } return out } func TestChooseTakesTheAgreedNodesWithFewestRunningStepsTiesByName(t *testing.T) { // 1607/AC4, ADR 0478 clause 3 entries := tcs("go", "go1.27.1", "novox", "ace", "g14", "shanks") nodes, tc, err := ChooseNodes("go", entries, placesRunning(map[string]int{"novox": 3, "ace": 1, "g14": 1, "shanks": 0}), nil, 2) if err != nil { t.Fatal(err) } if len(nodes) != 2 || nodes[0] != "shanks" || nodes[1] != "ace" { t.Errorf("nodes = %v, want [shanks ace]: fewest steps first, a tie by name", nodes) } if tc.Version != "go1.27.1" || tc.Recipe != "1" || tc.Language != "go" { t.Errorf("toolchain = %+v", tc) } } func TestChooseWithOneNodeAnswersOneNode(t *testing.T) { // 1607/AC4 nodes, _, err := ChooseNodes("go", tcs("go", "go1.27.1", "novox"), nil, nil, 2) if err != nil || len(nodes) != 1 || nodes[0] != "novox" { t.Errorf("nodes = %v, err %v, want [novox]", nodes, err) } } func TestChooseFailsNamingTheLanguageAndTheVersionsWhenNoNodeQualifies(t *testing.T) { // 1607/AC4 _, _, err := ChooseNodes("typescript", nil, nil, nil, 2) if err == nil || !strings.Contains(err.Error(), "typescript") || !strings.Contains(err.Error(), delivery.ToolchainNoReport) { t.Errorf("no report: error = %v, want it to name typescript and %q", err, delivery.ToolchainNoReport) } entries := append(tcs("go", "go1.27.1", "novox"), tcs("go", "go1.26.0", "ace")...) _, _, err = ChooseNodes("go", entries, nil, nil, 2) if err == nil || !strings.Contains(err.Error(), "go1.27.1") || !strings.Contains(err.Error(), "go1.26.0") || !strings.Contains(err.Error(), "go") { t.Errorf("disagreeing nodes: error = %v, want both versions named", err) } } func TestAgreeNeedsEqualDigestsAndNamesBothNodesWhenTheyDiffer(t *testing.T) { // 1607/AC4, ADR 0478 clause 4 d, err := Agree("tool", map[string]string{"ace": digest, "novox": digest}) if err != nil || d != digest { t.Errorf("Agree = %q, %v, want %q", d, err, digest) } _, err = Agree("tool", map[string]string{"ace": digest, "novox": "sha256:" + hex64('9')}) if err == nil || !strings.Contains(err.Error(), "ace") || !strings.Contains(err.Error(), "novox") || !strings.Contains(err.Error(), digest) || !strings.Contains(err.Error(), hex64('9')) || !strings.Contains(err.Error(), "tool") { t.Errorf("differing digests: error = %v, want the artifact, both nodes and both digests", err) } if d, err := Agree("tool", map[string]string{"ace": digest}); err != nil || d != digest { t.Errorf("one node: %q, %v", d, err) } } func TestChooseSkipsNodesThatAreNotOnlineAndFailsNamingThemWhenNoneIs(t *testing.T) { // 1607 review 3, ADR 0478 clause 3 entries := tcs("go", "go1.27.1", "ace", "g14", "novox") online := func(n string) bool { return n != "g14" } // a sleeping laptop nodes, tc, err := ChooseNodes("go", entries, placesRunning(map[string]int{"novox": 5}), online, 2) if err != nil || len(nodes) != 2 || nodes[0] != "ace" || nodes[1] != "novox" || tc.Version != "go1.27.1" { t.Errorf("nodes = %v, toolchain %+v, err %v, want [ace novox] with g14 left out although it runs nothing", nodes, tc, err) } _, tc, err = ChooseNodes("go", entries, nil, func(string) bool { return false }, 2) if err == nil || !strings.Contains(err.Error(), "online") || !strings.Contains(err.Error(), "g14") || !strings.Contains(err.Error(), "go") { t.Errorf("none online: error = %v, want it to name the nodes that report and that none is online", err) } if tc.Version != "go1.27.1" { t.Errorf("none online: the agreed toolchain %+v is still answered", tc) } } // placesRunning is Places with these steps counted as running, for a test of the choice. func placesRunning(running map[string]int) *Places { return &Places{running: running, changed: make(chan struct{})} } func TestChoosePrefersNodesBelowTheirCapacity(t *testing.T) { // round 3 entries := tcs("go", "go1.27.1", "ace", "novox", "shanks") // shanks has capacity 3 with 2 running: room; ace has the default 1 with 1 running: none; novox has none running. p := placesRunning(map[string]int{"shanks": 2, "ace": 1}) p.Capacity = map[string]int{"shanks": 3} nodes, _, err := ChooseNodes("go", entries, p, nil, 2) if err != nil || len(nodes) != 2 || nodes[0] != "novox" || nodes[1] != "shanks" { t.Errorf("nodes = %v, err %v, want [novox shanks]: the nodes with room, ace at capacity left out", nodes, err) } }