mesh/merge-gate head bbb42d704ce0 merged into trunk 5b09d1ccb52d — pass: builds mesh-delivery-checker → novox; no bus step; every machine composes with the…
mesh/repo-check head bbb42d704ce0 merged into trunk 5b09d1ccb52d — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/check pass bbb42d704ce0 on 5b09d1ccb52d | unit 354 run 0 kept | int 392 run 0 kept | clone 8.7s unit 199ms int 7.1s+44s | composition not yet run
143 lines
5.0 KiB
Go
143 lines
5.0 KiB
Go
package check
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Issue 1820: a build step goes to a node whose build module is not heard (a laptop asleep, not yet marked offline by the
|
|
// watchman), and the bus answers "no responders". The check moves the step to another node of the same build module.
|
|
|
|
// deafNodes makes every tool on the named nodes fail as the bus does when nobody listens there; count says how many
|
|
// times the node was called.
|
|
func deafNodes(b *bench, deaf ...string) {
|
|
for tool, h := range b.steps.handlers {
|
|
h := h
|
|
b.steps.handlers[tool] = func(c stepCall) (any, error) {
|
|
for _, n := range deaf {
|
|
if c.node == n {
|
|
return nil, fmt.Errorf("%s on %s: %w", c.tool, c.node, nats.ErrNoResponders)
|
|
}
|
|
}
|
|
return h(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func nodesOf(calls []stepCall) []string {
|
|
var out []string
|
|
for _, c := range calls {
|
|
out = append(out, c.node)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestAFetchThatNobodyAnswersMovesToAnotherBuildNode(t *testing.T) { // 1820/AC1
|
|
b := newBench(t)
|
|
b.ex.Toolchains = goEntries("ace", "g14")
|
|
deafNodes(b, "ace")
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictPass {
|
|
t.Fatalf("verdict = %+v, want a pass on the node that answers", v)
|
|
}
|
|
if got := nodesOf(b.steps.tool("fetch")); len(got) != 2 || got[0] != "ace" || got[1] != "g14" {
|
|
t.Errorf("fetch went to %v, want ace (no responders) then g14", got)
|
|
}
|
|
for _, c := range b.steps.tool("test") {
|
|
if c.node != "g14" {
|
|
t.Errorf("a test went to %s, want g14 only", c.node)
|
|
}
|
|
}
|
|
if len(v.Machines) != 1 || v.Machines[0] != "g14" {
|
|
t.Errorf("machines = %v, want [g14]: ace ran nothing", v.Machines)
|
|
}
|
|
}
|
|
|
|
func TestATestStepThatNobodyAnswersIsFetchedAgainOnAnotherNode(t *testing.T) { // 1820/AC2
|
|
b := newBench(t)
|
|
b.ex.Toolchains = goEntries("ace", "g14")
|
|
// ace answers the fetch and the digest, then goes to sleep before the test.
|
|
test := b.steps.handlers["build_go_test"]
|
|
b.steps.handlers["build_go_test"] = func(c stepCall) (any, error) {
|
|
if c.node == "ace" {
|
|
return nil, fmt.Errorf("test on ace: %w", nats.ErrNoResponders)
|
|
}
|
|
return test(c)
|
|
}
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictPass {
|
|
t.Fatalf("verdict = %+v, want a pass", v)
|
|
}
|
|
if got := nodesOf(b.steps.tool("fetch")); len(got) != 2 || got[1] != "g14" {
|
|
t.Errorf("fetch went to %v, want ace then g14 (the workspace was on ace)", got)
|
|
}
|
|
for _, c := range b.steps.tool("test") {
|
|
if c.node == "g14" && c.args["workspace"] == nil {
|
|
t.Errorf("a test on g14 has no workspace")
|
|
}
|
|
}
|
|
if len(v.Machines) != 1 || v.Machines[0] != "g14" {
|
|
t.Errorf("machines = %v, want [g14]", v.Machines)
|
|
}
|
|
}
|
|
|
|
func TestWhenNoBuildNodeAnswersTheFailureNamesEveryNodeTried(t *testing.T) { // 1820/AC3
|
|
b := newBench(t)
|
|
b.ex.Toolchains = goEntries("ace", "g14", "shanks")
|
|
deafNodes(b, "ace", "g14", "shanks")
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictFail {
|
|
t.Fatalf("verdict = %+v, want a failure", v)
|
|
}
|
|
for _, n := range []string{"ace", "g14", "shanks"} {
|
|
if !strings.Contains(v.Failed.Words, n) {
|
|
t.Errorf("failure %q does not name %s", v.Failed.Words, n)
|
|
}
|
|
}
|
|
if !IsNodesFailure(v) {
|
|
t.Errorf("failure %q is not a failure of the nodes, so it would not be judged again when they change", v.Failed.Words)
|
|
}
|
|
if got := len(b.steps.tool("fetch")); got != 3 {
|
|
t.Errorf("fetch was tried %d times, want once on each of 3 nodes", got)
|
|
}
|
|
}
|
|
|
|
func TestAStepThatFailedForTheChangeIsNotMovedToAnotherNode(t *testing.T) { // 1820/AC4
|
|
b := newBench(t)
|
|
b.ex.Toolchains = goEntries("ace", "g14")
|
|
b.steps.handlers["build_go_fetch"] = func(stepCall) (any, error) { return nil, fmt.Errorf("fetch on ace: the step failed: git exploded") }
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict
|
|
if v.State != delivery.VerdictFail || len(b.steps.tool("fetch")) != 1 {
|
|
t.Errorf("verdict = %+v after %d fetches, want a failure after one", v, len(b.steps.tool("fetch")))
|
|
}
|
|
}
|
|
|
|
func TestACanceledCheckIsNotMovedToAnotherNode(t *testing.T) { // 1820/AC4
|
|
b := newBench(t)
|
|
b.ex.Toolchains = goEntries("ace", "g14")
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
b.steps.handlers["build_go_fetch"] = func(stepCall) (any, error) {
|
|
cancel()
|
|
return nil, fmt.Errorf("fetch on ace: %w", nats.ErrNoResponders)
|
|
}
|
|
_, _ = b.ex.Judge(ctx, caseOf(t, pullOf(repoA, 7, head1)))
|
|
if n := len(b.steps.tool("fetch")); n != 1 {
|
|
t.Errorf("fetch was tried %d times after the check ended, want 1", n)
|
|
}
|
|
}
|
|
|
|
func TestAPinReadThatNobodyAnswersIsTheForgesFailureNotABuildNodes(t *testing.T) { // 1820/AC5
|
|
b := groupBench(t, "cb54756c3865")
|
|
b.ex.Forge.(*fakeForge).err = fmt.Errorf("gitea.gitea_get_file: %w", nats.ErrNoResponders)
|
|
v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict
|
|
if v.State != delivery.VerdictFail || v.Failed.Machine != "forge" {
|
|
t.Errorf("verdict = %+v, want a failure at the forge: no build node was to blame", v)
|
|
}
|
|
}
|