package buildstep import ( "encoding/json" stdio "git.novox.be/novox/mesh-sdk/go" "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, never a pulled ask's // (the stream redelivers it) 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", "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) != 1 || ended[0] != "old1" { 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) } for key, state := range map[string]string{"ace.mjk3l9x0abcdef": 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) } }