| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "sync" |
| 6 | "testing" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | func TestTopicActivationPendingPruneDoesNotBlockNextHistoryOpen(t *testing.T) { |
| 11 | app, source, target, root, _ := canonicalWorkspaceOpenFixture(t) |
| 12 | app.readyHook = func() {} |
| 13 | installNoopRuntimeEvents(app, source.sink) |
| 14 | events := newActivationEventRecorder(app) |
| 15 | t.Cleanup(func() { app.shutdown(context.Background()) }) |
| 16 | pruning := make(chan struct{}, 4) |
| 17 | app.runtimeMutationBeforeLockHook = func(operation string) { |
| 18 | if operation == "prune-visible-tabs" { |
| 19 | pruning <- struct{}{} |
| 20 | } |
| 21 | } |
| 22 | app.runtimeRebuildMu.Lock() |
| 23 | unlock := sync.OnceFunc(app.runtimeRebuildMu.Unlock) |
| 24 | defer unlock() |
| 25 | _, err := app.StartTopicActivation(TopicActivationRequest{ |
| 26 | Scope: source.Scope, WorkspaceRoot: source.WorkspaceRoot, |
| 27 | SessionPath: sessionRoute(source.SessionID), RequestID: "source", |
| 28 | }) |
| 29 | if err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | select { |
| 33 | case <-pruning: |
| 34 | case <-time.After(5 * time.Second): |
| 35 | t.Fatal("source completion did not reach runtime pruning") |
| 36 | } |
| 37 | type result struct { |
| 38 | ticket TopicActivationTicket |
| 39 | err error |
| 40 | } |
| 41 | done := make(chan result, 1) |
| 42 | go func() { |
| 43 | ticket, err := app.StartTopicActivation(TopicActivationRequest{ |
| 44 | Scope: "project", WorkspaceRoot: root, SessionPath: sessionRoute(target.Ref().SessionID), RequestID: "target", |
| 45 | }) |
| 46 | done <- result{ticket, err} |
| 47 | }() |
| 48 | var ticket TopicActivationTicket |
| 49 | select { |
| 50 | case got := <-done: |
| 51 | if got.err != nil { |
| 52 | t.Fatal(got.err) |
| 53 | } |
| 54 | ticket = got.ticket |
| 55 | case <-time.After(5 * time.Second): |
| 56 | unlock() |
| 57 | <-done |
| 58 | t.Fatal("previous activation's runtime prune blocked the next history identity") |
| 59 | } |
| 60 | canonicalTopicHistoryReady(t, app, ticket.TabID) |
| 61 | unlock() |
| 62 | events.waitFor(t, activationEventFor("target", "ready")) |
| 63 | app.mu.RLock() |
| 64 | visible := app.tabs[app.activeTabID] |
| 65 | correct := visible != nil && visible.ID == ticket.TabID && visible.SessionID == target.Ref().SessionID |
| 66 | app.mu.RUnlock() |
| 67 | if !correct { |
| 68 | t.Fatal("late prune replaced the latest selected history") |
| 69 | } |
| 70 | } |
| 71 |