返回 DeepSeek-Reasonix
session_read_performance_test.go
根目录 / desktop / session_read_performance_test.go
1 package main
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "reasonix/desktop/internal/workspacestate"
12 "reasonix/internal/identitylock"
13 "reasonix/internal/session"
14 "reasonix/internal/sessioncatalog"
15 )
16
17 func TestProjectTreeSnapshotIsReadOnlyAcrossManyUnmigratedWorkspaces(t *testing.T) {
18 isolateDesktopUserDirs(t)
19 base := t.TempDir()
20 app := NewApp()
21 t.Cleanup(app.closeSessionServices)
22 app.ctx = t.Context()
23 app.desktopSessions.root = filepath.Join(base, "by-id")
24 app.desktopSessions.workspaceState = workspacestate.NewStore(filepath.Join(base, "workspace-state.json"))
25
26 pinned := true
27 for i := range 8 {
28 root := filepath.Join(base, fmt.Sprintf("workspace-%02d", i))
29 if err := os.MkdirAll(root, 0o700); err != nil {
30 t.Fatal(err)
31 }
32 if err := addProject(root, fmt.Sprintf("Workspace %02d", i)); err != nil {
33 t.Fatal(err)
34 }
35 workspaceID, err := app.ensureDesktopWorkspace(t.Context(), "project", root)
36 if err != nil {
37 t.Fatal(err)
38 }
39 sessionID := fmt.Sprintf("session-%02d", i)
40 // Ordinary rows represent readable sessions, not registry-only ghosts.
41 if _, err := app.desktopSessionService("").Create(t.Context(), session.CreateOptions{SessionID: sessionID, CWD: root, Origin: session.SessionOriginNew}); err != nil {
42 t.Fatal(err)
43 }
44 if err := app.workspaceRegistry().AttachSession(t.Context(), "", workspaceID, sessionID, ""); err != nil {
45 t.Fatal(err)
46 }
47 if err := app.workspaceRegistry().EnsureSessionTopic(t.Context(), sessionID, "topic-"+sessionID, sessionID); err != nil {
48 t.Fatal(err)
49 }
50 if err := app.workspaceRegistry().UpdatePresentation(t.Context(), []string{sessionID}, nil, &pinned); err != nil {
51 t.Fatal(err)
52 }
53 }
54 state, err := app.workspaceRegistry().Load(t.Context())
55 if err != nil {
56 t.Fatal(err)
57 }
58 for id, workspace := range state.Workspaces {
59 if workspace.Organization != nil {
60 t.Fatalf("fixture workspace %q was already migrated", id)
61 }
62 }
63 before, err := os.ReadFile(app.workspaceRegistry().Path())
64 if err != nil {
65 t.Fatal(err)
66 }
67
68 snapshot := mustProjectTreeSnapshot(t, app)
69 if len(snapshot.Projects) < 8 {
70 t.Fatalf("snapshot projects=%d, want at least 8", len(snapshot.Projects))
71 }
72 pinnedSessions := 0
73 for _, project := range snapshot.Projects {
74 pinnedSessions += len(project.Children)
75 }
76 if pinnedSessions != 8 {
77 t.Fatalf("snapshot pinned sessions=%d, want 8", pinnedSessions)
78 }
79 after, err := os.ReadFile(app.workspaceRegistry().Path())
80 if err != nil {
81 t.Fatal(err)
82 }
83 if !bytes.Equal(before, after) {
84 t.Fatal("project-tree read modified the workspace registry")
85 }
86 }
87
88 func TestSettledOrganizationReadDoesNotAcquireWriterLock(t *testing.T) {
89 app, root, _ := canonicalOrganizationFixture(t, "a", "b")
90 if _, _, err := app.ensureSessionOrganization("project", root); err != nil {
91 t.Fatal(err)
92 }
93 release, err := identitylock.Acquire(t.Context(), app.workspaceRegistry().Path()+".lock")
94 if err != nil {
95 t.Fatal(err)
96 }
97 done := make(chan error, 1)
98 go func() { _, _, err := app.ensureSessionOrganization("project", root); done <- err }()
99 select {
100 case err := <-done:
101 release()
102 if err != nil {
103 t.Fatal(err)
104 }
105 case <-time.After(3 * time.Second):
106 release()
107 <-done
108 t.Fatal("settled list query waited for the registry writer lock")
109 }
110 }
111
112 func TestTopicIndexReusesOrderAndObservesPresentationChanges(t *testing.T) {
113 app, root, refs := canonicalOrganizationFixture(t, "a", "b", "c")
114 req := ProjectTopicPageRequest{Scope: "project", WorkspaceRoot: root, Limit: 1}
115 page, err := app.ListProjectTopics(req)
116 if err != nil || len(page.Items) != 1 || page.NextCursor == "" {
117 t.Fatalf("first page: %+v %v", page, err)
118 }
119 index := &app.desktopSessions.readSnapshots
120 index.mu.Lock()
121 entries := len(index.entries)
122 index.mu.Unlock()
123 if entries != 2 {
124 t.Fatalf("index entries=%d", entries)
125 }
126 req.Cursor = page.NextCursor
127 second, err := app.ListProjectTopics(req)
128 if err != nil || len(second.Items) != 1 {
129 t.Fatalf("second page: %+v %v", second, err)
130 }
131 index.mu.Lock()
132 after := len(index.entries)
133 index.mu.Unlock()
134 if after != entries {
135 t.Fatal("pagination rebuilt an unchanged sorted index")
136 }
137 second.Items[0].Session.SessionID = "caller-owned"
138 repeated, err := app.ListProjectTopics(req)
139 if err != nil || repeated.Items[0].Session.SessionID == "caller-owned" {
140 t.Fatal("page mutation corrupted the index")
141 }
142 pinned := true
143 if err := app.workspaceRegistry().UpdatePresentation(t.Context(), []string{refs["c"].SessionID}, nil, &pinned); err != nil {
144 t.Fatal(err)
145 }
146 if frozen, err := app.ListProjectTopics(req); err != nil || frozen.SnapshotID != page.SnapshotID {
147 t.Fatalf("presentation write disturbed snapshot: %+v %v", frozen, err)
148 }
149 req.Cursor, req.pinnedOnly = "", true
150 page, err = app.ListProjectTopics(req)
151 if err != nil || len(page.Items) != 1 || page.Items[0].Session.SessionID != refs["c"].SessionID {
152 t.Fatalf("pin index: %+v %v", page, err)
153 }
154 }
155
156 func TestCatalogWatchSettledAndUnavailableRootsWaitForRotatingAudit(t *testing.T) {
157 good, missing := t.TempDir(), t.TempDir()
158 good, missing = canonicalWorkspaceRoot(good), canonicalWorkspaceRoot(missing)
159 targets := []sessioncatalog.DirectoryTarget{{Path: good}, {Path: missing}}
160 watched, dirty := map[string]bool{good: true}, map[string]bool{}
161 current := refreshCatalogWatchTargets(nil, nil, targets, watched, dirty, nil)
162 clear(dirty)
163 current = refreshCatalogWatchTargets(nil, current, targets, watched, dirty, nil)
164 if len(dirty) != 0 {
165 t.Fatalf("metadata refresh bypassed the rotating discovery audit: %v", dirty)
166 }
167 current = refreshCatalogWatchTargets(nil, current, targets[:1], watched, dirty, nil)
168 if len(current) != 1 || dirty[missing] {
169 t.Fatal("removed target retained maintenance work")
170 }
171 }
172
172 lines GO