Say nothing more on a step's log after one publish fails, so a bus that does not take its lines costs a step at most one timeout and never fails it

This commit is contained in:
2026-10-30 11:09:35 +01:00
parent 530d8d3007
commit c2ec8b6b2f
2 changed files with 20 additions and 4 deletions
+12
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"testing"
"time"
@@ -90,6 +91,17 @@ func TestStepEndsPassedFailedOrRefusedOnItsKey(t *testing.T) {
{"no whole output", func(context.Context) (any, error) {
return map[string]any{"failed": []map[string]string{{"package": "p", "full_output": "all of it"}}}, nil
}, delivery.StepPassed, "", `{"failed":[{"package":"p"}]}`},
// A log the bus does not take is tried once in a step, then left: the step passes, its failures name no step.
{"a dead log", func(context.Context) (any, error) {
tries := 0
l := &stepLog{say: func(string, any) error { tries++; return errors.New("timeout") }, id: "s1", run: "r1"}
raw := l.narrate(json.RawMessage(`{"failed":[{"package":"p","full_output":"a"},{"package":"q","full_output":"b"}]}`), nil)
raw = l.narrate(raw, nil)
if tries != 1 {
return nil, fmt.Errorf("%d publishes, want one", tries)
}
return raw, nil
}, delivery.StepPassed, "", `{"failed":[{"package":"p"},{"package":"q"}]}`},
{"error", func(context.Context) (any, error) { return nil, errors.New("exit 2") }, delivery.StepFailed, "exit 2", ""},
{"refusal", func(context.Context) (any, error) {
return nil, &Refusal{Text: "go 1.27.1 installed, the recipe builds with go 1.27.0", Want: "1.27.0", Have: "1.27.1"}
+8 -4
View File
@@ -31,6 +31,9 @@ type stepLog struct {
module, node string
id, run string
seq int
// dead says a publish failed: the step says nothing more on its log, so a bus that does not take its lines costs the
// step at most one publish's timeout, and never fails it (the 2026-10-30 review).
dead bool
}
// stepLog is the log of the step whose ask runs under ctx, or nil when no ask runs (a tool called by hand) or the module
@@ -49,8 +52,8 @@ func (s AskSteps) stepLog(ctx context.Context) *stepLog {
}
// narrate says every failure's Full of a step answer's "failed" on the step's log and empties it, setting the failure's
// Step, Module, Machine and Run; a failure whose lines could not all be said keeps no address, and that is said through
// logf. With l nil, Full is only emptied: the step's result never holds it.
// Step, Module, Machine and Run; a failure whose lines could not all be said keeps no address, that is said once through
// logf, and the step says nothing more on its log (dead). With l nil, Full is only emptied: the step's result never holds it.
func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.RawMessage {
if !strings.Contains(string(raw), `"full_output"`) {
return raw
@@ -67,7 +70,7 @@ func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.R
var full string
_ = json.Unmarshal(f["full_output"], &full)
delete(failed[i], "full_output")
if full == "" || l == nil {
if full == "" || l == nil || l.dead {
continue
}
var name struct {
@@ -77,8 +80,9 @@ func (l *stepLog) narrate(raw json.RawMessage, logf func(string, ...any)) json.R
b, _ := json.Marshal(f)
_ = json.Unmarshal(b, &name)
if err := l.say1(name.Package, name.Test, strings.Split(full, "\n")); err != nil {
l.dead = true
if logf != nil {
logf("the whole output of %s %s in the step %s is not on its log: %v", name.Package, name.Test, l.id, err)
logf("the step %s says no more on its log, from the whole output of %s %s on: %v", l.id, name.Package, name.Test, err)
}
continue
}