| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "path/filepath" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | "time" |
| 8 | |
| 9 | "reasonix/internal/agent" |
| 10 | "reasonix/internal/control" |
| 11 | "reasonix/internal/event" |
| 12 | "reasonix/internal/provider" |
| 13 | "reasonix/internal/transcript" |
| 14 | ) |
| 15 | |
| 16 | func TestPreloadedPageDoesNotEraseNewlyCommittedTurn(t *testing.T) { |
| 17 | isolateDesktopUserDirs(t) |
| 18 | path := filepath.Join(t.TempDir(), "session.jsonl") |
| 19 | session := agent.NewSession("system") |
| 20 | session.Add(provider.Message{Role: provider.RoleUser, Content: "first"}) |
| 21 | if err := session.Save(path); err != nil { |
| 22 | t.Fatal(err) |
| 23 | } |
| 24 | executor := agent.New(nil, nil, session, agent.Options{}, event.Discard) |
| 25 | ctrl := control.New(control.Options{Executor: executor, SessionDir: filepath.Dir(path), SessionPath: path, Sink: event.Discard}) |
| 26 | defer ctrl.Close() |
| 27 | // A same-session or detached-runtime switch reads before the owner settles. |
| 28 | preloaded, err := agent.LoadSession(path) |
| 29 | if err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | session.Add(provider.Message{Role: provider.RoleUser, Content: "second"}) |
| 33 | session.Add(provider.Message{Role: provider.RoleAssistant, Content: "completed while switching"}) |
| 34 | if err := ctrl.Snapshot(); err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | if ctrl.RuntimeStatus().Running || ctrl.SessionHasUnsavedChanges() { |
| 38 | t.Fatal("fixture must be idle and fully persisted") |
| 39 | } |
| 40 | tab := &WorkspaceTab{ID: "tab", Ctrl: ctrl, SessionPath: path} |
| 41 | page, reread := historyPageForController(tab, ctrl, preloaded, path, 0, defaultHistoryPageTurns) |
| 42 | if !reread { |
| 43 | t.Fatal("obsolete preload must be refreshed before installation") |
| 44 | } |
| 45 | current, _ := historyPageForController(tab, ctrl, nil, "", 0, defaultHistoryPageTurns) |
| 46 | if current.TotalTurns != 2 { |
| 47 | t.Fatalf("invalid current history: %d", current.TotalTurns) |
| 48 | } |
| 49 | if page.TotalTurns != current.TotalTurns { |
| 50 | t.Fatalf("reused an obsolete preload: switch=%d current=%d", page.TotalTurns, current.TotalTurns) |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | func TestTranscriptSwitchReportsActualLoadWithoutLegacyPage(t *testing.T) { |
| 55 | for _, channel := range []bool{false, true} { |
| 56 | t.Run(map[bool]string{false: "resume", true: "channel"}[channel], func(t *testing.T) { |
| 57 | app, tab, _, _, _, _ := newAtomicRebindTestApp(t) |
| 58 | path := resumedCleanTarget(t, app, tab, "snapshot-target.jsonl") |
| 59 | var phases HistorySwitchPhases |
| 60 | var err error |
| 61 | if channel { |
| 62 | phases, err = app.OpenChannelTranscriptSessionForTab(tab.ID, path) |
| 63 | } else { |
| 64 | phases, err = app.ResumeTranscriptSessionForTab(tab.ID, path) |
| 65 | } |
| 66 | if err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | if phases.Outcome != "ok" || phases.DurableReads != 1 || phases.LoadedCount == 0 || phases.LoadedBytes == 0 { |
| 70 | t.Fatalf("missing load evidence: %+v", phases) |
| 71 | } |
| 72 | if phases.HistoryCount != 0 || phases.HistoryMs != 0 { |
| 73 | t.Fatalf("snapshot adoption built a legacy page: %+v", phases) |
| 74 | } |
| 75 | snapshot, err := app.TranscriptSnapshotForTab(tab.ID, transcript.PageRequest{}) |
| 76 | if err != nil || tab.SessionID == "" || snapshot.Identity.SessionID != tab.SessionID || len(snapshot.Records) == 0 { |
| 77 | t.Fatalf("snapshot did not adopt target: %v", err) |
| 78 | } |
| 79 | if tab.currentSessionPath() != "" { |
| 80 | t.Fatalf("v3 tab retained legacy execution path %q", tab.currentSessionPath()) |
| 81 | } |
| 82 | if tab.ReadOnly != channel { |
| 83 | t.Fatal("switch changed channel write policy") |
| 84 | } |
| 85 | }) |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | func TestLargeTranscriptSwitchPhaseMeasurement(t *testing.T) { |
| 90 | app, tab, _, _, _, _ := newAtomicRebindTestApp(t) |
| 91 | path := resumedCleanTarget(t, app, tab, "large-snapshot-target.jsonl") |
| 92 | loaded, err := agent.LoadSession(path) |
| 93 | if err != nil { |
| 94 | t.Fatal(err) |
| 95 | } |
| 96 | for range 500 { |
| 97 | loaded.Add(provider.Message{Role: provider.RoleUser, Content: "question"}) |
| 98 | loaded.Add(provider.Message{Role: provider.RoleAssistant, Content: strings.Repeat("x", 10<<10)}) |
| 99 | } |
| 100 | if err := loaded.Save(path); err != nil { |
| 101 | t.Fatal(err) |
| 102 | } |
| 103 | phases, err := app.ResumeTranscriptSessionForTab(tab.ID, path) |
| 104 | if err != nil { |
| 105 | t.Fatal(err) |
| 106 | } |
| 107 | started := time.Now() |
| 108 | follow, err := app.TranscriptFollowForTab(tab.ID, transcript.FollowRequest{}) |
| 109 | if err != nil || follow.Snapshot == nil || follow.History == nil || !follow.History.HasOlder { |
| 110 | t.Fatalf("large snapshot: %v", err) |
| 111 | } |
| 112 | defer app.TranscriptFollowForTab(tab.ID, transcript.FollowRequest{Subscription: follow.Subscription, Close: true}) |
| 113 | snapshot := follow.Snapshot |
| 114 | if phases.DurableReads != 1 || phases.HistoryCount != 0 { |
| 115 | t.Fatalf("large switch repeated history work: %+v", phases) |
| 116 | } |
| 117 | t.Logf("loaded_bytes=%d loaded_messages=%d load_ms=%d rebind_ms=%d snapshot_ms=%d records=%d durable_reads=%d", phases.LoadedBytes, phases.LoadedCount, phases.LoadMs, phases.RebindMs, time.Since(started).Milliseconds(), len(snapshot.Records), phases.DurableReads) |
| 118 | } |
| 119 |