返回 DeepSeek-Reasonix
manual_creation_build_test.go
根目录 / desktop / manual_creation_build_test.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "path/filepath"
7 "sync"
8 "sync/atomic"
9 "testing"
10 "time"
11
12 "reasonix/internal/identitylock"
13 )
14
15 func TestManualCreationWaitsForSupersededBuildActualExit(t *testing.T) {
16 a := newManualSessionTestApp(t)
17 entered := make(chan struct{}, 2)
18 first, second := make(chan struct{}), make(chan struct{})
19 var firstOnce, secondOnce sync.Once
20 defer firstOnce.Do(func() { close(first) })
21 defer secondOnce.Do(func() { close(second) })
22 var calls atomic.Int32
23 a.tabBuildStartHook = func(string) {
24 n := calls.Add(1)
25 entered <- struct{}{}
26 if n == 1 {
27 <-first
28 } else {
29 <-second
30 }
31 }
32 v, err := a.BeginManualSessionCreation(ManualSessionCreationRequest{OperationID: "superseded-creation-owner", Scope: "global"})
33 if err != nil {
34 t.Fatal(err)
35 }
36 <-entered
37 a.mu.RLock()
38 var tab *WorkspaceTab
39 for _, candidate := range a.tabs {
40 if candidate.SessionID == v.Ref.SessionID {
41 tab = candidate
42 }
43 }
44 old := tab.buildExecution
45 oldNotification := tab.buildDone
46 a.mu.RUnlock()
47 a.startTabControllerBuild(tab)
48 <-entered
49 select {
50 case <-oldNotification:
51 default:
52 t.Fatal("test did not supersede the notification")
53 }
54 m := a.creationManager()
55 a.shuttingDown.Store(true)
56 ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
57 defer cancel()
58 if err = m.CancelAndWait(ctx); !errors.Is(err, context.DeadlineExceeded) {
59 t.Fatalf("unjoined build stopped: %v", err)
60 }
61 firstOnce.Do(func() { close(first) })
62 <-old.done
63 path := filepath.Join(filepath.Dir(a.sessionUIStore().Path()), "manual-creation-locks", v.Ref.SessionID+".lock")
64 unlock, err := identitylock.TryAcquire(path)
65 if err == nil {
66 unlock()
67 t.Fatal("released before the second build exited")
68 }
69 if !errors.Is(err, identitylock.ErrHeld) {
70 t.Fatal(err)
71 }
72 secondOnce.Do(func() { close(second) })
73 if err = m.CancelAndWait(t.Context()); err != nil {
74 t.Fatal(err)
75 }
76 got, _ := a.GetManualSessionCreation(v.OperationID)
77 if got.Phase != "starting" {
78 t.Fatalf("cancelled attempt persisted a terminal result: %+v", got)
79 }
80 }
81
82 func TestManualCreationResumesPreparedStorage(t *testing.T) {
83 a := newManualSessionTestApp(t)
84 v, _ := seedManualCreation(t, a, "prepared-creation-recovery", "starting")
85 tab, err := a.reserveManualSessionTab(t.Context(), v, func(string) {})
86 if err != nil {
87 t.Fatal(err)
88 }
89 m := a.creationManager()
90 m.Ensure(v.OperationID, "recovery", "")
91 a.manualCreationTasks.Wait()
92 got, err := a.GetManualSessionCreation(v.OperationID)
93 if err != nil || got.Phase != "ready" || got.Ref != v.Ref {
94 t.Fatalf("resume: %+v %v", got, err)
95 }
96 a.mu.RLock()
97 defer a.mu.RUnlock()
98 if len(a.tabs) != 1 || a.tabs[tab.ID] != tab {
99 t.Fatal("prepared runtime identity was replaced")
100 }
101 }
102
102 lines GO