1package modes
2
3import (
4 "context"
5 "errors"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/patriceckhart/zot/packages/agent/swarm"
11 "github.com/patriceckhart/zot/packages/core"
12)
13
14func TestTrackSwarmAgentReportsStartupFailure(t *testing.T) {
15 mgr := swarm.New(swarm.Config{
16 Root: t.TempDir(),
17 RepoRoot: t.TempDir(),
18 NewRunner: func(*swarm.Agent) swarm.Runner {
19 return swarm.RunnerFunc(func(context.Context, swarm.Sink) error {
20 return errors.New("listener startup failed")
21 })
22 },
23 })
24 a, err := mgr.Spawn(context.Background(), "report the date")
25 if err != nil {
26 t.Fatal(err)
27 }
28
29 iv := newQueuedAutoSwarmInteractive()
30 iv.TrackSwarmAgent(a, a.Task)
31
32 update := waitForQueuedPrompt(t, iv)
33 if !strings.Contains(update, "status: failed") {
34 t.Fatalf("update missing failed status: %q", update)
35 }
36 if !strings.Contains(update, "listener startup failed") {
37 t.Fatalf("update missing startup error: %q", update)
38 }
39}
40
41func TestCompleteSwarmWatchReportsTurnOutcomeOnce(t *testing.T) {
42 started := make(chan struct{})
43 mgr := swarm.New(swarm.Config{
44 Root: t.TempDir(),
45 RepoRoot: t.TempDir(),
46 NewRunner: func(*swarm.Agent) swarm.Runner {
47 return swarm.RunnerFunc(func(ctx context.Context, _ swarm.Sink) error {
48 close(started)
49 <-ctx.Done()
50 return ctx.Err()
51 })
52 },
53 })
54 a, err := mgr.Spawn(context.Background(), "report the date")
55 if err != nil {
56 t.Fatal(err)
57 }
58 <-started
59 t.Cleanup(func() {
60 _ = mgr.Stop(a.ID)
61 a.Wait()
62 })
63
64 iv := newQueuedAutoSwarmInteractive()
65 entry := &swarmWatchEntry{agent: a, task: a.Task}
66 iv.swarmWatch = []*swarmWatchEntry{entry}
67 iv.completeSwarmWatchEntry(entry, "completed", "")
68 iv.completeSwarmWatchEntry(entry, "failed", "late error")
69
70 update := waitForQueuedPrompt(t, iv)
71 if !strings.Contains(update, "status: completed") {
72 t.Fatalf("update uses daemon status instead of turn outcome: %q", update)
73 }
74 iv.mu.Lock()
75 queued := len(iv.queued)
76 iv.mu.Unlock()
77 if queued != 1 {
78 t.Fatalf("queued updates = %d; want exactly one", queued)
79 }
80}
81
82func newQueuedAutoSwarmInteractive() *Interactive {
83 return &Interactive{
84 agent: &core.Agent{},
85 busy: true,
86 compacting: true,
87 dirty: make(chan struct{}, 1),
88 }
89}
90
91func waitForQueuedPrompt(t *testing.T, iv *Interactive) string {
92 t.Helper()
93 deadline := time.NewTimer(time.Second)
94 defer deadline.Stop()
95 ticker := time.NewTicker(time.Millisecond)
96 defer ticker.Stop()
97 for {
98 iv.mu.Lock()
99 if len(iv.queued) > 0 {
100 prompt := iv.queued[0]
101 iv.mu.Unlock()
102 return prompt
103 }
104 iv.mu.Unlock()
105 select {
106 case <-deadline.C:
107 t.Fatal("timed out waiting for auto-swarm update")
108 case <-ticker.C:
109 }
110 }
111}