mesh/delivery merged: source.main moved to novox/mesh-sdk@2aae6f5580f2
mesh/delivery-group group fix/1890-left-running-steps merged: every member is merged
mesh/check pass 6b8497314297 on 2aae6f5580f2 | carried from b4cd95e7c4a4, mesh-catalog a4f5df797b68 | unit 3132 run, 3022 kept, 7 skipped | integratio…
132 lines
5.1 KiB
Go
132 lines
5.1 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"encoding/json"
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// scanOf is the steps a keys double holds, last value per key, its revision the count of its puts; a write through
|
|
// Update lands only at the revision read, as the state's does. race, when set, runs once before the next Update's check.
|
|
func scanOf(k *keys, race func(key string)) Scan {
|
|
return Scan{
|
|
Keys: func() ([]string, error) {
|
|
k.mu.Lock()
|
|
defer k.mu.Unlock()
|
|
var out []string
|
|
for key := range k.puts {
|
|
out = append(out, key)
|
|
}
|
|
return out, nil
|
|
},
|
|
Get: func(key string) (json.RawMessage, uint64, error) {
|
|
k.mu.Lock()
|
|
defer k.mu.Unlock()
|
|
s, ok := k.puts[key]
|
|
if !ok {
|
|
return nil, 0, nil
|
|
}
|
|
raw, err := delivery.EncodeStep(s[len(s)-1])
|
|
return raw, uint64(len(s)), err
|
|
},
|
|
Update: func(key string, value any, revision uint64) (uint64, error) {
|
|
if race != nil {
|
|
race(key)
|
|
}
|
|
k.mu.Lock()
|
|
n := uint64(len(k.puts[key]))
|
|
k.mu.Unlock()
|
|
if n != revision {
|
|
return 0, stdio.ErrChanged
|
|
}
|
|
return k.put(key, value)
|
|
},
|
|
}
|
|
}
|
|
|
|
// 1890: EndStale ends only this node's hand-started steps left running before the process began and the asks past the
|
|
// step bound (a younger ask is the stream's to redeliver), and never a key written meanwhile; EndOne ends one by hand, never one this process started.
|
|
func TestEndStaleAndEndOne(t *testing.T) {
|
|
k := newKeys()
|
|
boot := time.Now()
|
|
oldBoot := Boot
|
|
Boot = boot
|
|
defer func() { Boot = oldBoot }()
|
|
put := func(node, id, tool, state string, started time.Time) {
|
|
key, _ := delivery.StepKey(node, id)
|
|
_, _ = k.put(key, json.RawMessage(mustStep(t, delivery.Step{ID: id, Tool: tool, Node: node, State: state, Started: started})))
|
|
}
|
|
put("ace", "old1", "build_go_fetch", delivery.StepRunning, boot.Add(-time.Hour))
|
|
put("ace", "mjk3l9x0abcdef", "check ask", delivery.StepRunning, boot.Add(-time.Hour))
|
|
put("ace", "mjk3l9x0abcdee", "check ask", delivery.StepRunning, time.Now().Add(-5*time.Minute))
|
|
put("ace", "raced1", "build_go_test", delivery.StepRunning, boot.Add(-time.Hour))
|
|
put("ace", "new1", "build_go_fetch", delivery.StepRunning, boot.Add(time.Second))
|
|
put("ace", "done1", "build_go_fetch", delivery.StepPassed, boot.Add(-time.Hour))
|
|
put("g14", "other1", "build_go_fetch", delivery.StepRunning, boot.Add(-time.Hour))
|
|
// raced1 is ended by its own run between the read and the write.
|
|
raced := false
|
|
scan := scanOf(k, func(key string) {
|
|
if key == "ace.raced1" && !raced {
|
|
raced = true
|
|
put("ace", "raced1", "build_go_test", delivery.StepPassed, boot.Add(-time.Hour))
|
|
}
|
|
})
|
|
ended, err := EndStale(scan, "ace", boot, nil)
|
|
if err != nil || len(ended) != 2 || ended[0] == ended[1] {
|
|
t.Fatalf("ended %v, %v", ended, err)
|
|
}
|
|
if s := k.last("ace.old1"); s.State != delivery.StepFailed || s.Reason != RestartReason || s.Ended.IsZero() {
|
|
t.Errorf("old1 = %+v", s)
|
|
}
|
|
if s := k.last("ace.mjk3l9x0abcdef"); s.State != delivery.StepFailed || !strings.HasPrefix(s.Reason, "past the step bound: its asker stopped waiting at ") {
|
|
t.Errorf("the old ask = %+v", s)
|
|
}
|
|
for key, state := range map[string]string{"ace.mjk3l9x0abcdee": delivery.StepRunning, "ace.raced1": delivery.StepPassed, "ace.new1": delivery.StepRunning, "ace.done1": delivery.StepPassed, "g14.other1": delivery.StepRunning} {
|
|
if s := k.last(key); s.State != state {
|
|
t.Errorf("%s = %s, want %s", key, s.State, state)
|
|
}
|
|
}
|
|
// EndOne: refuses without why, a held step, one started by this process (a pulled ask or a step tool's), a step not
|
|
// running and another node's; ends a step a previous process left.
|
|
for _, c := range []struct{ id, why string }{{"old1", ""}, {"new1", "stuck"}, {"done1", "x"}, {"g14.other1", "x"}} {
|
|
if _, err := EndOne(scan, "ace", c.id, c.why, nil); err == nil {
|
|
t.Errorf("ended %s with why %q", c.id, c.why)
|
|
}
|
|
}
|
|
put("ace", "old2", "build_go_fetch", delivery.StepRunning, boot.Add(-time.Hour))
|
|
if _, err := EndOne(scan, "ace", "old2", "x", func(string) bool { return true }); err == nil {
|
|
t.Error("ended a held step")
|
|
}
|
|
if _, err := EndOne(scan, "ace", "ace.old2", "stuck for good", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := k.last("ace.old2"); s.State != delivery.StepFailed || s.Reason != "stuck for good" {
|
|
t.Errorf("old2 = %+v", s)
|
|
}
|
|
}
|
|
|
|
func mustStep(t *testing.T, s delivery.Step) []byte {
|
|
t.Helper()
|
|
raw, err := delivery.EncodeStep(s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// 1890: AskWhat names the repository, head and levels of a check ask and the module of a build ask.
|
|
func TestAskWhat(t *testing.T) {
|
|
check := delivery.StepAsk{Kind: "check", Check: &delivery.CheckJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": "0123456789abcdef0123"}, Levels: []string{"unit", "integration"}}}
|
|
if got, want := AskWhat(check), "check ask, novox/mesh-sdk@0123456789ab, levels unit,integration"; got != want {
|
|
t.Errorf("check = %q", got)
|
|
}
|
|
build := delivery.StepAsk{Kind: "delivery", Build: &delivery.BuildJob{Module: "mesh-x", FP: "abcdef0123456789"}}
|
|
if got, want := AskWhat(build), "delivery ask, module mesh-x, fp abcdef012345"; got != want {
|
|
t.Errorf("build = %q", got)
|
|
}
|
|
}
|