Show the backlog of build asks per language and kind: waiting, held by a node, and the oldest waiting ask's age (issue 1887 A8a)
This commit is contained in:
@@ -602,3 +602,97 @@ func TestProcessWholeWritesTheAnswerToAnAskNeedingNoProof(t *testing.T) { // 129
|
||||
}
|
||||
wait(func(s view.Snapshot) bool { return len(s.Asks) == 1 && s.Asks[0].Key == mkey }, "the answered ask never left the list")
|
||||
}
|
||||
|
||||
// TestProcessWholeShowsTheBacklogOfBuildAsks starts the program whole against a bus holding the go seat's work-queue stream
|
||||
// with its three shared workers, three check asks waiting and one held by a node: the socket's backlog row names three
|
||||
// waiting, one held, and the oldest's age (1887/A8a AC; compiled in the unit run, run by the merge check).
|
||||
func TestProcessWholeShowsTheBacklogOfBuildAsks(t *testing.T) { // 1887/A8a AC
|
||||
srv, err := server.NewServer(&server.Options{Host: "127.0.0.1", Port: -1, JetStream: true, StoreDir: t.TempDir(), NoLog: true, NoSigs: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go srv.Start()
|
||||
if !srv.ReadyForConnections(10 * time.Second) {
|
||||
t.Fatal("the bus did not come up")
|
||||
}
|
||||
t.Cleanup(srv.Shutdown)
|
||||
nc, err := nats.Connect(srv.ClientURL())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(nc.Close)
|
||||
js, err := jetstream.New(nc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
stream, _ := delivery.AskStream(delivery.AskGo)
|
||||
st, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: stream, Subjects: []string{"mesh.seat.node-build-go.accept.>"}, Retention: jetstream.WorkQueuePolicy})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var checkWorker jetstream.Consumer
|
||||
for _, kind := range delivery.AskKinds {
|
||||
name, _ := delivery.AskWorker(delivery.AskGo, kind)
|
||||
subject, _ := delivery.AskSubject(delivery.AskGo, kind)
|
||||
c, err := st.CreateOrUpdateConsumer(ctx, jetstream.ConsumerConfig{Durable: name, FilterSubject: subject, AckPolicy: jetstream.AckExplicitPolicy})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kind == delivery.AskCheck {
|
||||
checkWorker = c
|
||||
}
|
||||
}
|
||||
subject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck)
|
||||
oldest := time.Now().UTC().Add(-2 * time.Minute)
|
||||
for i, id := range []string{"m0000001aaaaaa", "m0000002bbbbbb", "m0000003cccccc", "m0000004dddddd"} {
|
||||
body, err := delivery.EncodeStepAsk(delivery.StepAsk{ID: id, Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-checker", At: oldest.Add(time.Duration(i) * time.Second),
|
||||
Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": "0123456789abcdef0123456789abcdef01234567"}, Levels: []string{delivery.LevelUnit}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := js.Publish(ctx, subject, body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// A node pulls one and holds it unacknowledged.
|
||||
held, err := checkWorker.Fetch(1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for range held.Messages() {
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "i")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { os.RemoveAll(dir) })
|
||||
broker := filepath.Join(dir, "broker")
|
||||
cred, _ := json.Marshal(credential{URL: srv.ClientURL(), Node: "n", Module: "mesh-delivery-view", User: "mesh-delivery-view", Password: "x"})
|
||||
if err := os.WriteFile(broker, cred, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
socket := filepath.Join(dir, "run", "cli.sock")
|
||||
env := map[string]string{"MESH_BROKER_FILE": broker, "MESH_DELIVERY_SOCKET": socket, "MESH_DELIVERY_WEB": "127.0.0.1:0"}
|
||||
runCtx, stop := context.WithCancel(ctx)
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- run(runCtx, func(k string) string { return env[k] }, io.Discard) }()
|
||||
t.Cleanup(func() { stop(); <-done })
|
||||
|
||||
var last view.Snapshot
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) {
|
||||
s, err := view.Ask(socket)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
last = s
|
||||
for _, b := range s.Backlog {
|
||||
if b.Language == "go" && b.Kind == "check" && b.Waiting == 3 && b.Held == 1 && b.OldestSecs >= 118 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatalf("the backlog never showed three waiting asks with the oldest's age; last: %+v", last.Backlog)
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
return err
|
||||
}
|
||||
model.SetWriter(kvWriter{ctx: ctx, js: js})
|
||||
model.SetBacklog(view.BacklogOver(js, 5*time.Second))
|
||||
for _, bucket := range []string{delivery.HaveBucket, view.AssignmentsBucket, view.BuildsBucket, delivery.PresenceBucket, delivery.StuckBucket, delivery.CandidatesBucket, delivery.ModulesBucket, delivery.AsksBucket, delivery.AnswersBucket, delivery.BuilderBuildsBucket, delivery.AssignerDifferencesBucket, delivery.AssignerAsksBucket, delivery.ViewAnswersBucket} {
|
||||
go follow(ctx, js, bucket, model, logger)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// The backlog of the build seats (novox/hq ADR 0525 §4, issue 1887 A8a): for each language and kind of ask, how many asks wait
|
||||
// on the seat's stream for a node to pull them, how many a node holds without having finished them, and how old the oldest
|
||||
// waiting ask is. It is read from the stream's shared pull worker (delivery.AskWorker) when the view is asked: NumPending
|
||||
// waits, NumAckPending is held by a node, and the oldest waiting ask is the stream's message at the worker's next sequence.
|
||||
|
||||
// BacklogRow is one language's kind of ask: Waiting asks not yet pulled, Held asks pulled by a node and not yet ended, and the
|
||||
// oldest waiting ask's time and age in seconds. Error says why the row could not be read, never dropping it silently.
|
||||
type BacklogRow struct {
|
||||
Language string `json:"language"`
|
||||
Kind string `json:"kind"`
|
||||
Waiting uint64 `json:"waiting"`
|
||||
Held uint64 `json:"held"`
|
||||
Oldest time.Time `json:"oldest,omitzero"`
|
||||
OldestSecs int64 `json:"oldest-secs,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Backlog reads the seats' workers. Info is the worker's counts and the stream sequence it delivers next; Message is the body
|
||||
// of the first message of the stream at or after seq on the subject.
|
||||
type Backlog struct {
|
||||
Info func(stream, worker string) (waiting, held, next uint64, err error)
|
||||
Message func(stream, subject string, seq uint64) ([]byte, error)
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
// Rows are the rows of every language and kind that has an ask waiting or held, or that could not be read.
|
||||
func (b *Backlog) Rows() []BacklogRow {
|
||||
now := time.Now
|
||||
if b.Now != nil {
|
||||
now = b.Now
|
||||
}
|
||||
out := []BacklogRow{}
|
||||
for _, lang := range delivery.AskLanguages {
|
||||
stream, _ := delivery.AskStream(lang)
|
||||
for _, kind := range delivery.AskKinds {
|
||||
worker, _ := delivery.AskWorker(lang, kind)
|
||||
subject, _ := delivery.AskSubject(lang, kind)
|
||||
row := BacklogRow{Language: lang, Kind: kind}
|
||||
waiting, held, next, err := b.Info(stream, worker)
|
||||
if err != nil {
|
||||
row.Error = err.Error()
|
||||
out = append(out, row)
|
||||
continue
|
||||
}
|
||||
row.Waiting, row.Held = waiting, held
|
||||
if waiting > 0 {
|
||||
body, err := b.Message(stream, subject, next)
|
||||
if err == nil {
|
||||
var a delivery.StepAsk
|
||||
a, err = delivery.DecodeStepAsk(body)
|
||||
if err == nil {
|
||||
row.Oldest, row.OldestSecs = a.At, int64(now().Sub(a.At).Seconds())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
row.Error = fmt.Sprintf("the oldest waiting ask cannot be read: %v", err)
|
||||
}
|
||||
}
|
||||
if row.Waiting > 0 || row.Held > 0 || row.Error != "" {
|
||||
out = append(out, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// BacklogOver reads the backlog from the bus: the worker's consumer info, and the stream's message by sequence.
|
||||
func BacklogOver(js jetstream.JetStream, timeout time.Duration) *Backlog {
|
||||
ctx := func() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), timeout)
|
||||
}
|
||||
return &Backlog{
|
||||
Info: func(stream, worker string) (uint64, uint64, uint64, error) {
|
||||
c, cancel := ctx()
|
||||
defer cancel()
|
||||
s, err := js.Stream(c, stream)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("the stream %s: %w", stream, err)
|
||||
}
|
||||
cons, err := s.Consumer(c, worker)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("the worker %s: %w", worker, err)
|
||||
}
|
||||
info := cons.CachedInfo()
|
||||
if i, err := cons.Info(c); err == nil {
|
||||
info = i
|
||||
}
|
||||
return uint64(info.NumPending), uint64(info.NumAckPending), info.Delivered.Stream + 1, nil
|
||||
},
|
||||
Message: func(stream, subject string, seq uint64) ([]byte, error) {
|
||||
c, cancel := ctx()
|
||||
defer cancel()
|
||||
s, err := js.Stream(c, stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err := s.GetMsg(c, seq, jetstream.WithGetMsgSubject(subject))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.Data, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
func stepAsk(t *testing.T, id, kind string, at time.Time) []byte {
|
||||
t.Helper()
|
||||
b, err := delivery.EncodeStepAsk(delivery.StepAsk{ID: id, Kind: kind, Language: delivery.AskGo, Asker: "mesh-delivery-checker", At: at,
|
||||
Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": "0123456789abcdef0123456789abcdef01234567"}, Levels: []string{delivery.LevelUnit}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// fakeSeats is the check worker of the go seat with three asks waiting and one held, the oldest at t0.
|
||||
func fakeSeats(t *testing.T, now time.Time) *Backlog {
|
||||
worker, _ := delivery.AskWorker(delivery.AskGo, delivery.AskCheck)
|
||||
subject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck)
|
||||
oldest := stepAsk(t, "m0000001aaaaaa", delivery.AskCheck, now.Add(-90*time.Second))
|
||||
return &Backlog{Now: func() time.Time { return now },
|
||||
Info: func(stream, w string) (uint64, uint64, uint64, error) {
|
||||
switch w {
|
||||
case worker:
|
||||
return 3, 1, 42, nil
|
||||
case "SEAT_NODE_BUILD_IMAGE_DELIVERY_worker":
|
||||
return 0, 0, 1, fmt.Errorf("the worker is not there")
|
||||
}
|
||||
return 0, 0, 1, nil
|
||||
},
|
||||
Message: func(stream, s string, seq uint64) ([]byte, error) {
|
||||
if s != subject || seq != 42 {
|
||||
return nil, fmt.Errorf("asked %s at %d", s, seq)
|
||||
}
|
||||
return oldest, nil
|
||||
}}
|
||||
}
|
||||
|
||||
func TestThreeWaitingAsksAreShownWithTheOldestsAge(t *testing.T) { // 1887/A8a AC
|
||||
now := time.Date(2026, 10, 23, 12, 0, 0, 0, time.UTC)
|
||||
m := New()
|
||||
m.SetBacklog(fakeSeats(t, now))
|
||||
rows := m.Snapshot().Backlog
|
||||
var check, image *BacklogRow
|
||||
for i := range rows {
|
||||
switch {
|
||||
case rows[i].Language == "go" && rows[i].Kind == "check":
|
||||
check = &rows[i]
|
||||
case rows[i].Language == "image" && rows[i].Kind == "delivery":
|
||||
image = &rows[i]
|
||||
default:
|
||||
t.Errorf("a quiet seat is listed: %+v", rows[i])
|
||||
}
|
||||
}
|
||||
if check == nil || check.Waiting != 3 || check.Held != 1 || check.OldestSecs != 90 || !check.Oldest.Equal(now.Add(-90*time.Second)) || check.Error != "" {
|
||||
t.Fatalf("check row = %+v", check)
|
||||
}
|
||||
if image == nil || image.Error == "" {
|
||||
t.Errorf("a worker that cannot be read is dropped: %+v", rows)
|
||||
}
|
||||
var out strings.Builder
|
||||
Table(&out, m.Snapshot())
|
||||
for _, w := range []string{"BACKLOG OF BUILD ASKS", "go", "check", "3", "1m30s", "unreadable"} {
|
||||
if !strings.Contains(out.String(), w) {
|
||||
t.Errorf("table lacks %q:\n%s", w, out.String())
|
||||
}
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
Web(m).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
if !strings.Contains(rec.Body.String(), "BACKLOG OF BUILD ASKS") || !strings.Contains(rec.Body.String(), "1m30s") {
|
||||
t.Errorf("page lacks the backlog:\n%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoBacklogReaderGivesNoRows(t *testing.T) {
|
||||
if got := New().Snapshot().Backlog; got == nil || len(got) != 0 {
|
||||
t.Errorf("backlog = %#v", got)
|
||||
}
|
||||
}
|
||||
@@ -236,6 +236,24 @@ func differenceText(s Snapshot) [][]string {
|
||||
return out
|
||||
}
|
||||
|
||||
// backlogText is one line per language and kind: its waiting asks, the asks held by a node, the oldest waiting ask's age.
|
||||
func backlogText(s Snapshot) [][]string {
|
||||
var out [][]string
|
||||
for _, b := range s.Backlog {
|
||||
age := ""
|
||||
if !b.Oldest.IsZero() {
|
||||
age = (time.Duration(b.OldestSecs) * time.Second).String() + " (since " + since(b.Oldest) + ")"
|
||||
}
|
||||
if b.Error != "" {
|
||||
age = "unreadable: " + b.Error
|
||||
}
|
||||
out = append(out, []string{b.Language, b.Kind, fmt.Sprint(b.Waiting), fmt.Sprint(b.Held), age})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var backlogHead = []string{"LANGUAGE", "KIND", "WAITING", "HELD", "OLDEST WAITING"}
|
||||
|
||||
var differenceHead = []string{"NODE", "MODULE", "WANT", "SENT", "RULE", "WORDS"}
|
||||
|
||||
var head = []string{"NODE", "MODULE", "ASSIGNED", "HAVE", "PHASE", "HEALTH", "SINCE", "DETAIL", "WAITS"}
|
||||
@@ -277,6 +295,14 @@ func Table(w io.Writer, s Snapshot) {
|
||||
row(cells)
|
||||
}
|
||||
}
|
||||
if b := backlogText(s); len(b) > 0 {
|
||||
fmt.Fprintln(tw)
|
||||
row([]string{"BACKLOG OF BUILD ASKS"})
|
||||
row(backlogHead)
|
||||
for _, cells := range b {
|
||||
row(cells)
|
||||
}
|
||||
}
|
||||
if d := differenceText(s); len(d) > 0 {
|
||||
fmt.Fprintln(tw)
|
||||
row([]string{"WANTS AGAINST SENDS"})
|
||||
@@ -329,6 +355,7 @@ var page = template.Must(template.New("p").Parse(`<!doctype html><html><head><me
|
||||
{{range .Rows}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>
|
||||
{{if .Groups}}<h2>CANDIDATES</h2><table><tr><th>GROUP</th><th>PULL REQUEST</th><th>HEAD</th><th>MODULES</th></tr>{{range .Groups}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>{{end}}
|
||||
{{if .Asks}}<h2>ASKS</h2><table><tr><th>ASK</th><th>HEADLINE</th><th>WORDS</th><th>PULL REQUESTS</th><th>ANSWER</th></tr>{{range .Asks}}<tr>{{range .Cells}}<td>{{.}}</td>{{end}}<td>{{if .Options}}<form method="post" action="/answer"><input type="hidden" name="key" value="{{.Key}}"><input name="words" placeholder="words (optional)">{{range .Options}} <button name="option" value="{{.}}">{{.}}</button>{{end}}</form>{{end}}</td></tr>{{end}}</table>{{end}}
|
||||
{{if .Backlog}}<h2>BACKLOG OF BUILD ASKS</h2><table><tr>{{range .BacklogHead}}<th>{{.}}</th>{{end}}</tr>{{range .Backlog}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>{{end}}
|
||||
{{if .Differences}}<h2>WANTS AGAINST SENDS</h2><table><tr>{{range .DifferenceHead}}<th>{{.}}</th>{{end}}</tr>{{range .Differences}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>{{end}}
|
||||
{{if .Fingerprints}}<h2>FINGERPRINTS</h2><table><tr><th>MODULE</th><th>STATE</th><th>INDEXER AGAINST BUILD</th></tr>{{range .Fingerprints}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>{{end}}
|
||||
{{if .Offline}}<h2>OFFLINE</h2><table>{{range .Offline}}<tr>{{range .}}<td>{{.}}</td>{{end}}</tr>{{end}}</table>{{end}}
|
||||
@@ -383,7 +410,7 @@ func Web(m *Model) http.Handler {
|
||||
for _, u := range snap.Unreadable {
|
||||
bad = append(bad, []string{u.Bucket, u.Key, u.Error})
|
||||
}
|
||||
page.Execute(w, map[string]any{"Head": head, "Rows": rows, "Groups": groupText(snap), "Asks": askPage(snap), "Fingerprints": fingerprintText(snap), "Differences": differenceText(snap), "DifferenceHead": differenceHead, "Offline": off, "Stuck": stuck, "Unreadable": bad})
|
||||
page.Execute(w, map[string]any{"Head": head, "Rows": rows, "Groups": groupText(snap), "Asks": askPage(snap), "Fingerprints": fingerprintText(snap), "Differences": differenceText(snap), "Backlog": backlogText(snap), "BacklogHead": backlogHead, "DifferenceHead": differenceHead, "Offline": off, "Stuck": stuck, "Unreadable": bad})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ type Snapshot struct {
|
||||
// Differences is every difference between what the assigner wants a node to run and what the controller sends, with the rule that
|
||||
// explains it or unexplained (novox/hq ADR 0528 clause 4, issue 1297 B8).
|
||||
Differences []DifferenceRow `json:"differences"`
|
||||
// Backlog is, for each language and kind of build ask with one waiting or held, the asks waiting for a node, the asks a node
|
||||
// holds and the oldest waiting ask's age (issue 1887 A8a).
|
||||
Backlog []BacklogRow `json:"backlog"`
|
||||
}
|
||||
|
||||
// DifferenceRow is a module on a node whose want and send differ: the fingerprint wanted (empty when the node has no want,
|
||||
@@ -186,6 +189,7 @@ type Model struct {
|
||||
aasks map[string]assignerAskRecord // the assigner's asks (hold, restore), by key
|
||||
chosen map[string]delivery.ViewAnswer // the answers the view wrote, by ask key
|
||||
writer Writer
|
||||
backlog *Backlog
|
||||
answers map[string]string // by key: the router id of the asking the teller's answer is for ("" when unreadable)
|
||||
newest map[string]string // module -> id
|
||||
haves map[string]haveEntry
|
||||
@@ -452,6 +456,25 @@ func errText(err error) string {
|
||||
// Snapshot derives one row per module and node with an open assignment, a row marked left behind for each have with
|
||||
// none, and a row per node's own key. Rows are ordered by node and module.
|
||||
func (m *Model) Snapshot() Snapshot {
|
||||
s := m.snapshot()
|
||||
m.mu.Lock()
|
||||
b := m.backlog
|
||||
m.mu.Unlock()
|
||||
s.Backlog = []BacklogRow{}
|
||||
if b != nil {
|
||||
s.Backlog = b.Rows() // read from the bus outside the model's lock
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SetBacklog gives the model the seats' workers to read the backlog of asks from.
|
||||
func (m *Model) SetBacklog(b *Backlog) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.backlog = b
|
||||
}
|
||||
|
||||
func (m *Model) snapshot() Snapshot {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
rows := []Row{}
|
||||
|
||||
@@ -48,6 +48,7 @@ type manifest struct {
|
||||
Version string `json:"version"`
|
||||
Slug string `json:"slug"`
|
||||
Reads []string `json:"reads"`
|
||||
Uses []string `json:"uses"`
|
||||
Tools []string `json:"tools"`
|
||||
OwnSecrets map[string]string `json:"own-secrets"`
|
||||
SecretsOwner string `json:"secrets-owner"`
|
||||
@@ -93,6 +94,10 @@ func TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
|
||||
t.Errorf("the manifest declares %q: the view writes only its answers", k)
|
||||
}
|
||||
}
|
||||
// The view reads the build seats' streams for the backlog of asks (issue 1887 A8a): the three seats it uses.
|
||||
if want := []string{"node-build-go", "node-build-image", "node-build-node"}; !reflect.DeepEqual(m.Uses, want) {
|
||||
t.Errorf("uses = %v, want %v", m.Uses, want)
|
||||
}
|
||||
// The one state the view writes: the operator's answers to the asks that need no proof (ADR 0528 clause 6, 1297/B9), which the teller reads.
|
||||
if want := []state{{Name: "answers", History: 5}}; !reflect.DeepEqual(m.State, want) {
|
||||
t.Errorf("state = %+v, want %+v", m.State, want)
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
"mesh-delivery-watchman.stuck",
|
||||
"node-engine.have"
|
||||
],
|
||||
"uses": [
|
||||
"node-build-go",
|
||||
"node-build-image",
|
||||
"node-build-node"
|
||||
],
|
||||
"own-secrets": {
|
||||
"broker": "${dir:state}/broker"
|
||||
},
|
||||
|
||||
@@ -171,7 +171,8 @@ func TestWatchmanWhole(t *testing.T) { // 1135/AC8
|
||||
askSubject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck)
|
||||
putAsk := func(id string) {
|
||||
t.Helper()
|
||||
body, err := json.Marshal(delivery.StepAsk{V: delivery.StepAskVersion, ID: id, Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-checker", At: time.Now().UTC()})
|
||||
body, err := delivery.EncodeStepAsk(delivery.StepAsk{ID: id, Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-checker", At: time.Now().UTC(),
|
||||
Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/x", "commit": "0123456789abcdef0123456789abcdef01234567"}, Levels: []string{delivery.LevelUnit}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user