Path: blob/main/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Barrier } from '../../../../base/common/async.js';7import { DisposableStore } from '../../../../base/common/lifecycle.js';8import { URI, UriComponents } from '../../../../base/common/uri.js';9import { ExtensionIdentifier } from '../../../../platform/extensions/common/extensions.js';10import { NullLogService } from '../../../../platform/log/common/log.js';11import { ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from '../../common/extHost.protocol.js';12import { ExtHostCommands } from '../../common/extHostCommands.js';13import { ExtHostDocuments } from '../../common/extHostDocuments.js';14import { ExtHostDocumentsAndEditors } from '../../common/extHostDocumentsAndEditors.js';15import { IExtHostInitDataService } from '../../common/extHostInitDataService.js';16import { ExtHostNotebookController } from '../../common/extHostNotebook.js';17import { ExtHostNotebookDocument } from '../../common/extHostNotebookDocument.js';18import { ExtHostNotebookDocuments } from '../../common/extHostNotebookDocuments.js';19import { ExtHostNotebookKernels } from '../../common/extHostNotebookKernels.js';20import { NotebookCellOutput, NotebookCellOutputItem } from '../../common/extHostTypes.js';21import { CellKind, CellUri, NotebookCellsChangeType } from '../../../contrib/notebook/common/notebookCommon.js';22import { CellExecutionUpdateType } from '../../../contrib/notebook/common/notebookExecutionService.js';23import { nullExtensionDescription } from '../../../services/extensions/common/extensions.js';24import { SerializableObjectWithBuffers } from '../../../services/extensions/common/proxyIdentifier.js';25import { TestRPCProtocol } from '../common/testRPCProtocol.js';26import { mock } from '../../../test/common/workbenchTestServices.js';27import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';28import { ExtHostConsumerFileSystem } from '../../common/extHostFileSystemConsumer.js';29import { ExtHostFileSystemInfo } from '../../common/extHostFileSystemInfo.js';30import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';31import { ExtHostSearch } from '../../common/extHostSearch.js';32import { URITransformerService } from '../../common/extHostUriTransformerService.js';3334suite('NotebookKernel', function () {35let rpcProtocol: TestRPCProtocol;36let extHostNotebookKernels: ExtHostNotebookKernels;37let notebook: ExtHostNotebookDocument;38let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;39let extHostDocuments: ExtHostDocuments;40let extHostNotebooks: ExtHostNotebookController;41let extHostNotebookDocuments: ExtHostNotebookDocuments;42let extHostCommands: ExtHostCommands;43let extHostConsumerFileSystem: ExtHostConsumerFileSystem;44let extHostSearch: ExtHostSearch;4546const notebookUri = URI.parse('test:///notebook.file');47const kernelData = new Map<number, INotebookKernelDto2>();48const disposables = new DisposableStore();4950const cellExecuteCreate: { notebook: UriComponents; cell: number }[] = [];51const cellExecuteUpdates: ICellExecuteUpdateDto[] = [];52const cellExecuteComplete: ICellExecutionCompleteDto[] = [];5354teardown(function () {55disposables.clear();56});5758ensureNoDisposablesAreLeakedInTestSuite();5960setup(async function () {61cellExecuteCreate.length = 0;62cellExecuteUpdates.length = 0;63cellExecuteComplete.length = 0;64kernelData.clear();6566rpcProtocol = new TestRPCProtocol();67rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {68override $registerCommand() { }69});70rpcProtocol.set(MainContext.MainThreadNotebookKernels, new class extends mock<MainThreadNotebookKernelsShape>() {71override async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> {72kernelData.set(handle, data);73}74override $removeKernel(handle: number) {75kernelData.delete(handle);76}77override $updateKernel(handle: number, data: Partial<INotebookKernelDto2>) {78assert.strictEqual(kernelData.has(handle), true);79kernelData.set(handle, { ...kernelData.get(handle)!, ...data, });80}81override $createExecution(handle: number, controllerId: string, uri: UriComponents, cellHandle: number): void {82cellExecuteCreate.push({ notebook: uri, cell: cellHandle });83}84override $updateExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {85cellExecuteUpdates.push(...data.value);86}87override $completeExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void {88cellExecuteComplete.push(data.value);89}90});91rpcProtocol.set(MainContext.MainThreadNotebookDocuments, new class extends mock<MainThreadNotebookDocumentsShape>() {9293});94rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {95override async $registerNotebookSerializer() { }96override async $unregisterNotebookSerializer() { }97});98extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());99extHostDocuments = disposables.add(new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));100extHostCommands = new ExtHostCommands(rpcProtocol, new NullLogService(), new class extends mock<IExtHostTelemetry>() {101override onExtensionError(): boolean {102return true;103}104});105extHostConsumerFileSystem = new ExtHostConsumerFileSystem(rpcProtocol, new ExtHostFileSystemInfo());106extHostSearch = new ExtHostSearch(rpcProtocol, new URITransformerService(null), new NullLogService());107extHostNotebooks = new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostConsumerFileSystem, extHostSearch, new NullLogService());108109extHostNotebookDocuments = new ExtHostNotebookDocuments(extHostNotebooks);110111extHostNotebooks.$acceptDocumentAndEditorsDelta(new SerializableObjectWithBuffers({112addedDocuments: [{113uri: notebookUri,114viewType: 'test',115versionId: 0,116cells: [{117handle: 0,118uri: CellUri.generate(notebookUri, 0),119source: ['### Heading'],120eol: '\n',121language: 'markdown',122cellKind: CellKind.Markup,123outputs: [],124}, {125handle: 1,126uri: CellUri.generate(notebookUri, 1),127source: ['console.log("aaa")', 'console.log("bbb")'],128eol: '\n',129language: 'javascript',130cellKind: CellKind.Code,131outputs: [],132}],133}],134addedEditors: [{135documentUri: notebookUri,136id: '_notebook_editor_0',137selections: [{ start: 0, end: 1 }],138visibleRanges: [],139viewType: 'test',140}]141}));142extHostNotebooks.$acceptDocumentAndEditorsDelta(new SerializableObjectWithBuffers({ newActiveEditor: '_notebook_editor_0' }));143144notebook = extHostNotebooks.notebookDocuments[0]!;145146disposables.add(notebook);147disposables.add(extHostDocuments);148149150extHostNotebookKernels = new ExtHostNotebookKernels(151rpcProtocol,152new class extends mock<IExtHostInitDataService>() { },153extHostNotebooks,154extHostCommands,155new NullLogService()156);157});158159test('create/dispose kernel', async function () {160161const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');162163assert.throws(() => (<any>kernel).id = 'dd');164assert.throws(() => (<any>kernel).notebookType = 'dd');165166assert.ok(kernel);167assert.strictEqual(kernel.id, 'foo');168assert.strictEqual(kernel.label, 'Foo');169assert.strictEqual(kernel.notebookType, '*');170171await rpcProtocol.sync();172assert.strictEqual(kernelData.size, 1);173174const [first] = kernelData.values();175assert.strictEqual(first.id, 'nullExtensionDescription/foo');176assert.strictEqual(ExtensionIdentifier.equals(first.extensionId, nullExtensionDescription.identifier), true);177assert.strictEqual(first.label, 'Foo');178assert.strictEqual(first.notebookType, '*');179180kernel.dispose();181await rpcProtocol.sync();182assert.strictEqual(kernelData.size, 0);183});184185test('update kernel', async function () {186187const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));188189await rpcProtocol.sync();190assert.ok(kernel);191192let [first] = kernelData.values();193assert.strictEqual(first.id, 'nullExtensionDescription/foo');194assert.strictEqual(first.label, 'Foo');195196kernel.label = 'Far';197assert.strictEqual(kernel.label, 'Far');198199await rpcProtocol.sync();200[first] = kernelData.values();201assert.strictEqual(first.id, 'nullExtensionDescription/foo');202assert.strictEqual(first.label, 'Far');203});204205test('execute - simple createNotebookCellExecution', function () {206const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));207208extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);209210const cell1 = notebook.apiNotebook.cellAt(0);211const task = kernel.createNotebookCellExecution(cell1);212task.start();213task.end(undefined);214});215216test('createNotebookCellExecution, must be selected/associated', function () {217const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));218assert.throws(() => {219kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));220});221222extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);223const execution = kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));224execution.end(true);225});226227test('createNotebookCellExecution, cell must be alive', function () {228const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));229230const cell1 = notebook.apiNotebook.cellAt(0);231232extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);233extHostNotebookDocuments.$acceptModelChanged(notebook.uri, new SerializableObjectWithBuffers({234versionId: 12,235rawEvents: [{236kind: NotebookCellsChangeType.ModelChange,237changes: [[0, notebook.apiNotebook.cellCount, []]]238}]239}), true);240241assert.strictEqual(cell1.index, -1);242243assert.throws(() => {244kernel.createNotebookCellExecution(cell1);245});246});247248test('interrupt handler, cancellation', async function () {249250let interruptCallCount = 0;251let tokenCancelCount = 0;252253const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));254kernel.interruptHandler = () => { interruptCallCount += 1; };255extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);256257const cell1 = notebook.apiNotebook.cellAt(0);258259const task = kernel.createNotebookCellExecution(cell1);260disposables.add(task.token.onCancellationRequested(() => tokenCancelCount += 1));261262await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);263assert.strictEqual(interruptCallCount, 1);264assert.strictEqual(tokenCancelCount, 0);265266await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);267assert.strictEqual(interruptCallCount, 2);268assert.strictEqual(tokenCancelCount, 0);269270// should cancelling the cells end the execution task?271task.end(false);272});273274test('set outputs on cancel', async function () {275276const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));277extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);278279const cell1 = notebook.apiNotebook.cellAt(0);280const task = kernel.createNotebookCellExecution(cell1);281task.start();282283const b = new Barrier();284285disposables.add(286task.token.onCancellationRequested(async () => {287await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('canceled')]));288task.end(true);289b.open(); // use barrier to signal that cancellation has happened290})291);292293cellExecuteUpdates.length = 0;294await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);295296await b.wait();297298assert.strictEqual(cellExecuteUpdates.length > 0, true);299300let found = false;301for (const edit of cellExecuteUpdates) {302if (edit.editType === CellExecutionUpdateType.Output) {303assert.strictEqual(edit.append, false);304assert.strictEqual(edit.outputs.length, 1);305assert.strictEqual(edit.outputs[0].items.length, 1);306assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('canceled')));307found = true;308}309}310assert.ok(found);311});312313test('set outputs on interrupt', async function () {314315const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');316extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);317318319const cell1 = notebook.apiNotebook.cellAt(0);320const task = kernel.createNotebookCellExecution(cell1);321task.start();322323kernel.interruptHandler = async _notebook => {324assert.ok(notebook.apiNotebook === _notebook);325await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('interrupted')]));326task.end(true);327};328329cellExecuteUpdates.length = 0;330await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);331332assert.strictEqual(cellExecuteUpdates.length > 0, true);333334let found = false;335for (const edit of cellExecuteUpdates) {336if (edit.editType === CellExecutionUpdateType.Output) {337assert.strictEqual(edit.append, false);338assert.strictEqual(edit.outputs.length, 1);339assert.strictEqual(edit.outputs[0].items.length, 1);340assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('interrupted')));341found = true;342}343}344assert.ok(found);345});346});347348349