返回 DeepSeek-Reasonix
remote_tab_rejection_order_test.go
根目录 / desktop / remote_tab_rejection_order_test.go
1 package main
2
3 import (
4 "net/http"
5 "strings"
6 "sync"
7 "testing"
8 "time"
9 )
10
11 func TestRemoteResumeFailurePublicationOrdersRetirement(t *testing.T) {
12 for _, kind := range []string{"reconnect", "retire", "suspend", "park", "close", "state"} {
13 t.Run(kind, func(t *testing.T) {
14 isolateDesktopUserDirs(t)
15 client := &http.Client{}
16 tab := &remoteTab{id: "remote-1", state: "ready", client: client, gen: 7, selectionRevision: 9,
17 ref: RemoteTabRef{HostID: "box", Workspace: "app"},
18 session: remoteTabSessionState{name: "target", path: "/target"}, topicTitle: "Target",
19 routing: remoteTabSessionRouting{currentPath: "/target", pathRevision: 11, running: map[string]bool{}},
20 }
21 a := &App{remoteTabs: map[string]*remoteTab{tab.id: tab}}
22 previous := &remoteTabOpenSelection{session: remoteTabSessionState{name: "old", path: "/old"}, topicTitle: "Old", currentPath: "/old", revision: 9}
23 route := a.beginRemoteTabProvisionalResume(tab.id, tab, client, 7, "/target")
24 route.previousSelection = previous
25 entered, release := make(chan struct{}), make(chan struct{})
26 var releaseOnce sync.Once
27 unblock := func() { releaseOnce.Do(func() { close(release) }) }
28 t.Cleanup(unblock)
29 events := &eventLog{}
30 a.remoteEventHook = func(name string, payload any) {
31 if name == "remote-tab:updated" {
32 close(entered)
33 <-release
34 }
35 events.add(name, payload)
36 }
37 finished := make(chan struct{})
38 go func() { a.completeRemoteTabResumeFailure(tab.id, tab, client, 7, route, "rejected"); close(finished) }()
39 select {
40 case <-entered:
41 case <-time.After(3 * time.Second):
42 t.Fatal("failure did not reach metadata publication")
43 }
44 attempted, retired := make(chan struct{}), make(chan struct{})
45 go func() {
46 close(attempted)
47 switch kind {
48 case "reconnect":
49 a.reconnectRemoteTabGeneration(tab.id, 7)
50 case "retire":
51 a.retireRemoteTabGeneration(tab.id, 7)
52 case "suspend":
53 a.suspendRemoteTabPumps("box", "reconnecting", "")
54 case "park":
55 a.parkRemoteTabsForServer("box", "app", "serve_down", "")
56 case "close":
57 _ = a.closeRemoteTabRegistration(tab.id, true)
58 case "state":
59 a.emitRemoteTabStateForGeneration(tab.id, 7, "error", "stream ended")
60 }
61 close(retired)
62 }()
63 <-attempted
64 select {
65 case <-retired:
66 t.Fatal("retirement overtook an in-flight failure publication")
67 case <-time.After(30 * time.Millisecond):
68 }
69 a.remoteTabMu.Lock()
70 intact := a.remoteTabs[tab.id] == tab && tab.gen == 7 && tab.state == "ready" && tab.err == "rejected" && tab.session.path == "/old"
71 a.remoteTabMu.Unlock()
72 if !intact {
73 t.Fatal("retirement mutated identity before prior publication completed")
74 }
75 unblock()
76 select {
77 case <-finished:
78 case <-time.After(3 * time.Second):
79 t.Fatal("failure did not finish")
80 }
81 select {
82 case <-retired:
83 case <-time.After(3 * time.Second):
84 t.Fatal("retirement did not finish")
85 }
86 records := events.recorded()
87 if len(records) < 2 {
88 t.Fatalf("missing ordered failure events: %v", records)
89 }
90 // The terminal failure follows its metadata; any retirement state follows both.
91 if !strings.Contains(records[0], "remote-tab:updated") || !strings.Contains(records[1], "rejected") {
92 t.Fatalf("publication order = %v", records)
93 }
94 })
95 }
96 }
97
98 func TestRemoteResumeFailureRejectsLostOwnership(t *testing.T) {
99 for _, kind := range []string{"generation", "selection", "route-revision", "path", "client", "replacement"} {
100 t.Run(kind, func(t *testing.T) {
101 client := &http.Client{}
102 tab := &remoteTab{id: "remote-1", state: "ready", client: client, gen: 7, selectionRevision: 9,
103 session: remoteTabSessionState{path: "/old"}, routing: remoteTabSessionRouting{currentPath: "/old", pathRevision: 11, running: map[string]bool{}},
104 }
105 log := &eventLog{}
106 a := &App{remoteTabs: map[string]*remoteTab{tab.id: tab}, remoteEventHook: log.add}
107 route := a.beginRemoteTabProvisionalResume(tab.id, tab, client, 7, "/target")
108 switch kind {
109 case "generation":
110 tab.gen++
111 case "selection":
112 tab.selectionRevision++
113 case "route-revision":
114 tab.routing.pathRevision++
115 case "path":
116 tab.routing.currentPath = "/newer"
117 case "client":
118 tab.client = &http.Client{}
119 case "replacement":
120 a.remoteTabs[tab.id] = &remoteTab{id: tab.id, state: "ready", gen: 7, client: client}
121 }
122 beforeRoute := tab.routing.currentPath
123 if !a.completeRemoteTabResumeFailure(tab.id, tab, client, 7, route, "obsolete") {
124 t.Fatal("stale failure claimed completion")
125 }
126 if tab.err != "" || tab.routing.currentPath != beforeRoute || len(log.recorded()) != 0 {
127 t.Fatalf("stale failure mutated or published: error=%q route=%q events=%v", tab.err, tab.routing.currentPath, log.recorded())
128 }
129 })
130 }
131 }
132
132 lines GO