From cf51cc7dbfd19fd42a0f0d81c664e81d0438cd3b Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 11:10:07 +0100 Subject: [PATCH] Say a failed test's whole output on the Go and Node build modules' own log streams, kept 14 days and 2 GiB, and read it with build_go_log and build_node_log, so a cut verdict's cause is read, not guessed --- modules/mesh-build-go/cmd/mesh-build-go/asks.go | 3 +++ .../mesh-build-go/cmd/mesh-build-go/emit_test.go | 8 +++++++- modules/mesh-build-go/cmd/mesh-build-go/main.go | 2 ++ modules/mesh-build-go/cmd/mesh-build-go/module.go | 3 +++ .../mesh-build-go/cmd/mesh-build-go/module_test.go | 2 +- modules/mesh-build-go/cmd/mesh-build-go/read.go | 14 ++++++++++++-- modules/mesh-build-go/go.mod | 2 +- modules/mesh-build-go/go.sum | 6 ++++++ modules/mesh-build-go/module.json | 7 +++++++ modules/mesh-build-node/.gitignore | 1 + .../mesh-build-node/cmd/mesh-build-node/asks.go | 3 +++ .../cmd/mesh-build-node/emit_test.go | 8 +++++++- .../mesh-build-node/cmd/mesh-build-node/main.go | 2 ++ .../mesh-build-node/cmd/mesh-build-node/module.go | 3 +++ .../cmd/mesh-build-node/module_test.go | 2 +- .../mesh-build-node/cmd/mesh-build-node/read.go | 14 ++++++++++++-- modules/mesh-build-node/go.mod | 2 +- modules/mesh-build-node/go.sum | 6 ++++++ modules/mesh-build-node/module.json | 7 +++++++ 19 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 modules/mesh-build-node/.gitignore diff --git a/modules/mesh-build-go/cmd/mesh-build-go/asks.go b/modules/mesh-build-go/cmd/mesh-build-go/asks.go index ac6bac89..7b53af02 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/asks.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/asks.go @@ -47,6 +47,9 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps { Has: m.Has, Release: func(ws string) error { return m.spaces().Release(ws) }, Logf: m.Say, + // A failed test's whole output is said on the module's own log stream (buildstep.LogTool reads it back). + Narrate: m.Emit, + Module: "mesh-build-go", } } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/emit_test.go b/modules/mesh-build-go/cmd/mesh-build-go/emit_test.go index 71fb11d5..bc826e03 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/emit_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/emit_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "git.novox.be/novox/mesh-sdk/go/buildstep" "os" "path/filepath" "slices" @@ -47,9 +48,14 @@ func TestTheManifestDeclaresTheToolchainsStream(t *testing.T) { Streams []struct { Name string `json:"name"` Events []string `json:"events"` + Age int `json:"max-age-days"` + MiB int `json:"max-mib"` } `json:"streams"` } - if err := json.Unmarshal(raw, &m); err != nil || len(m.Streams) != 1 || m.Streams[0].Name != "toolchains-go" || !slices.Equal(m.Streams[0].Events, []string{"toolchain.**"}) { + // And its log stream: a failed test's whole output, kept 14 days and at most 2 GiB, dropping its oldest (the operator, + // 2026-10-30). + if err := json.Unmarshal(raw, &m); err != nil || len(m.Streams) != 2 || m.Streams[0].Name != "toolchains-go" || !slices.Equal(m.Streams[0].Events, []string{"toolchain.**"}) || + m.Streams[1].Name != buildstep.LogStreamOf("go") || !slices.Equal(m.Streams[1].Events, []string{"log.**"}) || m.Streams[1].Age != 14 || m.Streams[1].MiB != 2048 { t.Errorf("streams = %+v (%v)", m.Streams, err) } } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/main.go b/modules/mesh-build-go/cmd/mesh-build-go/main.go index 4e96528b..782b7de6 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/main.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/main.go @@ -14,6 +14,7 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -41,6 +42,7 @@ func main() { Emit: func(t string, b any) error { return stdio.Emit(t, b) }, Steps: stdio.State(delivery.StepsState).Put, ReadSteps: readOf(stdio.State(delivery.StepsState)), + ReadLog: stdio.Stream(buildstep.LogStreamOf("go")).Read, Pauses: stdio.State(delivery.PausesState).Put, ReadPauses: readOf(stdio.State(delivery.PausesState)), ReadToolchainsState: readOf(stdio.State(delivery.ToolchainsState)), diff --git a/modules/mesh-build-go/cmd/mesh-build-go/module.go b/modules/mesh-build-go/cmd/mesh-build-go/module.go index 3c9ea34c..376ae3a8 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/module.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/module.go @@ -31,6 +31,9 @@ type Module struct { // Held is the puller whose running asks a step-end tool will not end (issue 1890); nil in a test. Held *buildstep.Puller ReadSteps, ReadToolchainsState StateRead + // ReadLog reads the module's own log stream (stdio.Stream(buildstep.LogStreamOf(language)).Read), where each step + // says a failed test's whole output (Emit says it); nil in a test. + ReadLog buildstep.StreamRead // Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back. Pauses buildstep.Put ReadPauses StateRead diff --git a/modules/mesh-build-go/cmd/mesh-build-go/module_test.go b/modules/mesh-build-go/cmd/mesh-build-go/module_test.go index c61e959f..34863af6 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/module_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/module_test.go @@ -115,7 +115,7 @@ func TestAGoThatCannotBeRunRefusesEveryStepAndHealthIsUnhealthy(t *testing.T) { {Name: "short_probe", Run: run}, }, m.StepTools()...) for _, tool := range m.Tools(steps) { - if tool.Name == StatusTool || tool.Name == HealthTool || tool.Name == StepReadTool || tool.Name == StepsReadTool || tool.Name == ToolchainsReadTool || tool.Name == ReleaseTool || tool.Name == StepEndTool { + if tool.Name == StatusTool || tool.Name == HealthTool || tool.Name == StepReadTool || tool.Name == StepsReadTool || tool.Name == LogReadTool || tool.Name == ToolchainsReadTool || tool.Name == ReleaseTool || tool.Name == StepEndTool { continue } var reason string diff --git a/modules/mesh-build-go/cmd/mesh-build-go/read.go b/modules/mesh-build-go/cmd/mesh-build-go/read.go index c0f9b979..cd0695d8 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/read.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/read.go @@ -15,13 +15,16 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/delivery" ) // The read tools, named as the module is: build_go_ and the verb. const ( - StepReadTool = "build_go_step" - StepsReadTool = "build_go_steps" + StepReadTool = "build_go_step" + StepsReadTool = "build_go_steps" + // LogReadTool reads one step's log, a failed test's whole output, from the module's own log stream. + LogReadTool = "build_go_log" ToolchainsReadTool = "build_go_toolchains" ) @@ -225,7 +228,14 @@ func (m *Module) ReadToolchains() (map[string]delivery.Toolchain, error) { // ReadTools are the module's read tools. All are read-only. func (m *Module) ReadTools() []stdio.Tool { + readLog := m.ReadLog + if readLog == nil { + readLog = func(string, uint64, int) (*stdio.StreamPage, error) { + return nil, fmt.Errorf("this process reads no log stream: it was not launched by the runtime") + } + } return []stdio.Tool{ + buildstep.LogTool(LogReadTool, readLog), {Name: StepReadTool, Description: "What one step of this machine ended with, as stored in the module's steps state: its tool, state " + "(running, passed, failed, refused), when it started and ended, why it failed or was refused, and its result " + diff --git a/modules/mesh-build-go/go.mod b/modules/mesh-build-go/go.mod index 1d51cdb8..c2dc7c75 100644 --- a/modules/mesh-build-go/go.mod +++ b/modules/mesh-build-go/go.mod @@ -3,7 +3,7 @@ module meshbuildgo go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-build-go/go.sum b/modules/mesh-build-go/go.sum index c8ee2177..2ea05326 100644 --- a/modules/mesh-build-go/go.sum +++ b/modules/mesh-build-go/go.sum @@ -1,5 +1,11 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 h1:Tt16HJk8JZRce1i3JzzDSNdCWOrv7dw8FQ/bckNQ5Ig= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095131-16bc0dcf3e9d h1:jFOz6wUfZvIayd4DctwlaFKKZHQNME6NLs2OS1rNv30= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095131-16bc0dcf3e9d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095816-530d8d3007dc h1:Wdjl2gomooHswJ5qpkwZPq/ureZgXVw6FMCsgzPJhPo= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095816-530d8d3007dc/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde h1:eKXSllHCS31GgMazfvZ7BeiLoXIUibnUmzxWVkj+Vcw= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index 88fbba9b..b96b3cad 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -51,6 +51,7 @@ "build_go_release", "build_go_step", "build_go_steps", + "build_go_log", "build_go_step_end", "build_go_toolchains", "build_go_status", @@ -60,6 +61,12 @@ { "name": "toolchains-go", "events": ["toolchain.**"] + }, + { + "name": "go-build-logs", + "events": ["log.**"], + "max-age-days": 14, + "max-mib": 2048 } ], "requires": [ diff --git a/modules/mesh-build-node/.gitignore b/modules/mesh-build-node/.gitignore new file mode 100644 index 00000000..1c8855bf --- /dev/null +++ b/modules/mesh-build-node/.gitignore @@ -0,0 +1 @@ +/mesh-build-node diff --git a/modules/mesh-build-node/cmd/mesh-build-node/asks.go b/modules/mesh-build-node/cmd/mesh-build-node/asks.go index bcb23ef4..f5b64ef3 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/asks.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/asks.go @@ -37,6 +37,9 @@ func (m *Module) askSteps(steps []StepTool) buildstep.AskSteps { }, Release: func(ws string) error { return m.spaces().Release(ws) }, Logf: m.Say, + // A failed test's whole output is said on the module's own log stream (buildstep.LogTool reads it back). + Narrate: m.Emit, + Module: "mesh-build-node", } } diff --git a/modules/mesh-build-node/cmd/mesh-build-node/emit_test.go b/modules/mesh-build-node/cmd/mesh-build-node/emit_test.go index 5d98c239..3313807d 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/emit_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/emit_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "git.novox.be/novox/mesh-sdk/go/buildstep" "os" "path/filepath" "slices" @@ -47,9 +48,14 @@ func TestTheManifestDeclaresTheToolchainsStream(t *testing.T) { Streams []struct { Name string `json:"name"` Events []string `json:"events"` + Age int `json:"max-age-days"` + MiB int `json:"max-mib"` } `json:"streams"` } - if err := json.Unmarshal(raw, &m); err != nil || len(m.Streams) != 1 || m.Streams[0].Name != "toolchains-node" || !slices.Equal(m.Streams[0].Events, []string{"toolchain.**"}) { + // And its log stream: a failed test's whole output, kept 14 days and at most 2 GiB, dropping its oldest (the operator, + // 2026-10-30). + if err := json.Unmarshal(raw, &m); err != nil || len(m.Streams) != 2 || m.Streams[0].Name != "toolchains-node" || !slices.Equal(m.Streams[0].Events, []string{"toolchain.**"}) || + m.Streams[1].Name != buildstep.LogStreamOf("node") || !slices.Equal(m.Streams[1].Events, []string{"log.**"}) || m.Streams[1].Age != 14 || m.Streams[1].MiB != 2048 { t.Errorf("streams = %+v (%v)", m.Streams, err) } } diff --git a/modules/mesh-build-node/cmd/mesh-build-node/main.go b/modules/mesh-build-node/cmd/mesh-build-node/main.go index 8f2e338d..a302e2ee 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/main.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/main.go @@ -16,6 +16,7 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/forgekey" ) @@ -47,6 +48,7 @@ func main() { Emit: func(t string, b any) error { return stdio.Emit(t, b) }, Steps: stdio.State(delivery.StepsState).Put, ReadSteps: readOf(stdio.State(delivery.StepsState)), + ReadLog: stdio.Stream(buildstep.LogStreamOf("node")).Read, Pauses: stdio.State(delivery.PausesState).Put, ReadPauses: readOf(stdio.State(delivery.PausesState)), ReadToolchainsState: readOf(stdio.State(delivery.ToolchainsState)), diff --git a/modules/mesh-build-node/cmd/mesh-build-node/module.go b/modules/mesh-build-node/cmd/mesh-build-node/module.go index 3bc5ec09..ebd15a32 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/module.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/module.go @@ -45,6 +45,9 @@ type Module struct { // Held is the puller whose running asks a step-end tool will not end (issue 1890); nil in a test. Held *buildstep.Puller ReadSteps, ReadToolchainsState StateRead + // ReadLog reads the module's own log stream (stdio.Stream(buildstep.LogStreamOf(language)).Read), where each step + // says a failed test's whole output (Emit says it); nil in a test. + ReadLog buildstep.StreamRead // Pauses is the Put of the pauses state, which keeps the node's pause key without an age; ReadPauses reads it back. Pauses buildstep.Put ReadPauses StateRead diff --git a/modules/mesh-build-node/cmd/mesh-build-node/module_test.go b/modules/mesh-build-node/cmd/mesh-build-node/module_test.go index e5fc60f3..8e76683a 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/module_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/module_test.go @@ -117,7 +117,7 @@ func TestANodeThatCannotBeRunRefusesEveryStepAndHealthIsUnhealthy(t *testing.T) {Name: "short_probe", Run: run}, }, (&Module{}).StepTools()...) for _, tool := range m.Tools(steps) { - if tool.Name == StatusTool || tool.Name == HealthTool || tool.Name == StepReadTool || tool.Name == StepsReadTool || tool.Name == ToolchainsReadTool || tool.Name == ReleaseTool || tool.Name == StepEndTool { + if tool.Name == StatusTool || tool.Name == HealthTool || tool.Name == StepReadTool || tool.Name == StepsReadTool || tool.Name == LogReadTool || tool.Name == ToolchainsReadTool || tool.Name == ReleaseTool || tool.Name == StepEndTool { continue } var reason string diff --git a/modules/mesh-build-node/cmd/mesh-build-node/read.go b/modules/mesh-build-node/cmd/mesh-build-node/read.go index 8c4463d8..c0dec9e8 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/read.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/read.go @@ -15,13 +15,16 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/delivery" ) // The read tools, named as the module is: build_node_ and the verb. const ( - StepReadTool = "build_node_step" - StepsReadTool = "build_node_steps" + StepReadTool = "build_node_step" + StepsReadTool = "build_node_steps" + // LogReadTool reads one step's log, a failed test's whole output, from the module's own log stream. + LogReadTool = "build_node_log" ToolchainsReadTool = "build_node_toolchains" ) @@ -225,7 +228,14 @@ func (m *Module) ReadToolchains() (map[string]delivery.Toolchain, error) { // ReadTools are the module's read tools. All are read-only. func (m *Module) ReadTools() []stdio.Tool { + readLog := m.ReadLog + if readLog == nil { + readLog = func(string, uint64, int) (*stdio.StreamPage, error) { + return nil, fmt.Errorf("this process reads no log stream: it was not launched by the runtime") + } + } return []stdio.Tool{ + buildstep.LogTool(LogReadTool, readLog), {Name: StepReadTool, Description: "What one step of this machine ended with, as stored in the module's steps state: its tool, state " + "(running, passed, failed, refused), when it started and ended, why it failed or was refused, and its result " + diff --git a/modules/mesh-build-node/go.mod b/modules/mesh-build-node/go.mod index f8b463c0..08096dc5 100644 --- a/modules/mesh-build-node/go.mod +++ b/modules/mesh-build-node/go.mod @@ -3,7 +3,7 @@ module meshbuildnode go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-build-node/go.sum b/modules/mesh-build-node/go.sum index eee5d03f..555f4090 100644 --- a/modules/mesh-build-node/go.sum +++ b/modules/mesh-build-node/go.sum @@ -1,5 +1,11 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771 h1:Tt16HJk8JZRce1i3JzzDSNdCWOrv7dw8FQ/bckNQ5Ig= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029233535-78c9ec881771/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095131-16bc0dcf3e9d h1:jFOz6wUfZvIayd4DctwlaFKKZHQNME6NLs2OS1rNv30= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095131-16bc0dcf3e9d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095816-530d8d3007dc h1:Wdjl2gomooHswJ5qpkwZPq/ureZgXVw6FMCsgzPJhPo= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030095816-530d8d3007dc/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde h1:eKXSllHCS31GgMazfvZ7BeiLoXIUibnUmzxWVkj+Vcw= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261030100935-c2ec8b6b2fde/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-node/module.json b/modules/mesh-build-node/module.json index 25f8611b..4c060607 100644 --- a/modules/mesh-build-node/module.json +++ b/modules/mesh-build-node/module.json @@ -46,6 +46,7 @@ "build_node_release", "build_node_step", "build_node_steps", + "build_node_log", "build_node_step_end", "build_node_toolchains", "build_node_status", @@ -57,6 +58,12 @@ "events": [ "toolchain.**" ] + }, + { + "name": "node-build-logs", + "events": ["log.**"], + "max-age-days": 14, + "max-mib": 2048 } ], "requires": [