返回 DeepSeek-Reasonix
goal_rebuild_test.go
根目录 / internal / control / goal_rebuild_test.go
1 package control
2
3 import (
4 "context"
5 "errors"
6 "testing"
7
8 "reasonix/internal/agent"
9 "reasonix/internal/event"
10 goaldomain "reasonix/internal/goal"
11 "reasonix/internal/session"
12 "reasonix/internal/tool"
13 )
14
15 func TestInheritLifecycleCarriesLiveGoalStateAcrossSameRuntimeRebuild(t *testing.T) {
16 service, err := session.NewService("desktop", session.NewFilesystemPersistence(t.TempDir()))
17 if err != nil {
18 t.Fatal(err)
19 }
20 t.Cleanup(func() { _ = service.CloseAll(context.Background()) })
21 runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "goal-rebuild"})
22 if err != nil {
23 t.Fatal(err)
24 }
25 newController := func() *Controller {
26 exec := agent.New(nil, tool.NewRegistry(), agent.NewSession("system"), agent.Options{}, event.Discard)
27 return newOwnedTestController(t, Options{Executor: exec, Sink: event.Discard, SessionService: service, SessionRuntime: runtime, ExclusiveSession: true, GoalTokenBudget: 1000})
28 }
29 old := newController()
30 t.Cleanup(old.ReleaseResources)
31 if err := old.SetGoalDurable("finish the rebuild"); err != nil {
32 t.Fatal(err)
33 }
34 replacement := newController()
35 t.Cleanup(replacement.ReleaseResources)
36 old.goalResourceMu.Lock()
37 old.goalTokensUsed = 340
38 old.goalRequestsUsed = 4
39 old.goalTokenLimit = 2000
40 old.goalBudgetExtensions = 1
41 old.goalResourceMu.Unlock()
42
43 if err := replacement.InheritLifecycleFrom(old); err != nil {
44 t.Fatal(err)
45 }
46 view, err := replacement.GetGoal(t.Context())
47 if err != nil {
48 t.Fatal(err)
49 }
50 if view == nil || view.Activation != goaldomain.ActivationArmed || view.Objective != "finish the rebuild" {
51 t.Fatalf("replacement goal = %+v", view)
52 }
53 runtimeView := replacement.GoalRuntime()
54 if runtimeView.TokensUsed != 340 || runtimeView.RequestsUsed != 4 || runtimeView.TokensLimit != 2000 || runtimeView.BudgetExtensions != 1 {
55 t.Fatalf("replacement resource counters = %+v", runtimeView)
56 }
57 }
58
59 func TestInheritLifecycleRejectsActiveGoalDriverReservation(t *testing.T) {
60 service, err := session.NewService("desktop", session.NewFilesystemPersistence(t.TempDir()))
61 if err != nil {
62 t.Fatal(err)
63 }
64 t.Cleanup(func() { _ = service.CloseAll(context.Background()) })
65 runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "goal-rebuild-busy"})
66 if err != nil {
67 t.Fatal(err)
68 }
69 newController := func() *Controller {
70 exec := agent.New(nil, tool.NewRegistry(), agent.NewSession("system"), agent.Options{}, event.Discard)
71 return newOwnedTestController(t, Options{Executor: exec, Sink: event.Discard, SessionService: service, SessionRuntime: runtime, ExclusiveSession: true})
72 }
73 old := newController()
74 replacement := newController()
75 t.Cleanup(old.ReleaseResources)
76 t.Cleanup(replacement.ReleaseResources)
77 old.goalDriverMu.Lock()
78 old.goalDriverPending = true
79 old.goalDriverMu.Unlock()
80 defer func() {
81 old.goalDriverMu.Lock()
82 old.goalDriverPending = false
83 old.goalDriverMu.Unlock()
84 }()
85
86 if err := replacement.InheritLifecycleFrom(old); !errors.Is(err, session.ErrRuntimeBusy) {
87 t.Fatalf("inherit error = %v, want runtime busy", err)
88 }
89 }
90
91 func TestEditGoalDurablePreservesIdentityAndAdmittedRounds(t *testing.T) {
92 service, err := session.NewService("desktop", session.NewFilesystemPersistence(t.TempDir()))
93 if err != nil {
94 t.Fatal(err)
95 }
96 t.Cleanup(func() { _ = service.CloseAll(context.Background()) })
97 runtime, err := service.Create(t.Context(), session.CreateOptions{SessionID: "goal-edit"})
98 if err != nil {
99 t.Fatal(err)
100 }
101 exec := agent.New(nil, tool.NewRegistry(), agent.NewSession("system"), agent.Options{}, event.Discard)
102 c := newOwnedTestController(t, Options{Executor: exec, Sink: event.Discard, SessionService: service, SessionRuntime: runtime, ExclusiveSession: true})
103 t.Cleanup(c.ReleaseResources)
104 if err := c.SetGoalDurable("original objective"); err != nil {
105 t.Fatal(err)
106 }
107 before, err := c.GetGoal(t.Context())
108 if err != nil || before == nil {
109 t.Fatalf("GetGoal before edit = %+v, %v", before, err)
110 }
111 if _, err := c.applyHostGoalMutation(t.Context(), "test-admit", func(machine *goaldomain.Machine) (*goaldomain.View, error) {
112 admitted, admitErr := machine.AdmitRound(before.Ref())
113 return &admitted, admitErr
114 }); err != nil {
115 t.Fatal(err)
116 }
117 limit := uint64(8)
118 if err := c.EditGoalDurable("revised objective", &limit); err != nil {
119 t.Fatal(err)
120 }
121 after, err := c.GetGoal(t.Context())
122 if err != nil || after == nil {
123 t.Fatalf("GetGoal after edit = %+v, %v", after, err)
124 }
125 if after.ID != before.ID || after.Revision != before.Revision+1 || after.RoundsStarted != 1 || after.Objective != "revised objective" || after.MaxGoalRounds == nil || *after.MaxGoalRounds != limit {
126 t.Fatalf("edited goal = %+v, before = %+v", after, before)
127 }
128 if err := c.EditGoalDurable("invalid lower limit", new(uint64)); err == nil {
129 t.Fatal("zero round limit unexpectedly accepted")
130 }
131 unchanged, _ := c.GetGoal(t.Context())
132 if unchanged.Objective != after.Objective || unchanged.Revision != after.Revision || unchanged.RoundsStarted != after.RoundsStarted {
133 t.Fatalf("failed edit changed goal: got %+v want %+v", unchanged, after)
134 }
135 }
136
136 lines GO