diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 2ea186b0211..d767c4b3796 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -566,90 +566,6 @@ suite('Notebook API tests', function () { }); }); - test('set outputs on cancel', async () => { - - const cancelableKernel = new class extends Kernel { - - constructor() { - super('cancelableKernel', 'Notebook Cancelable Test Kernel'); - } - - override async _execute(cells: vscode.NotebookCell[]) { - for (const cell of cells) { - const task = this.controller.createNotebookCellExecution(cell); - task.start(); - task.token.onCancellationRequested(async () => { - await task.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('Canceled', 'text/plain') - ])]); - task.end(undefined); - }); - - } - } - }; - - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - const cell = editor.document.cellAt(0); - - await withEvent(vscode.notebooks.onDidChangeCellOutputs, async (event) => { - await assertKernel(cancelableKernel, notebook); - assert.ok(editor === vscode.window.activeNotebookEditor); - await vscode.commands.executeCommand('notebook.cell.execute'); - await vscode.commands.executeCommand('notebook.cell.cancelExecution'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(cell.outputs[0].items.length, 1); - assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); - assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'Canceled'); - }); - - cancelableKernel.controller.dispose(); - }); - - test('set outputs on interrupt', async () => { - const interruptableKernel = new class extends Kernel { - - constructor() { - super('interruptableKernel', 'Notebook Interruptable Test Kernel'); - this.controller.interruptHandler = this.interrupt.bind(this); - } - - private _task: vscode.NotebookCellExecution | undefined; - - override async _execute(cells: vscode.NotebookCell[]) { - this._task = this.controller.createNotebookCellExecution(cells[0]); - this._task.start(); - } - - async interrupt() { - await this._task!.replaceOutput([new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('Interrupted', 'text/plain') - ])]); - this._task!.end(undefined); - } - }; - - const notebook = await openRandomNotebookDocument(); - const editor = await vscode.window.showNotebookDocument(notebook); - const cell = editor.document.cellAt(0); - - await withEvent(vscode.notebooks.onDidChangeCellOutputs, async (event) => { - await assertKernel(interruptableKernel, notebook); - assert.ok(editor === vscode.window.activeNotebookEditor); - await vscode.commands.executeCommand('notebook.cell.execute'); - await vscode.commands.executeCommand('notebook.cell.cancelExecution'); - await event; - assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(cell.outputs[0].items.length, 1); - assert.strictEqual(cell.outputs[0].items[0].mime, 'text/plain'); - assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].items[0].data), 'Interrupted'); - }); - - interruptableKernel.controller.dispose(); - }); - test('onDidChangeCellExecutionState is fired', async () => { const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts index a82287eac4b..b7bd986babf 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookKernel2.test.ts @@ -8,7 +8,7 @@ import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { mock } from 'vs/workbench/test/common/workbenchTestServices'; -import { INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; @@ -17,12 +17,14 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocum import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, IImmediateCellEditOperation, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; +import { NotebookCellOutput, NotebookCellOutputItem } from 'vs/workbench/api/common/extHostTypes'; +import { timeout } from 'vs/base/common/async'; suite('NotebookKernel', function () { @@ -38,11 +40,13 @@ suite('NotebookKernel', function () { const kernelData = new Map(); const disposables = new DisposableStore(); + const edits: IImmediateCellEditOperation[] = []; + teardown(function () { disposables.clear(); }); setup(async function () { - + edits.length = 0; kernelData.clear(); rpcProtocol = new TestRPCProtocol(); @@ -61,7 +65,11 @@ suite('NotebookKernel', function () { kernelData.set(handle, { ...kernelData.get(handle)!, ...data, }); } }); - + rpcProtocol.set(MainContext.MainThreadNotebookDocuments, new class extends mock() { + override async $applyEdits(resource: UriComponents, _edits: IImmediateCellEditOperation[]) { + edits.push(..._edits); + } + }); rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock() { override async $registerNotebookProvider() { } override async $unregisterNotebookProvider() { } @@ -233,4 +241,72 @@ suite('NotebookKernel', function () { assert.strictEqual(interruptCallCount, 2); assert.strictEqual(tokenCancelCount, 0); }); + + test('set outputs on cancel', async function () { + + const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'); + extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true); + + const cell1 = notebook.apiNotebook.cellAt(0); + const task = kernel.createNotebookCellExecution(cell1); + task.start(); + + task.token.onCancellationRequested(async () => { + await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('canceled')])); + task.end(true); + }); + + edits.length = 0; + await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]); + + await timeout(50); + + assert.strictEqual(edits.length > 0, true); + + let found = false; + for (let edit of edits) { + if (edit.editType === CellEditType.Output) { + assert.strictEqual(edit.append, false); + assert.strictEqual(edit.outputs.length, 1); + assert.strictEqual(edit.outputs[0].outputs.length, 1); + assert.deepStrictEqual(edit.outputs[0].outputs[0].valueBytes, Array.from(new TextEncoder().encode('canceled'))); + found = true; + } + } + assert.ok(found); + }); + + test('set outputs on interrupt', async function () { + + const kernel = extHostNotebookKernels.createNotebookController(nullExtensionDescription, 'foo', '*', 'Foo'); + extHostNotebookKernels.$acceptNotebookAssociation(0, notebook.uri, true); + + + const cell1 = notebook.apiNotebook.cellAt(0); + const task = kernel.createNotebookCellExecution(cell1); + task.start(); + + kernel.interruptHandler = async _notebook => { + assert.ok(notebook.apiNotebook === _notebook); + await task.replaceOutput(new NotebookCellOutput([NotebookCellOutputItem.text('interrupted')])); + task.end(true); + }; + + edits.length = 0; + await extHostNotebookKernels.$cancelCells(0, notebook.uri, [0]); + + assert.strictEqual(edits.length > 0, true); + + let found = false; + for (let edit of edits) { + if (edit.editType === CellEditType.Output) { + assert.strictEqual(edit.append, false); + assert.strictEqual(edit.outputs.length, 1); + assert.strictEqual(edit.outputs[0].outputs.length, 1); + assert.deepStrictEqual(edit.outputs[0].outputs[0].valueBytes, Array.from(new TextEncoder().encode('interrupted'))); + found = true; + } + } + assert.ok(found); + }); });