返回 DeepSeek-Reasonix
manual_creation_process_test.go
根目录 / desktop / manual_creation_process_test.go
1 package main
2
3 import (
4 "bufio"
5 "context"
6 "fmt"
7 "os"
8 "os/exec"
9 "sync/atomic"
10 "testing"
11 "time"
12
13 "reasonix/desktop/internal/sessionui"
14 )
15
16 func TestManualCreationManagerProcess(t *testing.T) {
17 path := os.Getenv("REASONIX_TEST_CREATION_STORE")
18 if path == "" {
19 return
20 }
21 a := NewApp()
22 a.ctx = t.Context()
23 installNoopRuntimeEvents(a)
24 a.sessionUI = sessionui.New(path)
25 m := a.creationManager()
26 m.execute = func(context.Context, ManualSessionCreationView, func(string)) error {
27 fmt.Println("executing")
28 _, _ = bufio.NewReader(os.Stdin).ReadString('\n')
29 return nil
30 }
31 m.Ensure("two-process-creation", "recovery", "")
32 a.manualCreationTasks.Wait()
33 if err := a.stopManualCreations(); err != nil {
34 t.Fatal(err)
35 }
36 _ = a.sessionUI.Close()
37 }
38
39 func TestManualCreationTwoManagersRespectCommittedResult(t *testing.T) {
40 a := newManualSessionTestApp(t)
41 v, _ := seedManualCreation(t, a, "two-process-creation", "starting")
42 cmd := exec.Command(os.Args[0], "-test.run=^TestManualCreationManagerProcess$")
43 cmd.Env = append(os.Environ(), "REASONIX_TEST_CREATION_STORE="+a.sessionUIStore().Path())
44 output, err := cmd.StdoutPipe()
45 if err != nil {
46 t.Fatal(err)
47 }
48 input, err := cmd.StdinPipe()
49 if err != nil {
50 t.Fatal(err)
51 }
52 cmd.Stderr = os.Stderr
53 if err = cmd.Start(); err != nil {
54 t.Fatal(err)
55 }
56 t.Cleanup(func() {
57 _ = input.Close()
58 if cmd.ProcessState == nil {
59 _ = cmd.Process.Kill()
60 _ = cmd.Wait()
61 }
62 })
63 line, err := bufio.NewReader(output).ReadString('\n')
64 if err != nil || line != "executing\n" {
65 t.Fatalf("owner handshake: %q %v", line, err)
66 }
67 m := a.creationManager()
68 var executions atomic.Int32
69 m.execute = func(context.Context, ManualSessionCreationView, func(string)) error { executions.Add(1); return nil }
70 m.Ensure(v.OperationID, "recovery", "")
71 waitFor(t, "second manager waits", func() bool { p := m.Snapshot(v.OperationID); return p != nil && p.Status == "waiting_lock" })
72 _ = input.Close()
73 if err = cmd.Wait(); err != nil {
74 t.Fatal(err)
75 }
76 a.manualCreationTasks.Wait()
77 got, err := a.GetManualSessionCreation(v.OperationID)
78 if err != nil || got.Phase != "ready" || executions.Load() != 0 {
79 t.Fatalf("competing execution: %+v count=%d err=%v", got, executions.Load(), err)
80 }
81 }
82
83 func TestManualCreationStaleRetryCannotRestartNewerFailure(t *testing.T) {
84 a := newManualSessionTestApp(t)
85 v, record := seedManualCreation(t, a, "stale-retry-creation", "failed")
86 if _, err := a.sessionUIStore().Save(t.Context(), "creation", record.Key, record.Revision, record.Payload); err != nil {
87 t.Fatal(err)
88 }
89 m := a.creationManager()
90 var executions atomic.Int32
91 m.execute = func(context.Context, ManualSessionCreationView, func(string)) error { executions.Add(1); return nil }
92 m.Ensure(v.OperationID, "retry", record.Revision)
93 a.manualCreationTasks.Wait()
94 if executions.Load() != 0 {
95 t.Fatal("stale request restarted a newer failure")
96 }
97 if _, err := a.RetryManualSessionCreation(v.OperationID); err != nil {
98 t.Fatal(err)
99 }
100 a.manualCreationTasks.Wait()
101 if executions.Load() != 1 {
102 t.Fatal("fresh explicit retry did not execute")
103 }
104 }
105
106 func TestManualCreationSlowProgressIsObservationOnly(t *testing.T) {
107 a := &App{}
108 m := &manualCreationManager{a: a, tasks: make(map[string]*manualCreationTask), now: func() time.Time { return time.Unix(100, 0) }}
109 task := &manualCreationTask{id: "slow-operation", progress: ManualCreationProgress{Status: "running", Stage: "building_runtime", StageStartedAt: 1}}
110 m.tasks[task.id] = task
111 p := m.Snapshot(task.id)
112 if !p.Slow || p.Status != "running" || len(m.tasks) != 1 {
113 t.Fatalf("slow status triggered a transition: %+v", p)
114 }
115 }
116
117 func TestManualCreationLateStageCannotReplaceNewerGeneration(t *testing.T) {
118 m := &manualCreationManager{tasks: make(map[string]*manualCreationTask), now: time.Now}
119 task := &manualCreationTask{id: "late-stage", running: true}
120 m.tasks[task.id] = task
121 m.setStageForGeneration(task, "running", "building_runtime", 2)
122 m.setStageForGeneration(task, "running", "publishing_controller", 1)
123 if got := m.Snapshot(task.id); got.Stage != "building_runtime" {
124 t.Fatalf("late stage replaced generation 2: %+v", got)
125 }
126 }
127
127 lines GO