Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.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 { AsyncIterableObject, DeferredPromise } from '../../../../../base/common/async.js';
8
import { CancellationToken } from '../../../../../base/common/cancellation.js';
9
import { Event } from '../../../../../base/common/event.js';
10
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
11
import { URI } from '../../../../../base/common/uri.js';
12
import { mock } from '../../../../../base/test/common/mock.js';
13
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
14
import { PLAINTEXT_LANGUAGE_ID } from '../../../../../editor/common/languages/modesRegistry.js';
15
import { IMenu, IMenuService } from '../../../../../platform/actions/common/actions.js';
16
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
17
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
18
import { insertCellAtIndex } from '../../browser/controller/cellOperations.js';
19
import { NotebookExecutionService } from '../../browser/services/notebookExecutionServiceImpl.js';
20
import { NotebookExecutionStateService } from '../../browser/services/notebookExecutionStateServiceImpl.js';
21
import { NotebookKernelService } from '../../browser/services/notebookKernelServiceImpl.js';
22
import { NotebookViewModel } from '../../browser/viewModel/notebookViewModelImpl.js';
23
import { NotebookTextModel } from '../../common/model/notebookTextModel.js';
24
import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata, NotebookExecutionState } from '../../common/notebookCommon.js';
25
import { CellExecutionUpdateType, INotebookExecutionService } from '../../common/notebookExecutionService.js';
26
import { INotebookExecutionStateService, NotebookExecutionType } from '../../common/notebookExecutionStateService.js';
27
import { INotebookKernel, INotebookKernelService, VariablesResult } from '../../common/notebookKernelService.js';
28
import { INotebookLoggingService } from '../../common/notebookLoggingService.js';
29
import { INotebookService } from '../../common/notebookService.js';
30
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from './testNotebookEditor.js';
31
32
suite('NotebookExecutionStateService', () => {
33
34
let instantiationService: TestInstantiationService;
35
let kernelService: INotebookKernelService;
36
let disposables: DisposableStore;
37
let testNotebookModel: NotebookTextModel | undefined;
38
39
teardown(() => {
40
disposables.dispose();
41
});
42
43
ensureNoDisposablesAreLeakedInTestSuite();
44
45
setup(function () {
46
47
disposables = new DisposableStore();
48
49
instantiationService = setupInstantiationService(disposables);
50
51
instantiationService.stub(INotebookService, new class extends mock<INotebookService>() {
52
override onDidAddNotebookDocument = Event.None;
53
override onWillRemoveNotebookDocument = Event.None;
54
override getNotebookTextModels() { return []; }
55
override getNotebookTextModel(uri: URI): NotebookTextModel | undefined {
56
return testNotebookModel;
57
}
58
});
59
60
instantiationService.stub(IMenuService, new class extends mock<IMenuService>() {
61
override createMenu() {
62
return new class extends mock<IMenu>() {
63
override onDidChange = Event.None;
64
override getActions() { return []; }
65
override dispose() { }
66
};
67
}
68
});
69
instantiationService.stub(INotebookLoggingService, new class extends mock<INotebookLoggingService>() {
70
override debug(category: string, output: string): void {
71
//
72
}
73
});
74
75
kernelService = disposables.add(instantiationService.createInstance(NotebookKernelService));
76
instantiationService.set(INotebookKernelService, kernelService);
77
instantiationService.set(INotebookExecutionService, disposables.add(instantiationService.createInstance(NotebookExecutionService)));
78
instantiationService.set(INotebookExecutionStateService, disposables.add(instantiationService.createInstance(NotebookExecutionStateService)));
79
});
80
81
async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel, disposables: DisposableStore) => void | Promise<void>) {
82
return _withTestNotebook(cells, (editor, viewModel) => callback(viewModel, viewModel.notebookDocument, disposables));
83
}
84
85
function testCancelOnDelete(expectedCancels: number, implementsInterrupt: boolean) {
86
return withTestNotebook([], async (viewModel, _document, disposables) => {
87
testNotebookModel = viewModel.notebookDocument;
88
89
let cancels = 0;
90
const kernel = new class extends TestNotebookKernel {
91
implementsInterrupt = implementsInterrupt;
92
93
constructor() {
94
super({ languages: ['javascript'] });
95
}
96
97
override async executeNotebookCellsRequest(): Promise<void> { }
98
99
override async cancelNotebookCellExecution(_uri: URI, handles: number[]): Promise<void> {
100
cancels += handles.length;
101
}
102
};
103
disposables.add(kernelService.registerKernel(kernel));
104
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
105
106
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
107
108
// Should cancel executing and pending cells, when kernel does not implement interrupt
109
const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
110
const cell2 = disposables.add(insertCellAtIndex(viewModel, 1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
111
const cell3 = disposables.add(insertCellAtIndex(viewModel, 2, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
112
insertCellAtIndex(viewModel, 3, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true); // Not deleted
113
const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle); // Executing
114
exe.confirm();
115
exe.update([{ editType: CellExecutionUpdateType.ExecutionState, executionOrder: 1 }]);
116
const exe2 = executionStateService.createCellExecution(viewModel.uri, cell2.handle); // Pending
117
exe2.confirm();
118
executionStateService.createCellExecution(viewModel.uri, cell3.handle); // Unconfirmed
119
assert.strictEqual(cancels, 0);
120
viewModel.notebookDocument.applyEdits([{
121
editType: CellEditType.Replace, index: 0, count: 3, cells: []
122
}], true, undefined, () => undefined, undefined, false);
123
assert.strictEqual(cancels, expectedCancels);
124
});
125
126
}
127
128
// TODO@roblou Could be a test just for NotebookExecutionListeners, which can be a standalone contribution
129
test('cancel execution when cell is deleted', async function () {
130
return testCancelOnDelete(3, false);
131
});
132
133
test('cancel execution when cell is deleted in interrupt-type kernel', async function () {
134
return testCancelOnDelete(1, true);
135
});
136
137
test('fires onDidChangeCellExecution when cell is completed while deleted', async function () {
138
return withTestNotebook([], async (viewModel, _document, disposables) => {
139
testNotebookModel = viewModel.notebookDocument;
140
141
const kernel = new TestNotebookKernel();
142
disposables.add(kernelService.registerKernel(kernel));
143
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
144
145
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
146
const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
147
const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle);
148
149
let didFire = false;
150
disposables.add(executionStateService.onDidChangeExecution(e => {
151
if (e.type === NotebookExecutionType.cell) {
152
didFire = !e.changed;
153
}
154
}));
155
156
viewModel.notebookDocument.applyEdits([{
157
editType: CellEditType.Replace, index: 0, count: 1, cells: []
158
}], true, undefined, () => undefined, undefined, false);
159
exe.complete({});
160
assert.strictEqual(didFire, true);
161
});
162
});
163
164
test('does not fire onDidChangeCellExecution for output updates', async function () {
165
return withTestNotebook([], async (viewModel, _document, disposables) => {
166
testNotebookModel = viewModel.notebookDocument;
167
168
const kernel = new TestNotebookKernel();
169
disposables.add(kernelService.registerKernel(kernel));
170
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
171
172
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
173
const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
174
const exe = executionStateService.createCellExecution(viewModel.uri, cell.handle);
175
176
let didFire = false;
177
disposables.add(executionStateService.onDidChangeExecution(e => {
178
if (e.type === NotebookExecutionType.cell) {
179
didFire = true;
180
}
181
}));
182
183
exe.update([{ editType: CellExecutionUpdateType.OutputItems, items: [], outputId: '1' }]);
184
assert.strictEqual(didFire, false);
185
exe.update([{ editType: CellExecutionUpdateType.ExecutionState, executionOrder: 123 }]);
186
assert.strictEqual(didFire, true);
187
exe.complete({});
188
});
189
});
190
191
// #142466
192
test('getCellExecution and onDidChangeCellExecution', async function () {
193
return withTestNotebook([], async (viewModel, _document, disposables) => {
194
testNotebookModel = viewModel.notebookDocument;
195
196
const kernel = new TestNotebookKernel();
197
disposables.add(kernelService.registerKernel(kernel));
198
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
199
200
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
201
const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
202
203
const deferred = new DeferredPromise<void>();
204
disposables.add(executionStateService.onDidChangeExecution(e => {
205
if (e.type === NotebookExecutionType.cell) {
206
const cellUri = CellUri.generate(e.notebook, e.cellHandle);
207
const exe = executionStateService.getCellExecution(cellUri);
208
assert.ok(exe);
209
assert.strictEqual(e.notebook.toString(), exe.notebook.toString());
210
assert.strictEqual(e.cellHandle, exe.cellHandle);
211
212
assert.strictEqual(exe.notebook.toString(), e.changed?.notebook.toString());
213
assert.strictEqual(exe.cellHandle, e.changed?.cellHandle);
214
215
deferred.complete();
216
}
217
}));
218
219
executionStateService.createCellExecution(viewModel.uri, cell.handle);
220
221
return deferred.p;
222
});
223
});
224
test('getExecution and onDidChangeExecution', async function () {
225
return withTestNotebook([], async (viewModel, _document, disposables) => {
226
testNotebookModel = viewModel.notebookDocument;
227
228
const kernel = new TestNotebookKernel();
229
disposables.add(kernelService.registerKernel(kernel));
230
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
231
232
const eventRaisedWithExecution: boolean[] = [];
233
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
234
executionStateService.onDidChangeExecution(e => eventRaisedWithExecution.push(e.type === NotebookExecutionType.notebook && !!e.changed), this, disposables);
235
236
const deferred = new DeferredPromise<void>();
237
disposables.add(executionStateService.onDidChangeExecution(e => {
238
if (e.type === NotebookExecutionType.notebook) {
239
const exe = executionStateService.getExecution(viewModel.uri);
240
assert.ok(exe);
241
assert.strictEqual(e.notebook.toString(), exe.notebook.toString());
242
assert.ok(e.affectsNotebook(viewModel.uri));
243
assert.deepStrictEqual(eventRaisedWithExecution, [true]);
244
deferred.complete();
245
}
246
}));
247
248
executionStateService.createExecution(viewModel.uri);
249
250
return deferred.p;
251
});
252
});
253
254
test('getExecution and onDidChangeExecution 2', async function () {
255
return withTestNotebook([], async (viewModel, _document, disposables) => {
256
testNotebookModel = viewModel.notebookDocument;
257
258
const kernel = new TestNotebookKernel();
259
disposables.add(kernelService.registerKernel(kernel));
260
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
261
262
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
263
264
const deferred = new DeferredPromise<void>();
265
const expectedNotebookEventStates: (NotebookExecutionState | undefined)[] = [NotebookExecutionState.Unconfirmed, NotebookExecutionState.Pending, NotebookExecutionState.Executing, undefined];
266
executionStateService.onDidChangeExecution(e => {
267
if (e.type === NotebookExecutionType.notebook) {
268
const expectedState = expectedNotebookEventStates.shift();
269
if (typeof expectedState === 'number') {
270
const exe = executionStateService.getExecution(viewModel.uri);
271
assert.ok(exe);
272
assert.strictEqual(e.notebook.toString(), exe.notebook.toString());
273
assert.strictEqual(e.changed?.state, expectedState);
274
} else {
275
assert.ok(e.changed === undefined);
276
}
277
278
assert.ok(e.affectsNotebook(viewModel.uri));
279
if (expectedNotebookEventStates.length === 0) {
280
deferred.complete();
281
}
282
}
283
}, this, disposables);
284
285
const execution = executionStateService.createExecution(viewModel.uri);
286
execution.confirm();
287
execution.begin();
288
execution.complete();
289
290
return deferred.p;
291
});
292
});
293
294
test('force-cancel works for Cell Execution', async function () {
295
return withTestNotebook([], async (viewModel, _document, disposables) => {
296
testNotebookModel = viewModel.notebookDocument;
297
298
const kernel = new TestNotebookKernel();
299
disposables.add(kernelService.registerKernel(kernel));
300
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
301
302
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
303
const cell = disposables.add(insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true));
304
executionStateService.createCellExecution(viewModel.uri, cell.handle);
305
const exe = executionStateService.getCellExecution(cell.uri);
306
assert.ok(exe);
307
308
executionStateService.forceCancelNotebookExecutions(viewModel.uri);
309
const exe2 = executionStateService.getCellExecution(cell.uri);
310
assert.strictEqual(exe2, undefined);
311
});
312
});
313
test('force-cancel works for Notebook Execution', async function () {
314
return withTestNotebook([], async (viewModel, _document, disposables) => {
315
testNotebookModel = viewModel.notebookDocument;
316
317
const kernel = new TestNotebookKernel();
318
disposables.add(kernelService.registerKernel(kernel));
319
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
320
const eventRaisedWithExecution: boolean[] = [];
321
322
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
323
executionStateService.onDidChangeExecution(e => eventRaisedWithExecution.push(e.type === NotebookExecutionType.notebook && !!e.changed), this, disposables);
324
executionStateService.createExecution(viewModel.uri);
325
const exe = executionStateService.getExecution(viewModel.uri);
326
assert.ok(exe);
327
assert.deepStrictEqual(eventRaisedWithExecution, [true]);
328
329
executionStateService.forceCancelNotebookExecutions(viewModel.uri);
330
const exe2 = executionStateService.getExecution(viewModel.uri);
331
assert.deepStrictEqual(eventRaisedWithExecution, [true, false]);
332
assert.strictEqual(exe2, undefined);
333
});
334
});
335
test('force-cancel works for Cell and Notebook Execution', async function () {
336
return withTestNotebook([], async (viewModel, _document, disposables) => {
337
testNotebookModel = viewModel.notebookDocument;
338
339
const kernel = new TestNotebookKernel();
340
disposables.add(kernelService.registerKernel(kernel));
341
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
342
343
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
344
executionStateService.createExecution(viewModel.uri);
345
executionStateService.createExecution(viewModel.uri);
346
const cellExe = executionStateService.getExecution(viewModel.uri);
347
const exe = executionStateService.getExecution(viewModel.uri);
348
assert.ok(cellExe);
349
assert.ok(exe);
350
351
executionStateService.forceCancelNotebookExecutions(viewModel.uri);
352
const cellExe2 = executionStateService.getExecution(viewModel.uri);
353
const exe2 = executionStateService.getExecution(viewModel.uri);
354
assert.strictEqual(cellExe2, undefined);
355
assert.strictEqual(exe2, undefined);
356
});
357
});
358
});
359
360
class TestNotebookKernel implements INotebookKernel {
361
id: string = 'test';
362
label: string = '';
363
viewType = '*';
364
onDidChange = Event.None;
365
extension: ExtensionIdentifier = new ExtensionIdentifier('test');
366
localResourceRoot: URI = URI.file('/test');
367
description?: string | undefined;
368
detail?: string | undefined;
369
preloadUris: URI[] = [];
370
preloadProvides: string[] = [];
371
supportedLanguages: string[] = [];
372
async executeNotebookCellsRequest(): Promise<void> { }
373
async cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void> { }
374
provideVariables(notebookUri: URI, parentId: number | undefined, kind: 'named' | 'indexed', start: number, token: CancellationToken): AsyncIterableObject<VariablesResult> {
375
return AsyncIterableObject.EMPTY;
376
}
377
378
constructor(opts?: { languages?: string[]; id?: string }) {
379
this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID];
380
if (opts?.id) {
381
this.id = opts?.id;
382
}
383
}
384
}
385
386