|
1 |
| -import { EditorSessionEntry, EditorSessionManager } from "../../../src/node/vscodeSocket" |
2 |
| -import { clean, tmpdir } from "../../utils/helpers" |
3 |
| -import * as path from "path" |
4 | 1 | import * as net from "net"
|
5 |
| -import { promises as fs } from "fs" |
6 |
| - |
7 |
| -const makeTestEntries = (tmpDirPath: string): EditorSessionEntry[] => { |
8 |
| - return [ |
9 |
| - { |
10 |
| - workspace: { |
11 |
| - id: "ws1", |
12 |
| - folders: [ |
13 |
| - { |
14 |
| - uri: { |
15 |
| - path: "/foo", |
16 |
| - }, |
17 |
| - }, |
18 |
| - { |
19 |
| - uri: { |
20 |
| - path: "/bar", |
21 |
| - }, |
22 |
| - }, |
23 |
| - ], |
24 |
| - }, |
25 |
| - socketPath: `${tmpDirPath}/vscode-ipc-1-old.sock`, |
26 |
| - }, |
27 |
| - { |
28 |
| - workspace: { |
29 |
| - id: "ws1", |
30 |
| - folders: [ |
31 |
| - { |
32 |
| - uri: { |
33 |
| - path: "/foo", |
34 |
| - }, |
35 |
| - }, |
36 |
| - { |
37 |
| - uri: { |
38 |
| - path: "/bar", |
39 |
| - }, |
40 |
| - }, |
41 |
| - ], |
42 |
| - }, |
43 |
| - socketPath: `${tmpDirPath}/vscode-ipc-1-new.sock`, |
44 |
| - }, |
45 |
| - { |
46 |
| - workspace: { |
47 |
| - id: "ws2", |
48 |
| - folders: [ |
49 |
| - { |
50 |
| - uri: { |
51 |
| - path: "/foo", |
52 |
| - }, |
53 |
| - }, |
54 |
| - { |
55 |
| - uri: { |
56 |
| - path: "/baz", |
57 |
| - }, |
58 |
| - }, |
59 |
| - ], |
60 |
| - }, |
61 |
| - socketPath: `${tmpDirPath}/vscode-ipc-2.sock`, |
62 |
| - }, |
63 |
| - ] |
64 |
| -} |
| 2 | +import { EditorSessionManager } from "../../../src/node/vscodeSocket" |
| 3 | +import { clean, tmpdir } from "../../utils/helpers" |
65 | 4 |
|
66 |
| -describe("VscodeSocketResolver", () => { |
| 5 | +describe("EditorSessionManager", () => { |
67 | 6 | let tmpDirPath: string
|
68 |
| - let tmpFilePath: string |
69 |
| - let entries: EditorSessionEntry[] = [] |
70 |
| - let fileContents = "" |
71 | 7 |
|
72 |
| - const testName = "readSocketPath" |
| 8 | + const testName = "esm" |
| 9 | + |
73 | 10 | beforeAll(async () => {
|
74 | 11 | await clean(testName)
|
75 | 12 | })
|
76 | 13 |
|
77 | 14 | beforeEach(async () => {
|
78 | 15 | tmpDirPath = await tmpdir(testName)
|
79 |
| - tmpFilePath = path.join(tmpDirPath, "readSocketPath.txt") |
80 |
| - entries = makeTestEntries(tmpDirPath) |
81 |
| - fileContents = entries.map((entry) => JSON.stringify(entry)).join("\n") |
82 |
| - await fs.writeFile(tmpFilePath, fileContents) |
83 | 16 | })
|
84 | 17 |
|
85 |
| - it("should throw an error on load if the socket registry file is a directory", async () => { |
86 |
| - const resolver = new EditorSessionManager(tmpDirPath) |
87 |
| - expect(() => resolver.load()).rejects.toThrow("EISDIR") |
88 |
| - }) |
89 |
| - |
90 |
| - describe("getSocketPaths", () => { |
91 |
| - it("should return empty list if it can't read the socket registry file", async () => { |
92 |
| - const resolver = new EditorSessionManager(path.join(tmpDirPath, "not-a-file")) |
93 |
| - await resolver.load() |
94 |
| - const socketPaths = resolver.getSocketPaths() |
95 |
| - expect(socketPaths).toEqual([]) |
96 |
| - }) |
97 |
| - it("should prefer the last added recent socket path for a matching path", async () => { |
98 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
99 |
| - await resolver.load() |
100 |
| - const socketPaths = resolver.getSocketPaths(["/bar/README.md:1:1"]) |
101 |
| - expect(socketPaths).toEqual([ |
| 18 | + describe("getCandidatesForFile", () => { |
| 19 | + it("should prefer the last added socket path for a matching path", async () => { |
| 20 | + const manager = new EditorSessionManager() |
| 21 | + manager.addSession({ |
| 22 | + workspace: { |
| 23 | + id: "aaa", |
| 24 | + folders: [ |
| 25 | + { |
| 26 | + uri: { |
| 27 | + path: "/aaa", |
| 28 | + }, |
| 29 | + }, |
| 30 | + ], |
| 31 | + }, |
| 32 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`, |
| 33 | + }) |
| 34 | + manager.addSession({ |
| 35 | + workspace: { |
| 36 | + id: "aaa", |
| 37 | + folders: [ |
| 38 | + { |
| 39 | + uri: { |
| 40 | + path: "/aaa", |
| 41 | + }, |
| 42 | + }, |
| 43 | + ], |
| 44 | + }, |
| 45 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`, |
| 46 | + }) |
| 47 | + manager.addSession({ |
| 48 | + workspace: { |
| 49 | + id: "bbb", |
| 50 | + folders: [ |
| 51 | + { |
| 52 | + uri: { |
| 53 | + path: "/bbb", |
| 54 | + }, |
| 55 | + }, |
| 56 | + ], |
| 57 | + }, |
| 58 | + socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`, |
| 59 | + }) |
| 60 | + const socketPaths = manager.getCandidatesForFile("/aaa/some-file:1:1") |
| 61 | + expect(socketPaths.map((x) => x.socketPath)).toEqual([ |
102 | 62 | // Matches
|
103 |
| - `${tmpDirPath}/vscode-ipc-1-new.sock`, |
104 |
| - `${tmpDirPath}/vscode-ipc-1-old.sock`, |
| 63 | + `${tmpDirPath}/vscode-ipc-aaa-2.sock`, |
| 64 | + `${tmpDirPath}/vscode-ipc-aaa-1.sock`, |
105 | 65 | // Non-matches
|
106 |
| - `${tmpDirPath}/vscode-ipc-2.sock`, |
107 |
| - ]) |
108 |
| - }) |
109 |
| - it("should prefer the last added socket path for a matching path, even across workspaces", async () => { |
110 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
111 |
| - await resolver.load() |
112 |
| - const socketPaths = resolver.getSocketPaths(["/foo/README.md:1:1"]) |
113 |
| - expect(socketPaths).toEqual([ |
114 |
| - // Matches |
115 |
| - `${tmpDirPath}/vscode-ipc-2.sock`, |
116 |
| - `${tmpDirPath}/vscode-ipc-1-new.sock`, |
117 |
| - `${tmpDirPath}/vscode-ipc-1-old.sock`, |
| 66 | + `${tmpDirPath}/vscode-ipc-bbb.sock`, |
118 | 67 | ])
|
119 | 68 | })
|
120 |
| - it("should return the last added socketPath if there are no matches", async () => { |
121 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
122 |
| - await resolver.load() |
123 | 69 |
|
124 |
| - const socketPaths = resolver.getSocketPaths() |
125 |
| - expect(socketPaths).toEqual([ |
126 |
| - `${tmpDirPath}/vscode-ipc-2.sock`, |
127 |
| - `${tmpDirPath}/vscode-ipc-1-new.sock`, |
128 |
| - `${tmpDirPath}/vscode-ipc-1-old.sock`, |
| 70 | + it("should return the last added socketPath if there are no matches", async () => { |
| 71 | + const manager = new EditorSessionManager() |
| 72 | + manager.addSession({ |
| 73 | + workspace: { |
| 74 | + id: "aaa", |
| 75 | + folders: [ |
| 76 | + { |
| 77 | + uri: { |
| 78 | + path: "/aaa", |
| 79 | + }, |
| 80 | + }, |
| 81 | + ], |
| 82 | + }, |
| 83 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`, |
| 84 | + }) |
| 85 | + manager.addSession({ |
| 86 | + workspace: { |
| 87 | + id: "bbb", |
| 88 | + folders: [ |
| 89 | + { |
| 90 | + uri: { |
| 91 | + path: "/bbb", |
| 92 | + }, |
| 93 | + }, |
| 94 | + ], |
| 95 | + }, |
| 96 | + socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`, |
| 97 | + }) |
| 98 | + const socketPaths = manager.getCandidatesForFile("/ccc/some-file:1:1") |
| 99 | + expect(socketPaths.map((x) => x.socketPath)).toEqual([ |
| 100 | + `${tmpDirPath}/vscode-ipc-bbb.sock`, |
| 101 | + `${tmpDirPath}/vscode-ipc-aaa.sock`, |
129 | 102 | ])
|
130 | 103 | })
|
131 |
| - }) |
132 |
| - |
133 |
| - describe("save", () => { |
134 |
| - it("should write back to the socket registry file", async () => { |
135 |
| - jest.spyOn(fs, "writeFile") |
136 |
| - expect(fs.writeFile).not.toHaveBeenCalled() |
137 | 104 |
|
138 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
139 |
| - await resolver.load() |
140 |
| - await resolver.save() |
141 |
| - expect(fs.writeFile).toHaveBeenCalledWith(tmpFilePath, fileContents) |
| 105 | + it("does not just directly do a substring match", async () => { |
| 106 | + const manager = new EditorSessionManager() |
| 107 | + manager.addSession({ |
| 108 | + workspace: { |
| 109 | + id: "aaa", |
| 110 | + folders: [ |
| 111 | + { |
| 112 | + uri: { |
| 113 | + path: "/aaa", |
| 114 | + }, |
| 115 | + }, |
| 116 | + ], |
| 117 | + }, |
| 118 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`, |
| 119 | + }) |
| 120 | + manager.addSession({ |
| 121 | + workspace: { |
| 122 | + id: "bbb", |
| 123 | + folders: [ |
| 124 | + { |
| 125 | + uri: { |
| 126 | + path: "/bbb", |
| 127 | + }, |
| 128 | + }, |
| 129 | + ], |
| 130 | + }, |
| 131 | + socketPath: `${tmpDirPath}/vscode-ipc-bbb.sock`, |
| 132 | + }) |
| 133 | + const entries = manager.getCandidatesForFile("/aaaxxx/some-file:1:1") |
| 134 | + expect(entries.map((x) => x.socketPath)).toEqual([ |
| 135 | + `${tmpDirPath}/vscode-ipc-bbb.sock`, |
| 136 | + `${tmpDirPath}/vscode-ipc-aaa.sock`, |
| 137 | + ]) |
142 | 138 | })
|
143 | 139 | })
|
144 | 140 |
|
145 | 141 | describe("getConnectedSocketPath", () => {
|
146 | 142 | it("should return socket path if socket is active", async () => {
|
147 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
148 |
| - await resolver.load() |
149 |
| - const connectedPromise = new Promise((resolve) => { |
150 |
| - const server = net.createServer(() => { |
151 |
| - // Close after getting the first connection. |
152 |
| - server.close() |
153 |
| - }) |
154 |
| - server.once("listening", () => resolve(server)) |
155 |
| - server.listen(`${tmpDirPath}/vscode-ipc-1-new.sock`) |
| 143 | + listenOn(`${tmpDirPath}/vscode-ipc-aaa.sock`) |
| 144 | + const manager = new EditorSessionManager() |
| 145 | + manager.addSession({ |
| 146 | + workspace: { |
| 147 | + id: "aaa", |
| 148 | + folders: [ |
| 149 | + { |
| 150 | + uri: { |
| 151 | + path: "/aaa", |
| 152 | + }, |
| 153 | + }, |
| 154 | + ], |
| 155 | + }, |
| 156 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`, |
156 | 157 | })
|
157 |
| - const socketPathPromise = resolver.getConnectedSocketPath(["/bar/README.md:1:1"]) |
158 |
| - const [socketPath] = await Promise.all([socketPathPromise, connectedPromise]) |
159 |
| - expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-1-new.sock`) |
| 158 | + const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1") |
| 159 | + expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa.sock`) |
160 | 160 | })
|
| 161 | + |
161 | 162 | it("should return undefined if socket is inactive", async () => {
|
162 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
163 |
| - await resolver.load() |
164 |
| - const socketPath = await resolver.getConnectedSocketPath(["/bar/README.md:1:1"]) |
| 163 | + const manager = new EditorSessionManager() |
| 164 | + manager.addSession({ |
| 165 | + workspace: { |
| 166 | + id: "aaa", |
| 167 | + folders: [ |
| 168 | + { |
| 169 | + uri: { |
| 170 | + path: "/aaa", |
| 171 | + }, |
| 172 | + }, |
| 173 | + ], |
| 174 | + }, |
| 175 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`, |
| 176 | + }) |
| 177 | + const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1") |
| 178 | + expect(socketPath).toBeUndefined() |
| 179 | + }) |
| 180 | + |
| 181 | + it("should return undefined given no matching active sockets", async () => { |
| 182 | + const servers = listenOn(`${tmpDirPath}/vscode-ipc-bbb.sock`) |
| 183 | + const manager = new EditorSessionManager() |
| 184 | + manager.addSession({ |
| 185 | + workspace: { |
| 186 | + id: "aaa", |
| 187 | + folders: [ |
| 188 | + { |
| 189 | + uri: { |
| 190 | + path: "/aaa", |
| 191 | + }, |
| 192 | + }, |
| 193 | + ], |
| 194 | + }, |
| 195 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa.sock`, |
| 196 | + }) |
| 197 | + const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1") |
165 | 198 | expect(socketPath).toBeUndefined()
|
| 199 | + servers.close() |
166 | 200 | })
|
| 201 | + |
| 202 | + it("should return undefined if there are no entries", async () => { |
| 203 | + const manager = new EditorSessionManager() |
| 204 | + const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1") |
| 205 | + expect(socketPath).toBeUndefined() |
| 206 | + }) |
| 207 | + |
167 | 208 | it("should return most recently used socket path available", async () => {
|
168 |
| - jest.spyOn(fs, "writeFile") |
169 |
| - const resolver = new EditorSessionManager(tmpFilePath) |
170 |
| - jest.spyOn(resolver, "save") |
171 |
| - await resolver.load() |
172 |
| - const connectedPromise = new Promise((resolve) => { |
173 |
| - const server = net.createServer(() => { |
174 |
| - // Close after getting the first connection. |
175 |
| - server.close() |
176 |
| - }) |
177 |
| - server.once("listening", () => resolve(server)) |
178 |
| - server.listen(`${tmpDirPath}/vscode-ipc-1-old.sock`) |
| 209 | + listenOn(`${tmpDirPath}/vscode-ipc-aaa-1.sock`) |
| 210 | + const manager = new EditorSessionManager() |
| 211 | + manager.addSession({ |
| 212 | + workspace: { |
| 213 | + id: "aaa", |
| 214 | + folders: [ |
| 215 | + { |
| 216 | + uri: { |
| 217 | + path: "/aaa", |
| 218 | + }, |
| 219 | + }, |
| 220 | + ], |
| 221 | + }, |
| 222 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa-1.sock`, |
| 223 | + }) |
| 224 | + manager.addSession({ |
| 225 | + workspace: { |
| 226 | + id: "aaa", |
| 227 | + folders: [ |
| 228 | + { |
| 229 | + uri: { |
| 230 | + path: "/aaa", |
| 231 | + }, |
| 232 | + }, |
| 233 | + ], |
| 234 | + }, |
| 235 | + socketPath: `${tmpDirPath}/vscode-ipc-aaa-2.sock`, |
179 | 236 | })
|
180 |
| - const socketPathPromise = resolver.getConnectedSocketPath(["/bar/README.md:1:1"]) |
181 |
| - const [socketPath] = await Promise.all([socketPathPromise, connectedPromise]) |
182 |
| - expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-1-old.sock`) |
183 | 237 |
|
184 |
| - // Should clear socket paths that failed to connect. |
185 |
| - await (resolver.save as unknown as jest.SpiedFunction<typeof resolver.save>).mock.results[0].value |
186 |
| - expect(fs.writeFile).toHaveBeenCalledWith( |
187 |
| - tmpFilePath, |
188 |
| - [JSON.stringify(entries[0]), JSON.stringify(entries[2])].join("\n"), |
189 |
| - ) |
| 238 | + const socketPath = await manager.getConnectedSocketPath("/aaa/some-file:1:1") |
| 239 | + expect(socketPath).toBe(`${tmpDirPath}/vscode-ipc-aaa-1.sock`) |
| 240 | + // Failed sockets should be removed from the entries. |
| 241 | + expect((manager as any).entries.has(`${tmpDirPath}/vscode-ipc-aaa-2.sock`)).toBe(false) |
190 | 242 | })
|
191 | 243 | })
|
192 | 244 | })
|
| 245 | + |
| 246 | +function listenOn(...socketPaths: string[]) { |
| 247 | + const servers = socketPaths.map((socketPath) => { |
| 248 | + const server = net.createServer(() => { |
| 249 | + close() |
| 250 | + }) |
| 251 | + server.listen(socketPath) |
| 252 | + return server |
| 253 | + }) |
| 254 | + |
| 255 | + async function close() { |
| 256 | + await Promise.all( |
| 257 | + servers.map( |
| 258 | + (server) => |
| 259 | + new Promise<void>((resolve, reject) => { |
| 260 | + server.close((err) => { |
| 261 | + if (err) { |
| 262 | + reject(err) |
| 263 | + return |
| 264 | + } |
| 265 | + resolve() |
| 266 | + }) |
| 267 | + }), |
| 268 | + ), |
| 269 | + ) |
| 270 | + } |
| 271 | + return { close } |
| 272 | +} |
0 commit comments