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
3296 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
assert.throws(() => (<any>kernel).id = 'dd');
165
assert.throws(() => (<any>kernel).notebookType = 'dd');
166
167
assert.ok(kernel);
168
assert.strictEqual(kernel.id, 'foo');
169
assert.strictEqual(kernel.label, 'Foo');
170
assert.strictEqual(kernel.notebookType, '*');
171
172
await rpcProtocol.sync();
173
assert.strictEqual(kernelData.size, 1);
174
175
const [first] = kernelData.values();
176
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
177
assert.strictEqual(ExtensionIdentifier.equals(first.extensionId, nullExtensionDescription.identifier), true);
178
assert.strictEqual(first.label, 'Foo');
179
assert.strictEqual(first.notebookType, '*');
180
181
kernel.dispose();
182
await rpcProtocol.sync();
183
assert.strictEqual(kernelData.size, 0);
184
});
185
186
test('update kernel', async function () {
187
188
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
189
190
await rpcProtocol.sync();
191
assert.ok(kernel);
192
193
let [first] = kernelData.values();
194
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
195
assert.strictEqual(first.label, 'Foo');
196
197
kernel.label = 'Far';
198
assert.strictEqual(kernel.label, 'Far');
199
200
await rpcProtocol.sync();
201
[first] = kernelData.values();
202
assert.strictEqual(first.id, 'nullExtensionDescription/foo');
203
assert.strictEqual(first.label, 'Far');
204
});
205
206
test('execute - simple createNotebookCellExecution', function () {
207
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
208
209
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
210
211
const cell1 = notebook.apiNotebook.cellAt(0);
212
const task = kernel.createNotebookCellExecution(cell1);
213
task.start();
214
task.end(undefined);
215
});
216
217
test('createNotebookCellExecution, must be selected/associated', function () {
218
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
219
assert.throws(() => {
220
kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));
221
});
222
223
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
224
const execution = kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));
225
execution.end(true);
226
});
227
228
test('createNotebookCellExecution, cell must be alive', function () {
229
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
230
231
const cell1 = notebook.apiNotebook.cellAt(0);
232
233
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
234
extHostNotebookDocuments.$acceptModelChanged(notebook.uri, new SerializableObjectWithBuffers({
235
versionId: 12,
236
rawEvents: [{
237
kind: NotebookCellsChangeType.ModelChange,
238
changes: [[0, notebook.apiNotebook.cellCount, []]]
239
}]
240
}), true);
241
242
assert.strictEqual(cell1.index, -1);
243
244
assert.throws(() => {
245
kernel.createNotebookCellExecution(cell1);
246
});
247
});
248
249
test('interrupt handler, cancellation', async function () {
250
251
let interruptCallCount = 0;
252
let tokenCancelCount = 0;
253
254
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
255
kernel.interruptHandler = () => { interruptCallCount += 1; };
256
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
257
258
const cell1 = notebook.apiNotebook.cellAt(0);
259
260
const task = kernel.createNotebookCellExecution(cell1);
261
disposables.add(task.token.onCancellationRequested(() => tokenCancelCount += 1));
262
263
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
264
assert.strictEqual(interruptCallCount, 1);
265
assert.strictEqual(tokenCancelCount, 0);
266
267
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
268
assert.strictEqual(interruptCallCount, 2);
269
assert.strictEqual(tokenCancelCount, 0);
270
271
// should cancelling the cells end the execution task?
272
task.end(false);
273
});
274
275
test('set outputs on cancel', async function () {
276
277
const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));
278
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
279
280
const cell1 = notebook.apiNotebook.cellAt(0);
281
const task = kernel.createNotebookCellExecution(cell1);
282
task.start();
283
284
const b = new Barrier();
285
286
disposables.add(
287
task.token.onCancellationRequested(async () => {
288
await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('canceled')]));
289
task.end(true);
290
b.open(); // use barrier to signal that cancellation has happened
291
})
292
);
293
294
cellExecuteUpdates.length = 0;
295
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
296
297
await b.wait();
298
299
assert.strictEqual(cellExecuteUpdates.length > 0, true);
300
301
let found = false;
302
for (const edit of cellExecuteUpdates) {
303
if (edit.editType === CellExecutionUpdateType.Output) {
304
assert.strictEqual(edit.append, false);
305
assert.strictEqual(edit.outputs.length, 1);
306
assert.strictEqual(edit.outputs[0].items.length, 1);
307
assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('canceled')));
308
found = true;
309
}
310
}
311
assert.ok(found);
312
});
313
314
test('set outputs on interrupt', async function () {
315
316
const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');
317
extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);
318
319
320
const cell1 = notebook.apiNotebook.cellAt(0);
321
const task = kernel.createNotebookCellExecution(cell1);
322
task.start();
323
324
kernel.interruptHandler = async _notebook => {
325
assert.ok(notebook.apiNotebook === _notebook);
326
await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('interrupted')]));
327
task.end(true);
328
};
329
330
cellExecuteUpdates.length = 0;
331
await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);
332
333
assert.strictEqual(cellExecuteUpdates.length > 0, true);
334
335
let found = false;
336
for (const edit of cellExecuteUpdates) {
337
if (edit.editType === CellExecutionUpdateType.Output) {
338
assert.strictEqual(edit.append, false);
339
assert.strictEqual(edit.outputs.length, 1);
340
assert.strictEqual(edit.outputs[0].items.length, 1);
341
assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('interrupted')));
342
found = true;
343
}
344
}
345
assert.ok(found);
346
});
347
});
348
349