返回 DeepSeek-Reasonix
tabs_sink_context_test.go
根目录 / desktop / tabs_sink_context_test.go
1 package main
2
3 import (
4 "context"
5 "sync"
6 "testing"
7 "time"
8
9 "reasonix/internal/event"
10 )
11
12 // All tabEventSink context mutations go through the locked setContext /
13 // clearContext accessors (no bare s.ctx = ... writes that data-race the
14 // s.context() reads in emitRuntimeEvent). After clearContext the sink stops
15 // emitting — emitRuntimeEvent sees a nil ctx and no-ops — and the queued
16 // emitter is drained, so a detached/backgrounded session can't flush stale
17 // events onto the now-rebound tab (#5352: stale "AI 不断输出" on the visible
18 // session after rapid session switching).
19 func TestTabEventSinkClearContextStopsEmission(t *testing.T) {
20 var mu sync.Mutex
21 var emitted int
22 s := &tabEventSink{tabID: "t"}
23 s.runtimeEvents.emit = func(context.Context, string, ...any) {
24 mu.Lock()
25 emitted++
26 mu.Unlock()
27 }
28
29 s.setContext(context.Background())
30 if s.context() == nil {
31 t.Fatal("setContext did not install the context")
32 }
33
34 s.clearContext()
35 if s.context() != nil {
36 t.Fatal("clearContext did not clear the context")
37 }
38
39 // An emit after clearContext must not reach the runtime bridge.
40 s.emitRuntimeEvent(eventChannel, toWireTab(event.Event{}, s.tabID))
41
42 mu.Lock()
43 defer mu.Unlock()
44 if emitted != 0 {
45 t.Fatalf("sink emitted %d events after clearContext, want 0", emitted)
46 }
47 }
48
49 func TestTabEventSinkUsesBoundSessionGeneration(t *testing.T) {
50 sink := &tabEventSink{tabID: "tab", ctx: context.Background()}
51 sink.setSessionGeneration(7)
52 delivered := make(chan uint64, 1)
53 sink.runtimeEvents.emit = func(_ context.Context, name string, payload ...any) {
54 if name != eventChannel || len(payload) != 1 {
55 t.Fatalf("runtime event = %q/%d, want one %q event", name, len(payload), eventChannel)
56 }
57 wire, ok := payload[0].(wireEventTab)
58 if !ok {
59 t.Fatalf("payload type = %T, want wireEventTab", payload[0])
60 }
61 delivered <- wire.SessionGeneration
62 }
63 sink.Emit(event.Event{Kind: event.Notice, Text: "late"})
64 select {
65 case got := <-delivered:
66 if got != 7 {
67 t.Fatalf("event session generation = %d, want bound generation 7", got)
68 }
69 case <-time.After(time.Second):
70 t.Fatal("timed out waiting for sink event")
71 }
72 }
73
73 lines GO