Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
5222 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 { Barrier } from '../../../../base/common/async.js';
8
import { DisposableStore } from '../../../../base/common/lifecycle.js';
9
import { URI, UriComponents } from '../../../../base/common/uri.js';
10
import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';
11
import { NullLogService } from '../../../../platform/log/common/log.js';
12
import { ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from '../../common/extHost.protocol.js';
13
import { ExtHostCommands } from '../../common/extHostCommands.js';
14
import { ExtHostDocuments } from '../../common/extHostDocuments.js';
15
import { ExtHostDocumentsAndEditors } from '../../common/extHostDocumentsAndEditors.js';
16
import { IExtHostInitDataService } from '../../common/extHostInitDataService.js';
17
import { ExtHostNotebookController } from '../../common/extHostNotebook.js';
18
import { ExtHostNotebookDocument } from '../../common/extHostNotebookDocument.js';
19
import { ExtHostNotebookDocuments } from '../../common/extHostNotebookDocuments.js';
20
import { ExtHostNotebookKernels } from '../../common/extHostNotebookKernels.js';
21
import { NotebookCellOutput, NotebookCellOutputItem } from '../../common/extHostTypes.js';
22
import { CellKind, CellUri, NotebookCellsChangeType } from '../../../contrib/notebook/common/notebookCommon.js';
23
import { CellExecutionUpdateType } from '../../../contrib/notebook/common/notebookExecutionService.js';
24
import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';
25
import { SerializableObjectWithBuffers } from '../../../services/extensions/common/proxyIdentifier.js';
26
import { TestRPCProtocol } from '../common/testRPCProtocol.js';
27
import { mock } from '../../../test/common/workbenchTestServices.js';
28
import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';
29
import { ExtHostConsumerFileSystem } from '../../common/extHostFileSystemConsumer.js';
30
import { ExtHostFileSystemInfo } from '../../common/extHostFileSystemInfo.js';
31
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
32
import { ExtHostSearch } from '../../common/extHostSearch.js';
33
import { URITransformerService } from '../../common/extHostUriTransformerService.js';
34
35
suite('NotebookKernel', function () {
36
let rpcProtocol: TestRPCProtocol;
37
let extHostNotebookKernels: ExtHostNotebookKernels;
38
let notebook: ExtHostNotebookDocument;
39
let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
40
let extHostDocuments: ExtHostDocuments;
41
let extHostNotebooks: ExtHostNotebookController;
42
let extHostNotebookDocuments: ExtHostNotebookDocuments;
43
let extHostCommands: ExtHostCommands;
44
let extHostConsumerFileSystem: ExtHostConsumerFileSystem;
45
let extHostSearch: ExtHostSearch;
46
47
const notebookUri = URI.parse('test:///notebook.file');
48
const kernelData = new Map<number, INotebookKernelDto2>();
49
const disposables = new DisposableStore();
50
51
const cellExecuteCreate: { notebook: UriComponents; cell: number }[] = [];
52
const cellExecuteUpdates: ICellExecuteUpdateDto[] = [];
53
const cellExecuteComplete: ICellExecutionCompleteDto[] = [];
54
55
teardown(function () {
56
disposables.clear();
57
});
58
59
ensureNoDisposablesAreLeakedInTestSuite();
60
61
setup(async function () {
62
cellExecuteCreate.length = 0;
63
cellExecuteUpdates.length = 0;
64
cellExecuteComplete.length = 0;
65
kernelData.clear();
66
67
rpcProtocol = new TestRPCProtocol();
68
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
69
override $registerCommand() { }
70
});
71
rpcProtocol.set(MainContext.MainThreadNotebookKernels, new class extends mock<MainThreadNotebookKernelsShape>() {
72
override async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> {
73
kernelData.set(handle, data);
74
}
75
override $removeKernel(handle: number) {
76
kernelData.delete(handle);
77
}
78
override $updateKernel(handle: number, data: Partial<INotebookKernelDto2>) {
79
assert.strictEqual(kernelData.has(handle), true);
80
kernelData.set(handle, { ...kernelData.get(handle)!, ...data, });
81
}
82
override $createExecution(handle: number, controllerId: string, uri: UriComponents, cellHandle: number): void {
83
cellExecuteCreate.push({ notebook: uri, cell: cellHandle });
84
}
85
override $updateExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {
86
cellExecuteUpdates.push(...data.value);
87
}
88
override $completeExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void {
89
cellExecuteComplete.push(data.value);
90
}
91
});
92
rpcProtocol.set(MainContext.MainThreadNotebookDocuments, new class extends mock<MainThreadNotebookDocumentsShape>() {
93
94
});
95
rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {
96
override async $registerNotebookSerializer() { }
97
override async $unregisterNotebookSerializer() { }
98
});
99
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
100
extHostDocuments = disposables.add(new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
101
extHostCommands = new ExtHostCommands(rpcProtocol, new NullLogService(), new class extends mock<IExtHostTelemetry>() {
102
override onExtensionError(): boolean {
103
return true;
104
}
105
});
106
extHostConsumerFileSystem = new ExtHostConsumerFileSystem(rpcProtocol, new ExtHostFileSystemInfo());
107
extHostSearch = new ExtHostSearch(rpcProtocol, new URITransformerService(null), new NullLogService());
108
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostConsumerFileSystem, extHostSearch, new NullLogService());
109
110
extHostNotebookDocuments = new ExtHostNotebookDocuments(extHostNotebooks);
111
112
extHostNotebooks.$acceptDocumentAndEditorsDelta(new SerializableObjectWithBuffers({
113
addedDocuments: [{
114
uri: notebookUri,
115
viewType: 'test',
116
versionId: 0,
117
cells: [{
118
handle: 0,
119
uri: CellUri.generate(notebookUri, 0),
120
source: ['### Heading'],
121
eol: '\n',
122
language: 'markdown',
123
cellKind: CellKind.Markup,
124
outputs: [],
125
}, {
126
handle: 1,
127
uri: CellUri.generate(notebookUri, 1),
128
source: ['console.log("aaa")', 'console.log("bbb")'],
129
eol: '\n',
130
language: 'javascript',
131
cellKind: CellKind.Code,
132
outputs: [],
133
}],
134
}],
135
addedEditors: [{
136
documentUri: notebookUri,
137
id: '_notebook_editor_0',
138
selections: [{ start: 0, end: 1 }],
139
visibleRanges: [],
140
viewType: 'test',
141
}]
142
}));
143
extHostNotebooks.$acceptDocumentAndEditorsDelta(new SerializableObjectWithBuffers({ newActiveEditor: '_notebook_editor_0' }));
144
145
notebook = extHostNotebooks.notebookDocuments[0]!;
146
147
disposables.add(notebook);
148
disposables.add(extHostDocuments);
149
150
151
extHostNotebookKernels = new ExtHostNotebookKernels(
152
rpcProtocol,
153
new class extends mock<IExtHostInitDataService>() { },
154
extHostNotebooks,
155
extHostCommands,
156
new NullLogService()
157
);
158
});
159
160
test('create/dispose kernel', async function () {
161
162
const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');
163
164
// eslint-disable-next-line local/code-no-any-casts
165
assert.throws(() => (<any>kernel).id = 'dd');
166
// eslint-disable-next-line local/code-no-any-casts
167
assert.throws(() => (<any>kernel).notebookType = 'dd');
168
169
assert.ok(kernel);
170
assert.strictEqual(kernel.id, 'foo');
171
assert.strictEqual(kernel.label, 'Foo');
172
assert.strictEqual(kernel.notebookType, '*');
173
174
await rpcProtocol.sync();
175
assert.strictEqual(kernelData.size, 1);
176
177
const [first] = kernelData.values();
178
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
179
assert.strictEqual(ExtensionIdentifier.equals(first.extensionId, nullExtensionDescription.identifier), true);
180
assert.strictEqual(first.label, 'Foo');
181
assert.strictEqual(first.notebookType, '*');
182
183
kernel.dispose();
184
await rpcProtocol.sync();
185
assert.strictEqual(kernelData.size, 0);
186
});
187
188
test('update kernel', async function () {
189
190
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
191
192
await rpcProtocol.sync();
193
assert.ok(kernel);
194
195
let [first] = kernelData.values();
196
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
197
assert.strictEqual(first.label, 'Foo');
198
199
kernel.label = 'Far';
200
assert.strictEqual(kernel.label, 'Far');
201
202
await rpcProtocol.sync();
203
[first] = kernelData.values();
204
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
205
assert.strictEqual(first.label, 'Far');
206
});
207
208
test('execute - simple createNotebookCellExecution', function () {
209
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
210
211
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
212
213
const cell1 = notebook.apiNotebook.cellAt(0);
214
const task = kernel.createNotebookCellExecution(cell1);
215
task.start();
216
task.end(undefined);
217
});
218
219
test('createNotebookCellExecution, must be selected/associated', function () {
220
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
221
assert.throws(() => {
222
kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));
223
});
224
225
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
226
const execution = kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));
227
execution.end(true);
228
});
229
230
test('createNotebookCellExecution, cell must be alive', function () {
231
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
232
233
const cell1 = notebook.apiNotebook.cellAt(0);
234
235
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
236
extHostNotebookDocuments.$acceptModelChanged(notebook.uri, new SerializableObjectWithBuffers({
237
versionId: 12,
238
rawEvents: [{
239
kind: NotebookCellsChangeType.ModelChange,
240
changes: [[0, notebook.apiNotebook.cellCount, []]]
241
}]
242
}), true);
243
244
assert.strictEqual(cell1.index, -1);
245
246
assert.throws(() => {
247
kernel.createNotebookCellExecution(cell1);
248
});
249
});
250
251
test('interrupt handler, cancellation', async function () {
252
253
let interruptCallCount = 0;
254
let tokenCancelCount = 0;
255
256
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
257
kernel.interruptHandler = () => { interruptCallCount += 1; };
258
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
259
260
const cell1 = notebook.apiNotebook.cellAt(0);
261
262
const task = kernel.createNotebookCellExecution(cell1);
263
disposables.add(task.token.onCancellationRequested(() => tokenCancelCount += 1));
264
265
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
266
assert.strictEqual(interruptCallCount, 1);
267
assert.strictEqual(tokenCancelCount, 0);
268
269
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
270
assert.strictEqual(interruptCallCount, 2);
271
assert.strictEqual(tokenCancelCount, 0);
272
273
// should cancelling the cells end the execution task?
274
task.end(false);
275
});
276
277
test('set outputs on cancel', async function () {
278
279
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
280
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
281
282
const cell1 = notebook.apiNotebook.cellAt(0);
283
const task = kernel.createNotebookCellExecution(cell1);
284
task.start();
285
286
const b = new Barrier();
287
288
disposables.add(
289
task.token.onCancellationRequested(async () => {
290
await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('canceled')]));
291
task.end(true);
292
b.open(); // use barrier to signal that cancellation has happened
293
})
294
);
295
296
cellExecuteUpdates.length = 0;
297
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
298
299
await b.wait();
300
301
assert.strictEqual(cellExecuteUpdates.length > 0, true);
302
303
let found = false;
304
for (const edit of cellExecuteUpdates) {
305
if (edit.editType === CellExecutionUpdateType.Output) {
306
assert.strictEqual(edit.append, false);
307
assert.strictEqual(edit.outputs.length, 1);
308
assert.strictEqual(edit.outputs[0].items.length, 1);
309
assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('canceled')));
310
found = true;
311
}
312
}
313
assert.ok(found);
314
});
315
316
test('set outputs on interrupt', async function () {
317
318
const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');
319
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
320
321
322
const cell1 = notebook.apiNotebook.cellAt(0);
323
const task = kernel.createNotebookCellExecution(cell1);
324
task.start();
325
326
kernel.interruptHandler = async _notebook => {
327
assert.ok(notebook.apiNotebook === _notebook);
328
await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('interrupted')]));
329
task.end(true);
330
};
331
332
cellExecuteUpdates.length = 0;
333
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
334
335
assert.strictEqual(cellExecuteUpdates.length > 0, true);
336
337
let found = false;
338
for (const edit of cellExecuteUpdates) {
339
if (edit.editType === CellExecutionUpdateType.Output) {
340
assert.strictEqual(edit.append, false);
341
assert.strictEqual(edit.outputs.length, 1);
342
assert.strictEqual(edit.outputs[0].items.length, 1);
343
assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('interrupted')));
344
found = true;
345
}
346
}
347
assert.ok(found);
348
});
349
});
350
351