Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/parts/ipc/test/common/ipc.test.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { timeout } from '../../../../common/async.js';
8
import { VSBuffer } from '../../../../common/buffer.js';
9
import { CancellationToken, CancellationTokenSource } from '../../../../common/cancellation.js';
10
import { canceled } from '../../../../common/errors.js';
11
import { Emitter, Event } from '../../../../common/event.js';
12
import { DisposableStore } from '../../../../common/lifecycle.js';
13
import { isEqual } from '../../../../common/resources.js';
14
import { URI } from '../../../../common/uri.js';
15
import { BufferReader, BufferWriter, ClientConnectionEvent, deserialize, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel, serialize } from '../../common/ipc.js';
16
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../test/common/utils.js';
17
18
class QueueProtocol implements IMessagePassingProtocol {
19
20
private buffering = true;
21
private buffers: VSBuffer[] = [];
22
23
private readonly _onMessage = new Emitter<VSBuffer>({
24
onDidAddFirstListener: () => {
25
for (const buffer of this.buffers) {
26
this._onMessage.fire(buffer);
27
}
28
29
this.buffers = [];
30
this.buffering = false;
31
},
32
onDidRemoveLastListener: () => {
33
this.buffering = true;
34
}
35
});
36
37
readonly onMessage = this._onMessage.event;
38
other!: QueueProtocol;
39
40
send(buffer: VSBuffer): void {
41
this.other.receive(buffer);
42
}
43
44
protected receive(buffer: VSBuffer): void {
45
if (this.buffering) {
46
this.buffers.push(buffer);
47
} else {
48
this._onMessage.fire(buffer);
49
}
50
}
51
}
52
53
function createProtocolPair(): [IMessagePassingProtocol, IMessagePassingProtocol] {
54
const one = new QueueProtocol();
55
const other = new QueueProtocol();
56
one.other = other;
57
other.other = one;
58
59
return [one, other];
60
}
61
62
class TestIPCClient extends IPCClient<string> {
63
64
private readonly _onDidDisconnect = new Emitter<void>();
65
readonly onDidDisconnect = this._onDidDisconnect.event;
66
67
constructor(protocol: IMessagePassingProtocol, id: string) {
68
super(protocol, id);
69
}
70
71
override dispose(): void {
72
this._onDidDisconnect.fire();
73
super.dispose();
74
}
75
}
76
77
class TestIPCServer extends IPCServer<string> {
78
79
private readonly onDidClientConnect: Emitter<ClientConnectionEvent>;
80
81
constructor() {
82
const onDidClientConnect = new Emitter<ClientConnectionEvent>();
83
super(onDidClientConnect.event);
84
this.onDidClientConnect = onDidClientConnect;
85
}
86
87
createConnection(id: string): IPCClient<string> {
88
const [pc, ps] = createProtocolPair();
89
const client = new TestIPCClient(pc, id);
90
91
this.onDidClientConnect.fire({
92
protocol: ps,
93
onDidClientDisconnect: client.onDidDisconnect
94
});
95
96
return client;
97
}
98
}
99
100
const TestChannelId = 'testchannel';
101
102
interface ITestService {
103
marco(): Promise<string>;
104
error(message: string): Promise<void>;
105
neverComplete(): Promise<void>;
106
neverCompleteCT(cancellationToken: CancellationToken): Promise<void>;
107
buffersLength(buffers: VSBuffer[]): Promise<number>;
108
marshall(uri: URI): Promise<URI>;
109
context(): Promise<unknown>;
110
111
readonly onPong: Event<string>;
112
}
113
114
class TestService implements ITestService {
115
116
private readonly disposables = new DisposableStore();
117
118
private readonly _onPong = new Emitter<string>();
119
readonly onPong = this._onPong.event;
120
121
marco(): Promise<string> {
122
return Promise.resolve('polo');
123
}
124
125
error(message: string): Promise<void> {
126
return Promise.reject(new Error(message));
127
}
128
129
neverComplete(): Promise<void> {
130
return new Promise(_ => { });
131
}
132
133
neverCompleteCT(cancellationToken: CancellationToken): Promise<void> {
134
if (cancellationToken.isCancellationRequested) {
135
return Promise.reject(canceled());
136
}
137
138
return new Promise((_, e) => this.disposables.add(cancellationToken.onCancellationRequested(() => e(canceled()))));
139
}
140
141
buffersLength(buffers: VSBuffer[]): Promise<number> {
142
return Promise.resolve(buffers.reduce((r, b) => r + b.buffer.length, 0));
143
}
144
145
ping(msg: string): void {
146
this._onPong.fire(msg);
147
}
148
149
marshall(uri: URI): Promise<URI> {
150
return Promise.resolve(uri);
151
}
152
153
context(context?: unknown): Promise<unknown> {
154
return Promise.resolve(context);
155
}
156
157
dispose() {
158
this.disposables.dispose();
159
}
160
}
161
162
class TestChannel implements IServerChannel {
163
164
constructor(private service: ITestService) { }
165
166
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
167
switch (command) {
168
case 'marco': return this.service.marco();
169
case 'error': return this.service.error(arg);
170
case 'neverComplete': return this.service.neverComplete();
171
case 'neverCompleteCT': return this.service.neverCompleteCT(cancellationToken);
172
case 'buffersLength': return this.service.buffersLength(arg);
173
default: return Promise.reject(new Error('not implemented'));
174
}
175
}
176
177
listen(_: unknown, event: string, arg?: any): Event<any> {
178
switch (event) {
179
case 'onPong': return this.service.onPong;
180
default: throw new Error('not implemented');
181
}
182
}
183
}
184
185
class TestChannelClient implements ITestService {
186
187
get onPong(): Event<string> {
188
return this.channel.listen('onPong');
189
}
190
191
constructor(private channel: IChannel) { }
192
193
marco(): Promise<string> {
194
return this.channel.call('marco');
195
}
196
197
error(message: string): Promise<void> {
198
return this.channel.call('error', message);
199
}
200
201
neverComplete(): Promise<void> {
202
return this.channel.call('neverComplete');
203
}
204
205
neverCompleteCT(cancellationToken: CancellationToken): Promise<void> {
206
return this.channel.call('neverCompleteCT', undefined, cancellationToken);
207
}
208
209
buffersLength(buffers: VSBuffer[]): Promise<number> {
210
return this.channel.call('buffersLength', buffers);
211
}
212
213
marshall(uri: URI): Promise<URI> {
214
return this.channel.call('marshall', uri);
215
}
216
217
context(): Promise<unknown> {
218
return this.channel.call('context');
219
}
220
}
221
222
suite('Base IPC', function () {
223
224
const store = ensureNoDisposablesAreLeakedInTestSuite();
225
226
test('createProtocolPair', async function () {
227
const [clientProtocol, serverProtocol] = createProtocolPair();
228
229
const b1 = VSBuffer.alloc(0);
230
clientProtocol.send(b1);
231
232
const b3 = VSBuffer.alloc(0);
233
serverProtocol.send(b3);
234
235
const b2 = await Event.toPromise(serverProtocol.onMessage);
236
const b4 = await Event.toPromise(clientProtocol.onMessage);
237
238
assert.strictEqual(b1, b2);
239
assert.strictEqual(b3, b4);
240
});
241
242
suite('one to one', function () {
243
let server: IPCServer;
244
let client: IPCClient;
245
let service: TestService;
246
let ipcService: ITestService;
247
248
setup(function () {
249
service = store.add(new TestService());
250
const testServer = store.add(new TestIPCServer());
251
server = testServer;
252
253
server.registerChannel(TestChannelId, new TestChannel(service));
254
255
client = store.add(testServer.createConnection('client1'));
256
ipcService = new TestChannelClient(client.getChannel(TestChannelId));
257
});
258
259
test('call success', async function () {
260
const r = await ipcService.marco();
261
return assert.strictEqual(r, 'polo');
262
});
263
264
test('call error', async function () {
265
try {
266
await ipcService.error('nice error');
267
return assert.fail('should not reach here');
268
} catch (err) {
269
return assert.strictEqual(err.message, 'nice error');
270
}
271
});
272
273
test('cancel call with cancelled cancellation token', async function () {
274
try {
275
await ipcService.neverCompleteCT(CancellationToken.Cancelled);
276
return assert.fail('should not reach here');
277
} catch (err) {
278
return assert(err.message === 'Canceled');
279
}
280
});
281
282
test('cancel call with cancellation token (sync)', function () {
283
const cts = new CancellationTokenSource();
284
const promise = ipcService.neverCompleteCT(cts.token).then(
285
_ => assert.fail('should not reach here'),
286
err => assert(err.message === 'Canceled')
287
);
288
289
cts.cancel();
290
291
return promise;
292
});
293
294
test('cancel call with cancellation token (async)', function () {
295
const cts = new CancellationTokenSource();
296
const promise = ipcService.neverCompleteCT(cts.token).then(
297
_ => assert.fail('should not reach here'),
298
err => assert(err.message === 'Canceled')
299
);
300
301
setTimeout(() => cts.cancel());
302
303
return promise;
304
});
305
306
test('listen to events', async function () {
307
const messages: string[] = [];
308
309
store.add(ipcService.onPong(msg => messages.push(msg)));
310
await timeout(0);
311
312
assert.deepStrictEqual(messages, []);
313
service.ping('hello');
314
await timeout(0);
315
316
assert.deepStrictEqual(messages, ['hello']);
317
service.ping('world');
318
await timeout(0);
319
320
assert.deepStrictEqual(messages, ['hello', 'world']);
321
});
322
323
test('buffers in arrays', async function () {
324
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
325
return assert.strictEqual(r, 5);
326
});
327
328
test('round trips numbers', () => {
329
const input = [
330
0,
331
1,
332
-1,
333
12345,
334
-12345,
335
42.6,
336
123412341234
337
];
338
339
const writer = new BufferWriter();
340
serialize(writer, input);
341
assert.deepStrictEqual(deserialize(new BufferReader(writer.buffer)), input);
342
});
343
});
344
345
suite('one to one (proxy)', function () {
346
let server: IPCServer;
347
let client: IPCClient;
348
let service: TestService;
349
let ipcService: ITestService;
350
351
const disposables = new DisposableStore();
352
353
setup(function () {
354
service = store.add(new TestService());
355
const testServer = disposables.add(new TestIPCServer());
356
server = testServer;
357
358
server.registerChannel(TestChannelId, ProxyChannel.fromService(service, disposables));
359
360
client = disposables.add(testServer.createConnection('client1'));
361
ipcService = ProxyChannel.toService(client.getChannel(TestChannelId));
362
});
363
364
teardown(function () {
365
disposables.clear();
366
});
367
368
test('call success', async function () {
369
const r = await ipcService.marco();
370
return assert.strictEqual(r, 'polo');
371
});
372
373
test('call error', async function () {
374
try {
375
await ipcService.error('nice error');
376
return assert.fail('should not reach here');
377
} catch (err) {
378
return assert.strictEqual(err.message, 'nice error');
379
}
380
});
381
382
test('listen to events', async function () {
383
const messages: string[] = [];
384
385
disposables.add(ipcService.onPong(msg => messages.push(msg)));
386
await timeout(0);
387
388
assert.deepStrictEqual(messages, []);
389
service.ping('hello');
390
await timeout(0);
391
392
assert.deepStrictEqual(messages, ['hello']);
393
service.ping('world');
394
await timeout(0);
395
396
assert.deepStrictEqual(messages, ['hello', 'world']);
397
});
398
399
test('marshalling uri', async function () {
400
const uri = URI.file('foobar');
401
const r = await ipcService.marshall(uri);
402
assert.ok(r instanceof URI);
403
return assert.ok(isEqual(r, uri));
404
});
405
406
test('buffers in arrays', async function () {
407
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
408
return assert.strictEqual(r, 5);
409
});
410
});
411
412
suite('one to one (proxy, extra context)', function () {
413
let server: IPCServer;
414
let client: IPCClient;
415
let service: TestService;
416
let ipcService: ITestService;
417
418
const disposables = new DisposableStore();
419
420
setup(function () {
421
service = store.add(new TestService());
422
const testServer = disposables.add(new TestIPCServer());
423
server = testServer;
424
425
server.registerChannel(TestChannelId, ProxyChannel.fromService(service, disposables));
426
427
client = disposables.add(testServer.createConnection('client1'));
428
ipcService = ProxyChannel.toService(client.getChannel(TestChannelId), { context: 'Super Context' });
429
});
430
431
teardown(function () {
432
disposables.clear();
433
});
434
435
test('call extra context', async function () {
436
const r = await ipcService.context();
437
return assert.strictEqual(r, 'Super Context');
438
});
439
});
440
441
suite('one to many', function () {
442
test('all clients get pinged', async function () {
443
const service = store.add(new TestService());
444
const channel = new TestChannel(service);
445
const server = store.add(new TestIPCServer());
446
server.registerChannel('channel', channel);
447
448
let client1GotPinged = false;
449
const client1 = store.add(server.createConnection('client1'));
450
const ipcService1 = new TestChannelClient(client1.getChannel('channel'));
451
store.add(ipcService1.onPong(() => client1GotPinged = true));
452
453
let client2GotPinged = false;
454
const client2 = store.add(server.createConnection('client2'));
455
const ipcService2 = new TestChannelClient(client2.getChannel('channel'));
456
store.add(ipcService2.onPong(() => client2GotPinged = true));
457
458
await timeout(1);
459
service.ping('hello');
460
461
await timeout(1);
462
assert(client1GotPinged, 'client 1 got pinged');
463
assert(client2GotPinged, 'client 2 got pinged');
464
});
465
466
test('server gets pings from all clients (broadcast channel)', async function () {
467
const server = store.add(new TestIPCServer());
468
469
const client1 = server.createConnection('client1');
470
const clientService1 = store.add(new TestService());
471
const clientChannel1 = new TestChannel(clientService1);
472
client1.registerChannel('channel', clientChannel1);
473
474
const pings: string[] = [];
475
const channel = server.getChannel('channel', () => true);
476
const service = new TestChannelClient(channel);
477
store.add(service.onPong(msg => pings.push(msg)));
478
479
await timeout(1);
480
clientService1.ping('hello 1');
481
482
await timeout(1);
483
assert.deepStrictEqual(pings, ['hello 1']);
484
485
const client2 = server.createConnection('client2');
486
const clientService2 = store.add(new TestService());
487
const clientChannel2 = new TestChannel(clientService2);
488
client2.registerChannel('channel', clientChannel2);
489
490
await timeout(1);
491
clientService2.ping('hello 2');
492
493
await timeout(1);
494
assert.deepStrictEqual(pings, ['hello 1', 'hello 2']);
495
496
client1.dispose();
497
clientService1.ping('hello 1');
498
499
await timeout(1);
500
assert.deepStrictEqual(pings, ['hello 1', 'hello 2']);
501
502
await timeout(1);
503
clientService2.ping('hello again 2');
504
505
await timeout(1);
506
assert.deepStrictEqual(pings, ['hello 1', 'hello 2', 'hello again 2']);
507
508
client2.dispose();
509
});
510
});
511
});
512
513