Path: blob/main/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
5222 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');162163// eslint-disable-next-line local/code-no-any-casts164assert.throws(() => (<any>kernel).id = 'dd');165// eslint-disable-next-line local/code-no-any-casts166assert.throws(() => (<any>kernel).notebookType = 'dd');167168assert.ok(kernel);169assert.strictEqual(kernel.id, 'foo');170assert.strictEqual(kernel.label, 'Foo');171assert.strictEqual(kernel.notebookType, '*');172173await rpcProtocol.sync();174assert.strictEqual(kernelData.size, 1);175176const [first] = kernelData.values();177assert.strictEqual(first.id, 'nullExtensionDescription/foo');178assert.strictEqual(ExtensionIdentifier.equals(first.extensionId, nullExtensionDescription.identifier), true);179assert.strictEqual(first.label, 'Foo');180assert.strictEqual(first.notebookType, '*');181182kernel.dispose();183await rpcProtocol.sync();184assert.strictEqual(kernelData.size, 0);185});186187test('update kernel', async function () {188189const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));190191await rpcProtocol.sync();192assert.ok(kernel);193194let [first] = kernelData.values();195assert.strictEqual(first.id, 'nullExtensionDescription/foo');196assert.strictEqual(first.label, 'Foo');197198kernel.label = 'Far';199assert.strictEqual(kernel.label, 'Far');200201await rpcProtocol.sync();202[first] = kernelData.values();203assert.strictEqual(first.id, 'nullExtensionDescription/foo');204assert.strictEqual(first.label, 'Far');205});206207test('execute - simple createNotebookCellExecution', function () {208const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));209210extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);211212const cell1 = notebook.apiNotebook.cellAt(0);213const task = kernel.createNotebookCellExecution(cell1);214task.start();215task.end(undefined);216});217218test('createNotebookCellExecution, must be selected/associated', function () {219const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));220assert.throws(() => {221kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));222});223224extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);225const execution = kernel.createNotebookCellExecution(notebook.apiNotebook.cellAt(0));226execution.end(true);227});228229test('createNotebookCellExecution, cell must be alive', function () {230const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));231232const cell1 = notebook.apiNotebook.cellAt(0);233234extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);235extHostNotebookDocuments.$acceptModelChanged(notebook.uri, new SerializableObjectWithBuffers({236versionId: 12,237rawEvents: [{238kind: NotebookCellsChangeType.ModelChange,239changes: [[0, notebook.apiNotebook.cellCount, []]]240}]241}), true);242243assert.strictEqual(cell1.index, -1);244245assert.throws(() => {246kernel.createNotebookCellExecution(cell1);247});248});249250test('interrupt handler, cancellation', async function () {251252let interruptCallCount = 0;253let tokenCancelCount = 0;254255const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));256kernel.interruptHandler = () => { interruptCallCount += 1; };257extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);258259const cell1 = notebook.apiNotebook.cellAt(0);260261const task = kernel.createNotebookCellExecution(cell1);262disposables.add(task.token.onCancellationRequested(() => tokenCancelCount += 1));263264await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);265assert.strictEqual(interruptCallCount, 1);266assert.strictEqual(tokenCancelCount, 0);267268await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);269assert.strictEqual(interruptCallCount, 2);270assert.strictEqual(tokenCancelCount, 0);271272// should cancelling the cells end the execution task?273task.end(false);274});275276test('set outputs on cancel', async function () {277278const kernel = disposables.add(extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'));279extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);280281const cell1 = notebook.apiNotebook.cellAt(0);282const task = kernel.createNotebookCellExecution(cell1);283task.start();284285const b = new Barrier();286287disposables.add(288task.token.onCancellationRequested(async () => {289await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('canceled')]));290task.end(true);291b.open(); // use barrier to signal that cancellation has happened292})293);294295cellExecuteUpdates.length = 0;296await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);297298await b.wait();299300assert.strictEqual(cellExecuteUpdates.length > 0, true);301302let found = false;303for (const edit of cellExecuteUpdates) {304if (edit.editType === CellExecutionUpdateType.Output) {305assert.strictEqual(edit.append, false);306assert.strictEqual(edit.outputs.length, 1);307assert.strictEqual(edit.outputs[0].items.length, 1);308assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('canceled')));309found = true;310}311}312assert.ok(found);313});314315test('set outputs on interrupt', async function () {316317const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo');318extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true);319320321const cell1 = notebook.apiNotebook.cellAt(0);322const task = kernel.createNotebookCellExecution(cell1);323task.start();324325kernel.interruptHandler = async _notebook => {326assert.ok(notebook.apiNotebook === _notebook);327await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('interrupted')]));328task.end(true);329};330331cellExecuteUpdates.length = 0;332await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]);333334assert.strictEqual(cellExecuteUpdates.length > 0, true);335336let found = false;337for (const edit of cellExecuteUpdates) {338if (edit.editType === CellExecutionUpdateType.Output) {339assert.strictEqual(edit.append, false);340assert.strictEqual(edit.outputs.length, 1);341assert.strictEqual(edit.outputs[0].items.length, 1);342assert.deepStrictEqual(Array.from(edit.outputs[0].items[0].valueBytes.buffer), Array.from(new TextEncoder().encode('interrupted')));343found = true;344}345}346assert.ok(found);347});348});349350351