Files
mesh-catalog/modules/mesh-build-go/buildstep/narrate_test.go
T
jschoubben 37a1f2be53 CAT-NR1: every build narrates its asks of a seat in one place
asked, waiting on the seat every five minutes, stored or refused, as the ask's
phase on its step key, so a publish or a copy that stands still is readable on
the bus. PublishOne and PublishFiles narrate for every build module; the image
copy and the late publish use the same narration.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 16:05:27 +01:00

108 lines
3.7 KiB
Go

package buildstep
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"testing"
"time"
reg "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// CAT-NR1: a build's publish on a seat says asked, waiting on the seat (on every tick of NarrateEvery while the holder
// has not answered), and stored or refused, each as the ask's phase written to its running step key.
func TestABuildNarratesItsPublishOnASeat(t *testing.T) {
digest := "sha256:" + strings.Repeat("c", 64)
for name, c := range map[string]struct {
answer error
ticks int
want []string
}{
"stored": {want: []string{
"asked go-package-registry: publish of mod 1.2.3 at " + digest,
"stored on go-package-registry: publish of mod 1.2.3 at " + digest + " (0 of its 1 parts sent, the rest held)"}},
"refused": {answer: errors.New("the holder is down"), want: []string{
"asked go-package-registry: publish of mod 1.2.3 at " + digest,
"refused by go-package-registry: publish of mod 1.2.3 at " + digest + ": go-package-registry's publish of mod: the holder is down"}},
"waiting twice, then stored": {ticks: 2, want: []string{
"asked go-package-registry: publish of mod 1.2.3 at " + digest,
"waiting on go-package-registry: publish of mod 1.2.3 at " + digest + ", 5m0s since asked",
"waiting on go-package-registry: publish of mod 1.2.3 at " + digest + ", 10m0s since asked",
"stored on go-package-registry: publish of mod 1.2.3 at " + digest + " (0 of its 1 parts sent, the rest held)"}},
} {
t.Run(name, func(t *testing.T) {
clock := time.Date(2026, 11, 4, 16, 0, 0, 0, time.UTC)
var mu sync.Mutex
oldNow, oldTicks := now, narrateTicks
now = func() time.Time { mu.Lock(); defer mu.Unlock(); return clock }
tick := make(chan time.Time)
stopped := make(chan struct{})
narrateTicks = func(every time.Duration) (<-chan time.Time, func()) {
if every != 5*time.Minute {
t.Errorf("the waiting lines come every %s, want 5m", every)
}
return tick, func() { close(stopped) }
}
t.Cleanup(func() { now, narrateTicks = oldNow, oldTicks })
var said []string
wrote := make(chan struct{}, 16)
step := delivery.Step{ID: "a1", Tool: "build", Node: "ace", State: delivery.StepRunning, Started: clock}
ph := &phases{key: "ace.a1", step: step, put: func(key string, value any) (uint64, error) {
var s delivery.Step
_ = json.Unmarshal(value.(json.RawMessage), &s)
phasesMu.Lock()
said = append(said, s.Phase)
phasesMu.Unlock()
wrote <- struct{}{}
return 1, nil
}}
release := make(chan struct{})
call := func(key string, body any) (json.RawMessage, error) {
<-release
if c.answer != nil {
return nil, c.answer
}
return json.Marshal(reg.PublishAnswer{Held: true, Transfer: "t"})
}
ctx := context.WithValue(context.Background(), phaseKey{}, ph)
done := make(chan error, 1)
go func() { done <- PublishOne(ctx, call, GoSeat, "mod", "package", "1.2.3", digest, "t", []byte("x")) }()
line := func() {
select {
case <-wrote:
case <-time.After(5 * time.Second):
t.Fatal("the publish said no line")
}
}
line() // asked, at the clock's start
for i := 0; i < c.ticks; i++ {
mu.Lock()
clock = clock.Add(5 * time.Minute)
mu.Unlock()
tick <- clock
line() // its waiting line
}
close(release)
err := <-done
if (err != nil) != (c.answer != nil) {
t.Fatalf("PublishOne = %v", err)
}
<-stopped
phasesMu.Lock()
defer phasesMu.Unlock()
// Each line is the step's phase, written to its key, in order.
if fmt.Sprint(said) != fmt.Sprint(c.want) {
t.Errorf("phases =\n%s\nwant\n%s", strings.Join(said, "\n"), strings.Join(c.want, "\n"))
}
})
}
}
var phasesMu sync.Mutex