From 4edba5372708adf4cdda3e5eea382bafa60ed54b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:03:22 +1100 Subject: [PATCH 01/19] custom in metadata is optional based on ipynb ext --- .../editor-integration/cellFactory.ts | 4 +- .../cellExecutionMessageHandler.unit.test.ts | 1713 +++++++++-------- ...ferredKernelConnectionService.unit.test.ts | 549 +++--- src/platform/common/utils.ts | 121 +- .../import-export/importTracker.unit.test.ts | 464 ++--- .../import-export/jupyterExporter.ts | 21 +- .../extensionRecommendation.unit.test.ts | 477 ++--- .../datascience/editor-integration/helpers.ts | 5 +- .../datascience/notebook/executionHelper.ts | 3 +- src/test/datascience/notebook/helper.ts | 36 +- src/test/standardTest.node.ts | 4 +- 11 files changed, 1780 insertions(+), 1617 deletions(-) diff --git a/src/interactive-window/editor-integration/cellFactory.ts b/src/interactive-window/editor-integration/cellFactory.ts index e3c7928023e..ce123820ffc 100644 --- a/src/interactive-window/editor-integration/cellFactory.ts +++ b/src/interactive-window/editor-integration/cellFactory.ts @@ -5,7 +5,7 @@ import { NotebookCellData, NotebookCellKind, NotebookDocument, Range, TextDocume import { CellMatcher } from './cellMatcher'; import { ICellRange, IJupyterSettings } from '../../platform/common/types'; import { noop } from '../../platform/common/utils/misc'; -import { parseForComments, generateMarkdownFromCodeLines } from '../../platform/common/utils'; +import { parseForComments, generateMarkdownFromCodeLines, useCustomMetadata } from '../../platform/common/utils'; import { splitLines } from '../../platform/common/helpers'; import { isSysInfoCell } from '../systemInfoCell'; import { getCellMetadata } from '../../platform/common/utils'; @@ -145,7 +145,7 @@ export function generateCellsFromNotebookDocument(notebookDocument: NotebookDocu if (cell.kind === NotebookCellKind.Code) { cellData.outputs = [...cell.outputs]; } - cellData.metadata = { custom: getCellMetadata(cell) }; + cellData.metadata = useCustomMetadata() ? { custom: getCellMetadata(cell) } : getCellMetadata(cell); return cellData; }); } diff --git a/src/kernels/execution/cellExecutionMessageHandler.unit.test.ts b/src/kernels/execution/cellExecutionMessageHandler.unit.test.ts index f2a4d06d775..ad78798fc9e 100644 --- a/src/kernels/execution/cellExecutionMessageHandler.unit.test.ts +++ b/src/kernels/execution/cellExecutionMessageHandler.unit.test.ts @@ -31,721 +31,739 @@ import { import { JupyterRequestCreator } from '../jupyter/session/jupyterRequestCreator.node'; import { waitForCondition } from '../../test/common'; -suite(`Cell Execution Message Handler`, () => { - let disposables: IDisposable[] = []; - let controller: IKernelController; - let context: IExtensionContext; - let kernel: Kernel.IKernelConnection; - let fakeSocket: IFakeSocket; - let messageHandlerService: CellExecutionMessageHandlerService; - let tokenSource: CancellationTokenSource; - let messageHandlingFailure: undefined | Error; - const msgIdProducer = new MsgIdProducer(); - - function createNotebook(cells: NotebookCellData[]) { - const notebook = createMockedNotebookDocument(cells); - messageHandlerService = new CellExecutionMessageHandlerService(controller, instance(context), [], notebook); - disposables.push(messageHandlerService); - return notebook; - } - function sendRequest(cell: NotebookCell, code: string) { - const request = kernel.requestExecute({ - code, - allow_stdin: true, - silent: false, - stop_on_error: true, - store_history: true - }); - const producer = createMessageProducers(msgIdProducer).forExecRequest(request); - const handler = messageHandlerService.registerListenerForResumingExecution(cell, { - kernel, - msg_id: request.msg.header.msg_id, - cellExecution: createKernelController().createNotebookCellExecution(cell) - }); - handler.onErrorHandlingExecuteRequestIOPubMessage( - (ex) => (messageHandlingFailure = ex.error), - undefined, - disposables - ); - disposables.push(handler); - return { request, producer, handler }; - } - - setup(() => { - msgIdProducer.reset(); - messageHandlingFailure = undefined; - tokenSource = new CancellationTokenSource(); - disposables.push(tokenSource); - controller = createKernelController(); - context = mock(); - const fakes = createKernelConnection(new JupyterRequestCreator()); - kernel = fakes.connection; - fakeSocket = fakes.socket; - }); - teardown(() => (disposables = dispose(disposables))); - - suite('Display Updates', () => { - const display_id = 'displayIdXYZ'; - const display_id2 = 'displayIdXYZ_2'; - const imageCell = dedent` +[true, false].forEach((useCustomMetadata) => { + suite( + `Cell Execution Message Handler (${useCustomMetadata ? 'with custom metadata' : 'without custom metadata'})`, + async () => { + let disposables: IDisposable[] = []; + let controller: IKernelController; + let context: IExtensionContext; + let kernel: Kernel.IKernelConnection; + let fakeSocket: IFakeSocket; + let messageHandlerService: CellExecutionMessageHandlerService; + let tokenSource: CancellationTokenSource; + let messageHandlingFailure: undefined | Error; + const msgIdProducer = new MsgIdProducer(); + + function createNotebook(cells: NotebookCellData[]) { + const notebook = createMockedNotebookDocument(useCustomMetadata, cells); + messageHandlerService = new CellExecutionMessageHandlerService( + controller, + instance(context), + [], + notebook + ); + disposables.push(messageHandlerService); + return notebook; + } + function sendRequest(cell: NotebookCell, code: string) { + const request = kernel.requestExecute({ + code, + allow_stdin: true, + silent: false, + stop_on_error: true, + store_history: true + }); + const producer = createMessageProducers(msgIdProducer).forExecRequest(request); + const handler = messageHandlerService.registerListenerForResumingExecution(cell, { + kernel, + msg_id: request.msg.header.msg_id, + cellExecution: createKernelController().createNotebookCellExecution(cell) + }); + handler.onErrorHandlingExecuteRequestIOPubMessage( + (ex) => (messageHandlingFailure = ex.error), + undefined, + disposables + ); + disposables.push(handler); + return { request, producer, handler }; + } + + setup(() => { + msgIdProducer.reset(); + messageHandlingFailure = undefined; + tokenSource = new CancellationTokenSource(); + disposables.push(tokenSource); + controller = createKernelController(); + context = mock(); + const fakes = createKernelConnection(new JupyterRequestCreator()); + kernel = fakes.connection; + fakeSocket = fakes.socket; + }); + teardown(() => (disposables = dispose(disposables))); + + suite('Display Updates', () => { + const display_id = 'displayIdXYZ'; + const display_id2 = 'displayIdXYZ_2'; + const imageCell = dedent` from base64 import b64decode from IPython.display import Image, display img = b64decode('iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==') h = display(Image(img, width=50, height=50), display_id=True) `; - const imageOutput: IDisplayData = { - data: { - 'image/png': - 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', - 'text/plain': [''] - }, - metadata: { - 'image/png': { - height: 50, - width: 50 - } - }, - output_type: 'display_data' - }; - const emptyDisplayDataOutput: IDisplayData = { - data: {}, - metadata: {}, - output_type: 'display_data' - }; - const imageUpdateOutput: IDisplayUpdate = { - data: { - 'image/png': - 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', - 'text/plain': [''] - }, - metadata: { - 'image/png': { - height: 500, - width: 500 - } - }, - output_type: 'update_display_data' - }; - const addDisplayDataOutput = dedent` + const imageOutput: IDisplayData = { + data: { + 'image/png': + 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', + 'text/plain': [''] + }, + metadata: { + 'image/png': { + height: 50, + width: 50 + } + }, + output_type: 'display_data' + }; + const emptyDisplayDataOutput: IDisplayData = { + data: {}, + metadata: {}, + output_type: 'display_data' + }; + const imageUpdateOutput: IDisplayUpdate = { + data: { + 'image/png': + 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', + 'text/plain': [''] + }, + metadata: { + 'image/png': { + height: 500, + width: 500 + } + }, + output_type: 'update_display_data' + }; + const addDisplayDataOutput = dedent` from IPython.display import display dh = display(display_id=True) dh`; - const codeToUpdateDisplayDataHello = dedent` + const codeToUpdateDisplayDataHello = dedent` from IPython.display import Markdown dh.update(Markdown("Hello")) `; - const codeToUpdateDisplayDataWorld = dedent` + const codeToUpdateDisplayDataWorld = dedent` from IPython.display import Markdown dh.update(Markdown("World")) `; - const outputsFromHelloUpdate: IDisplayUpdate = { - data: { - 'text/markdown': 'Hello', - 'text/plain': '' - }, - transient: { - display_id: display_id - }, - metadata: {}, - output_type: 'update_display_data' - }; - const outputsFromWorldUpdate: IDisplayUpdate = { - data: { - 'text/markdown': 'World', - 'text/plain': '' - }, - transient: { - display_id: display_id - }, - metadata: {}, - output_type: 'update_display_data' - }; - - const codeForTwoDisplayUpdates = dedent` + const outputsFromHelloUpdate: IDisplayUpdate = { + data: { + 'text/markdown': 'Hello', + 'text/plain': '' + }, + transient: { + display_id: display_id + }, + metadata: {}, + output_type: 'update_display_data' + }; + const outputsFromWorldUpdate: IDisplayUpdate = { + data: { + 'text/markdown': 'World', + 'text/plain': '' + }, + transient: { + display_id: display_id + }, + metadata: {}, + output_type: 'update_display_data' + }; + + const codeForTwoDisplayUpdates = dedent` from IPython.display import display, HTML, Markdown dh = display(Markdown("Hello"), display_id=True) dh2 = display(Markdown("World"), display_id=True) `; - const codeToUpdateDisplayData1ILike = 'dh.update(Markdown("I Like"))'; - const codeToUpdateDisplayData1Pizza = 'dh.update(Markdown("Pizza"))'; - const outputsForTwoDisplayDataHelloWorld: IDisplayData[] = [ - { - data: { - 'text/markdown': 'Hello', - 'text/plain': '' - }, - transient: { - display_id: display_id - }, - metadata: {}, - output_type: 'display_data' - }, - { - data: { - 'text/markdown': 'World', - 'text/plain': '' - }, - transient: { - display_id: display_id2 - }, - metadata: {}, - output_type: 'display_data' - } - ]; - const outputsFromILikeUpdate: IDisplayUpdate = { - data: { - 'text/markdown': 'I Like', - 'text/plain': '' - }, - transient: { - display_id: display_id - }, - metadata: {}, - output_type: 'update_display_data' - }; - const outputsFromPizzaUpdate: IDisplayUpdate = { - data: { - 'text/markdown': 'Pizza', - 'text/plain': '' - }, - transient: { - display_id: display_id2 - }, - metadata: {}, - output_type: 'update_display_data' - }; - - async function executeCellWithOutput( - cell: NotebookCell, - code: string, - executionCount: number, - messageGenerator: ( - producer: ReturnType['forExecRequest']> - ) => KernelMessage.IMessage[] - ) { - // Now update the display data of the first cell from the second cell - const { request, producer } = sendRequest(cell, code); - - fakeSocket.emitOnMessage(producer.status('busy')); - fakeSocket.emitOnMessage(producer.execInput(executionCount)); - messageGenerator(producer).forEach((msg) => fakeSocket.emitOnMessage(msg)); - fakeSocket.emitOnMessage(producer.status('idle')); - fakeSocket.emitOnMessage(producer.reply(executionCount)); - - await request.done; - - assert.isUndefined(messageHandlingFailure); - } - - test('Execute cell and add output (Issue 8621)', async () => { - const notebook = createNotebook([ - { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: 'h.update(Image(img, width=500, height=500))', - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - - await executeCellWithOutput(cell, imageCell, 1, (producer) => { - return [ - producer.displayOutput({ - data: imageOutput.data, - metadata: imageOutput.metadata, - transient: { display_id: 'displayIdXYZ' } - }) + const codeToUpdateDisplayData1ILike = 'dh.update(Markdown("I Like"))'; + const codeToUpdateDisplayData1Pizza = 'dh.update(Markdown("Pizza"))'; + const outputsForTwoDisplayDataHelloWorld: IDisplayData[] = [ + { + data: { + 'text/markdown': 'Hello', + 'text/plain': '' + }, + transient: { + display_id: display_id + }, + metadata: {}, + output_type: 'display_data' + }, + { + data: { + 'text/markdown': 'World', + 'text/plain': '' + }, + transient: { + display_id: display_id2 + }, + metadata: {}, + output_type: 'display_data' + } ]; - }); - assert.strictEqual(cell.outputs.length, 1); - const output = translateCellDisplayOutput(cell.outputs[0]); - delete output.transient; - assert.deepEqual(output, imageOutput); - }); - test('Execute cell and update Display Data with metadata (Issue 8621)', async () => { - const notebook = createNotebook([ - { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: 'h.update(Image(img, width=500, height=500))', - outputs: [] + const outputsFromILikeUpdate: IDisplayUpdate = { + data: { + 'text/markdown': 'I Like', + 'text/plain': '' + }, + transient: { + display_id: display_id + }, + metadata: {}, + output_type: 'update_display_data' + }; + const outputsFromPizzaUpdate: IDisplayUpdate = { + data: { + 'text/markdown': 'Pizza', + 'text/plain': '' + }, + transient: { + display_id: display_id2 + }, + metadata: {}, + output_type: 'update_display_data' + }; + + async function executeCellWithOutput( + cell: NotebookCell, + code: string, + executionCount: number, + messageGenerator: ( + producer: ReturnType['forExecRequest']> + ) => KernelMessage.IMessage[] + ) { + // Now update the display data of the first cell from the second cell + const { request, producer } = sendRequest(cell, code); + + fakeSocket.emitOnMessage(producer.status('busy')); + fakeSocket.emitOnMessage(producer.execInput(executionCount)); + messageGenerator(producer).forEach((msg) => fakeSocket.emitOnMessage(msg)); + fakeSocket.emitOnMessage(producer.status('idle')); + fakeSocket.emitOnMessage(producer.reply(executionCount)); + + await request.done; + + assert.isUndefined(messageHandlingFailure); } - ]); - const cell = notebook.cellAt(0); - - await executeCellWithOutput(cell, imageCell, 1, (producer) => { - return [ - producer.displayOutput({ - data: imageOutput.data, - metadata: imageOutput.metadata, - transient: { display_id: 'displayIdXYZ' } - }) - ]; - }); - assert.strictEqual(cell.outputs.length, 1); - const output = translateCellDisplayOutput(cell.outputs[0]); - delete output.transient; - assert.deepEqual(output, imageOutput); - - // Now update the display data of the first cell from the second cell - await executeCellWithOutput( - notebook.cellAt(1), - 'h.update(Image(img, width=500, height=500))', - 2, - (producer) => { - return [ - producer.displayUpdate({ - data: imageUpdateOutput.data, - metadata: imageUpdateOutput.metadata, - transient: { display_id: 'displayIdXYZ' } + + test('Execute cell and add output (Issue 8621)', async () => { + const notebook = createNotebook([ + { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: 'h.update(Image(img, width=500, height=500))', + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + + await executeCellWithOutput(cell, imageCell, 1, (producer) => { + return [ + producer.displayOutput({ + data: imageOutput.data, + metadata: imageOutput.metadata, + transient: { display_id: 'displayIdXYZ' } + }) + ]; + }); + assert.strictEqual(cell.outputs.length, 1); + const output = translateCellDisplayOutput(cell.outputs[0]); + delete output.transient; + assert.deepEqual(output, imageOutput); + }); + test('Execute cell and update Display Data with metadata (Issue 8621)', async () => { + const notebook = createNotebook([ + { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: 'h.update(Image(img, width=500, height=500))', + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + + await executeCellWithOutput(cell, imageCell, 1, (producer) => { + return [ + producer.displayOutput({ + data: imageOutput.data, + metadata: imageOutput.metadata, + transient: { display_id: 'displayIdXYZ' } + }) + ]; + }); + assert.strictEqual(cell.outputs.length, 1); + const output = translateCellDisplayOutput(cell.outputs[0]); + delete output.transient; + assert.deepEqual(output, imageOutput); + + // Now update the display data of the first cell from the second cell + await executeCellWithOutput( + notebook.cellAt(1), + 'h.update(Image(img, width=500, height=500))', + 2, + (producer) => { + return [ + producer.displayUpdate({ + data: imageUpdateOutput.data, + metadata: imageUpdateOutput.metadata, + transient: { display_id: 'displayIdXYZ' } + }) + ]; + } + ); + assert.strictEqual(cell.outputs.length, 1); + const output2 = translateCellDisplayOutput(cell.outputs[0]); + delete output2.transient; + assert.deepEqual( + output2, + Object.assign({}, imageOutput, { + metadata: { + 'image/png': { + height: 500, + width: 500 + } + } }) - ]; - } - ); - assert.strictEqual(cell.outputs.length, 1); - const output2 = translateCellDisplayOutput(cell.outputs[0]); - delete output2.transient; - assert.deepEqual( - output2, - Object.assign({}, imageOutput, { - metadata: { - 'image/png': { - height: 500, - width: 500 + ); + }); + test('Execute cell and update Display Data with metadata (even if Cell DOM has not yet been updated) (Issue 8621)', async () => { + const notebook = createNotebook([ + { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: 'h.update(Image(img, width=500, height=500))', + outputs: [] } - } - }) - ); - }); - test('Execute cell and update Display Data with metadata (even if Cell DOM has not yet been updated) (Issue 8621)', async () => { - const notebook = createNotebook([ - { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: imageCell, outputs: [] }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: 'h.update(Image(img, width=500, height=500))', - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - await executeCellWithOutput(cell, imageCell, 1, (producer) => { - return [ - producer.displayOutput({ - data: imageOutput.data, - metadata: imageOutput.metadata, - transient: { display_id: 'displayIdXYZ' } - }) - ]; - }); - assert.strictEqual(cell.outputs.length, 1); - const output = translateCellDisplayOutput(cell.outputs[0]); - delete output.transient; - assert.deepEqual(output, imageOutput); - - // Mimic a situation where the cell outputs have not yet been updated in the DOM. - notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); - - // Now update the display data of the first cell from the second cell - await executeCellWithOutput( - notebook.cellAt(1), - 'h.update(Image(img, width=500, height=500))', - 2, - (producer) => { - return [ - producer.displayUpdate({ - data: imageUpdateOutput.data, - metadata: imageUpdateOutput.metadata, - transient: { display_id: 'displayIdXYZ' } + ]); + const cell = notebook.cellAt(0); + await executeCellWithOutput(cell, imageCell, 1, (producer) => { + return [ + producer.displayOutput({ + data: imageOutput.data, + metadata: imageOutput.metadata, + transient: { display_id: 'displayIdXYZ' } + }) + ]; + }); + assert.strictEqual(cell.outputs.length, 1); + const output = translateCellDisplayOutput(cell.outputs[0]); + delete output.transient; + assert.deepEqual(output, imageOutput); + + // Mimic a situation where the cell outputs have not yet been updated in the DOM. + notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); + + // Now update the display data of the first cell from the second cell + await executeCellWithOutput( + notebook.cellAt(1), + 'h.update(Image(img, width=500, height=500))', + 2, + (producer) => { + return [ + producer.displayUpdate({ + data: imageUpdateOutput.data, + metadata: imageUpdateOutput.metadata, + transient: { display_id: 'displayIdXYZ' } + }) + ]; + } + ); + assert.strictEqual(cell.outputs.length, 1); + const output2 = translateCellDisplayOutput(cell.outputs[0]); + delete output2.transient; + assert.deepEqual( + output2, + Object.assign({}, imageOutput, { + metadata: { + 'image/png': { + height: 500, + width: 500 + } + } }) - ]; - } - ); - assert.strictEqual(cell.outputs.length, 1); - const output2 = translateCellDisplayOutput(cell.outputs[0]); - delete output2.transient; - assert.deepEqual( - output2, - Object.assign({}, imageOutput, { - metadata: { - 'image/png': { - height: 500, - width: 500 + ); + }); + test('Execute cell and add Display output', async () => { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] } - } - }) - ); - }); - test('Execute cell and add Display output', async () => { - const notebook = createNotebook([ - { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: addDisplayDataOutput, outputs: [] }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - await executeCellWithOutput(cell, addDisplayDataOutput, 1, (producer) => { - return [ - producer.displayOutput({ - data: imageOutput.data, - metadata: imageOutput.metadata, - transient: { display_id } - }) - ]; - }); - - assert.isAtLeast(cell.outputs.length, 1); - const output = translateCellDisplayOutput(cell.outputs[0]); - assert.strictEqual((output.transient as any).display_id, display_id); - - // Update the display data. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataHello, 1, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromHelloUpdate.data, - metadata: outputsFromHelloUpdate.metadata, - transient: { display_id } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - // Update the display data again. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataWorld, 1, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromWorldUpdate.data, - metadata: outputsFromWorldUpdate.metadata, - transient: { display_id } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'World' - ); - }); - - test('Execute cell and add Display output (even if Cell DOM has not yet been updated) ', async () => { - const notebook = createNotebook([ - { kind: NotebookCellKind.Code, languageId: PYTHON_LANGUAGE, value: addDisplayDataOutput, outputs: [] }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - - await executeCellWithOutput(cell, addDisplayDataOutput, 1, (producer) => { - return [ - producer.displayOutput({ - data: emptyDisplayDataOutput.data, - metadata: emptyDisplayDataOutput.metadata, - transient: { display_id } - }) - ]; - }); - // Mimic a situation where the cell outputs have not yet been updated in the DOM. - notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); - // Update the display data. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataHello, 1, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromHelloUpdate.data, - metadata: outputsFromHelloUpdate.metadata, - transient: { display_id } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - // Update the display data again. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataWorld, 1, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromWorldUpdate.data, - metadata: outputsFromWorldUpdate.metadata, - transient: { display_id } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'World' - ); - }); - test('Updates to two separate display updates in the same cell output', async () => { - const notebook = createNotebook([ - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: codeForTwoDisplayUpdates, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { - return outputsForTwoDisplayDataHelloWorld.map((item) => - producer.displayOutput({ - data: item.data, - metadata: item.metadata, - transient: item.transient as any - }) - ); - }); - assert.strictEqual(notebook.cellAt(0).outputs.length, 2); - const output1 = translateCellDisplayOutput(notebook.cellAt(0).outputs[0]); - assert.strictEqual((output1.transient as any).display_id, display_id); - const output2 = translateCellDisplayOutput(notebook.cellAt(0).outputs[1]); - assert.strictEqual((output2.transient as any).display_id, display_id2); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'World' - ); - - // Update the first display data. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1ILike, 2, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromILikeUpdate.data, - metadata: outputsFromILikeUpdate.metadata, - transient: { display_id } - }) - ]; - }); - // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1ILike, 2, outputsFromILikeUpdate); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'I Like' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'World' - ); - - // Update the second display data. - await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1Pizza, 3, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromPizzaUpdate.data, - metadata: outputsFromPizzaUpdate.metadata, - transient: { display_id: display_id2 } - }) - ]; - }); - // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1Pizza, 3, outputsFromPizzaUpdate); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'I Like' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'Pizza' - ); - }); - - test('Updates to two separate display updates in the same cell output (update second display update)', async () => { - const notebook = createNotebook([ - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: codeForTwoDisplayUpdates, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { - return outputsForTwoDisplayDataHelloWorld.map((item) => - producer.displayOutput({ - data: item.data, - metadata: item.metadata, - transient: item.transient as any - }) - ); - }); - - assert.strictEqual(notebook.cellAt(0).outputs.length, 2); - const output1 = translateCellDisplayOutput(notebook.cellAt(0).outputs[0]); - assert.strictEqual((output1.transient as any).display_id, display_id); - const output2 = translateCellDisplayOutput(notebook.cellAt(0).outputs[1]); - assert.strictEqual((output2.transient as any).display_id, display_id2); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'World' - ); - - // Update the second display data. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1Pizza, 2, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromPizzaUpdate.data, - metadata: outputsFromPizzaUpdate.metadata, - transient: { display_id: display_id2 } - }) - ]; - }); - - // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1Pizza, 2, outputsFromPizzaUpdate); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'Pizza' - ); - - // Update the first display data. - await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1ILike, 3, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromILikeUpdate.data, - metadata: outputsFromILikeUpdate.metadata, - transient: { display_id } - }) - ]; - }); - // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1ILike, 3, outputsFromILikeUpdate); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'I Like' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'Pizza' - ); - }); - - test('Updates to two separate display updates in the same cell output (even if Cell DOM has not yet been updated)', async () => { - const notebook = createNotebook([ - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: codeForTwoDisplayUpdates, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - }, - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: addDisplayDataOutput, - outputs: [] - } - ]); - const cell = notebook.cellAt(0); - await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { - return outputsForTwoDisplayDataHelloWorld.map((item) => - producer.displayOutput({ - data: item.data, - metadata: item.metadata, - transient: item.transient as any - }) - ); - }); - - // Mimic a situation where the cell outputs have not yet been updated in the DOM. - notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); - - // Update the second display data. - await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1Pizza, 2, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromPizzaUpdate.data, - metadata: outputsFromPizzaUpdate.metadata, - transient: { display_id: display_id2 } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Hello' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'Pizza' - ); - - // Update the first display data. - await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1ILike, 3, (producer) => { - return [ - producer.displayUpdate({ - data: outputsFromILikeUpdate.data, - metadata: outputsFromILikeUpdate.metadata, - transient: { display_id } - }) - ]; - }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'I Like' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), - 'Pizza' - ); - }); - - test('Updates display updates in the same cell output within the same execution (even if Cell DOM has not yet been updated) (Issue 12755, 13105, 13163)', async () => { - const code = dedent` + ]); + const cell = notebook.cellAt(0); + await executeCellWithOutput(cell, addDisplayDataOutput, 1, (producer) => { + return [ + producer.displayOutput({ + data: imageOutput.data, + metadata: imageOutput.metadata, + transient: { display_id } + }) + ]; + }); + + assert.isAtLeast(cell.outputs.length, 1); + const output = translateCellDisplayOutput(cell.outputs[0]); + assert.strictEqual((output.transient as any).display_id, display_id); + + // Update the display data. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataHello, 1, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromHelloUpdate.data, + metadata: outputsFromHelloUpdate.metadata, + transient: { display_id } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + // Update the display data again. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataWorld, 1, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromWorldUpdate.data, + metadata: outputsFromWorldUpdate.metadata, + transient: { display_id } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'World' + ); + }); + + test('Execute cell and add Display output (even if Cell DOM has not yet been updated) ', async () => { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + + await executeCellWithOutput(cell, addDisplayDataOutput, 1, (producer) => { + return [ + producer.displayOutput({ + data: emptyDisplayDataOutput.data, + metadata: emptyDisplayDataOutput.metadata, + transient: { display_id } + }) + ]; + }); + // Mimic a situation where the cell outputs have not yet been updated in the DOM. + notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); + // Update the display data. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataHello, 1, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromHelloUpdate.data, + metadata: outputsFromHelloUpdate.metadata, + transient: { display_id } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + // Update the display data again. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayDataWorld, 1, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromWorldUpdate.data, + metadata: outputsFromWorldUpdate.metadata, + transient: { display_id } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'World' + ); + }); + test('Updates to two separate display updates in the same cell output', async () => { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: codeForTwoDisplayUpdates, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { + return outputsForTwoDisplayDataHelloWorld.map((item) => + producer.displayOutput({ + data: item.data, + metadata: item.metadata, + transient: item.transient as any + }) + ); + }); + assert.strictEqual(notebook.cellAt(0).outputs.length, 2); + const output1 = translateCellDisplayOutput(notebook.cellAt(0).outputs[0]); + assert.strictEqual((output1.transient as any).display_id, display_id); + const output2 = translateCellDisplayOutput(notebook.cellAt(0).outputs[1]); + assert.strictEqual((output2.transient as any).display_id, display_id2); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'World' + ); + + // Update the first display data. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1ILike, 2, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromILikeUpdate.data, + metadata: outputsFromILikeUpdate.metadata, + transient: { display_id } + }) + ]; + }); + // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1ILike, 2, outputsFromILikeUpdate); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'I Like' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'World' + ); + + // Update the second display data. + await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1Pizza, 3, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromPizzaUpdate.data, + metadata: outputsFromPizzaUpdate.metadata, + transient: { display_id: display_id2 } + }) + ]; + }); + // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1Pizza, 3, outputsFromPizzaUpdate); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'I Like' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'Pizza' + ); + }); + + test('Updates to two separate display updates in the same cell output (update second display update)', async () => { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: codeForTwoDisplayUpdates, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { + return outputsForTwoDisplayDataHelloWorld.map((item) => + producer.displayOutput({ + data: item.data, + metadata: item.metadata, + transient: item.transient as any + }) + ); + }); + + assert.strictEqual(notebook.cellAt(0).outputs.length, 2); + const output1 = translateCellDisplayOutput(notebook.cellAt(0).outputs[0]); + assert.strictEqual((output1.transient as any).display_id, display_id); + const output2 = translateCellDisplayOutput(notebook.cellAt(0).outputs[1]); + assert.strictEqual((output2.transient as any).display_id, display_id2); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'World' + ); + + // Update the second display data. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1Pizza, 2, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromPizzaUpdate.data, + metadata: outputsFromPizzaUpdate.metadata, + transient: { display_id: display_id2 } + }) + ]; + }); + + // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1Pizza, 2, outputsFromPizzaUpdate); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'Pizza' + ); + + // Update the first display data. + await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1ILike, 3, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromILikeUpdate.data, + metadata: outputsFromILikeUpdate.metadata, + transient: { display_id } + }) + ]; + }); + // await executeAndUpdateDisplayData(notebook, codeToUpdateDisplayData1ILike, 3, outputsFromILikeUpdate); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'I Like' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'Pizza' + ); + }); + + test('Updates to two separate display updates in the same cell output (even if Cell DOM has not yet been updated)', async () => { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: codeForTwoDisplayUpdates, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + }, + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: addDisplayDataOutput, + outputs: [] + } + ]); + const cell = notebook.cellAt(0); + await executeCellWithOutput(cell, codeForTwoDisplayUpdates, 1, (producer) => { + return outputsForTwoDisplayDataHelloWorld.map((item) => + producer.displayOutput({ + data: item.data, + metadata: item.metadata, + transient: item.transient as any + }) + ); + }); + + // Mimic a situation where the cell outputs have not yet been updated in the DOM. + notebook.cellAt(0).outputs.slice(0, notebook.cellAt(0).outputs.length); + + // Update the second display data. + await executeCellWithOutput(notebook.cellAt(1), codeToUpdateDisplayData1Pizza, 2, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromPizzaUpdate.data, + metadata: outputsFromPizzaUpdate.metadata, + transient: { display_id: display_id2 } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Hello' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'Pizza' + ); + + // Update the first display data. + await executeCellWithOutput(notebook.cellAt(2), codeToUpdateDisplayData1ILike, 3, (producer) => { + return [ + producer.displayUpdate({ + data: outputsFromILikeUpdate.data, + metadata: outputsFromILikeUpdate.metadata, + transient: { display_id } + }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'I Like' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'Pizza' + ); + }); + + test('Updates display updates in the same cell output within the same execution (even if Cell DOM has not yet been updated) (Issue 12755, 13105, 13163)', async () => { + const code = dedent` from IPython import display print("Touch me not") display.display(display.HTML('

A

'), display_id='1') @@ -755,171 +773,176 @@ suite(`Cell Execution Message Handler`, () => { display.update_display(display.HTML('

C

'), display_id='1') print('Pizza') `; - const notebook = createNotebook([ - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: code, - outputs: [] - } - ]); - await executeCellWithOutput(notebook.cellAt(0), code, 1, (producer) => { - return [ - producer.stream({ name: 'stdout', text: 'Touch me not\n' }), - producer.displayOutput({ - data: { - 'text/markdown': ['A'], - 'text/plain': [''] - }, - metadata: {}, - transient: { - display_id: display_id - } - }), - producer.stream({ name: 'stdout', text: 'Hello\n' }), - producer.displayUpdate({ - data: { - 'text/markdown': ['B'], - 'text/plain': [''] - }, - metadata: {}, - transient: { - display_id: display_id - } - }), - producer.stream({ name: 'stdout', text: 'World\n' }), - producer.displayUpdate({ - data: { - 'text/markdown': ['C'], - 'text/plain': [''] - }, - metadata: {}, - transient: { - display_id: display_id + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: code, + outputs: [] } - }), - producer.stream({ name: 'stdout', text: 'Pizza\n' }) - ]; + ]); + await executeCellWithOutput(notebook.cellAt(0), code, 1, (producer) => { + return [ + producer.stream({ name: 'stdout', text: 'Touch me not\n' }), + producer.displayOutput({ + data: { + 'text/markdown': ['A'], + 'text/plain': [''] + }, + metadata: {}, + transient: { + display_id: display_id + } + }), + producer.stream({ name: 'stdout', text: 'Hello\n' }), + producer.displayUpdate({ + data: { + 'text/markdown': ['B'], + 'text/plain': [''] + }, + metadata: {}, + transient: { + display_id: display_id + } + }), + producer.stream({ name: 'stdout', text: 'World\n' }), + producer.displayUpdate({ + data: { + 'text/markdown': ['C'], + 'text/plain': [''] + }, + metadata: {}, + transient: { + display_id: display_id + } + }), + producer.stream({ name: 'stdout', text: 'Pizza\n' }) + ]; + }); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), + 'Touch me not\n' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), + 'C' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[0].data).toString(), + 'Hello\n' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[1].data).toString(), + 'World\n' + ); + assert.strictEqual( + new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[2].data).toString(), + 'Pizza\n' + ); + }); }); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[0].items[0].data).toString(), - 'Touch me not\n' - ); - assert.strictEqual(new TextDecoder().decode(notebook.cellAt(0).outputs[1].items[0].data).toString(), 'C'); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[0].data).toString(), - 'Hello\n' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[1].data).toString(), - 'World\n' - ); - assert.strictEqual( - new TextDecoder().decode(notebook.cellAt(0).outputs[2].items[2].data).toString(), - 'Pizza\n' - ); - }); - }); - - suite('Resume Cell Execution', () => { - const print1To100 = dedent` + + suite('Resume Cell Execution', () => { + const print1To100 = dedent` import time for i in range(100): time.sleep(1) print(i) `; - const outputFor1To10 = new NotebookCellOutput([ - NotebookCellOutputItem.stdout('0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n') - ]); - test('Execute cell and resume cell execution on reload', async () => testResumingExecution(false)); - test('Execute cell and resume cell execution on reload (with reply message)', async () => - testResumingExecution(false)); - async function testResumingExecution(markEndOfExecutionWithReplyMessage: boolean) { - const notebook = createNotebook([ - { - kind: NotebookCellKind.Code, - languageId: PYTHON_LANGUAGE, - value: print1To100, - outputs: [outputFor1To10] - } - ]); - const cell = notebook.cellAt(0); - const { request, handler, producer } = sendRequest(cell, print1To100); - fakeSocket.emitOnMessage(producer.status('busy')); - fakeSocket.emitOnMessage(producer.execInput(1)); - Array.from({ length: 50 }, (_, i) => { - fakeSocket.emitOnMessage(producer.stream({ name: 'stdout', text: `${i}\n` })); - }); - - await waitForCondition( - () => cell.outputs.length === 1, - 100, - () => `Cell should have 1 output, but got ${cell.outputs.length}` - ); - await waitForCondition( - () => cell.outputs[0].items.length === 50, - 100, - () => `Cell output should have 50 output items, but got ${cell.outputs[0].items.length}` - ); - for (let index = 0; index < cell.outputs[0].items.length; index++) { - const item = cell.outputs[0].items[index]; - assert.strictEqual(item.mime, 'application/vnd.code.notebook.stdout'); - assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${index}\n`); - } - - // Now assume we closed VS Code, and then opened it again. - // At this point we need to resume the execution of the cell. - handler.dispose(); - request.dispose(); - - // Resume cell execution - const { handler: handler2 } = resumeExecution(cell, request.msg.header.msg_id); + const outputFor1To10 = new NotebookCellOutput([ + NotebookCellOutputItem.stdout('0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n') + ]); + test('Execute cell and resume cell execution on reload', async () => testResumingExecution(false)); + test('Execute cell and resume cell execution on reload (with reply message)', async () => + testResumingExecution(false)); + async function testResumingExecution(markEndOfExecutionWithReplyMessage: boolean) { + const notebook = createNotebook([ + { + kind: NotebookCellKind.Code, + languageId: PYTHON_LANGUAGE, + value: print1To100, + outputs: [outputFor1To10] + } + ]); + const cell = notebook.cellAt(0); + const { request, handler, producer } = sendRequest(cell, print1To100); + fakeSocket.emitOnMessage(producer.status('busy')); + fakeSocket.emitOnMessage(producer.execInput(1)); + Array.from({ length: 50 }, (_, i) => { + fakeSocket.emitOnMessage(producer.stream({ name: 'stdout', text: `${i}\n` })); + }); + + await waitForCondition( + () => cell.outputs.length === 1, + 100, + () => `Cell should have 1 output, but got ${cell.outputs.length}` + ); + await waitForCondition( + () => cell.outputs[0].items.length === 50, + 100, + () => `Cell output should have 50 output items, but got ${cell.outputs[0].items.length}` + ); + for (let index = 0; index < cell.outputs[0].items.length; index++) { + const item = cell.outputs[0].items[index]; + assert.strictEqual(item.mime, 'application/vnd.code.notebook.stdout'); + assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${index}\n`); + } - // Assume we start seeing outputs from 75 onwards, the others 50 to 74 are lost, as they were sent when vscode was closed. - Array.from({ length: 25 }, (_, i) => { - fakeSocket.emitOnMessage(producer.stream({ name: 'stdout', text: `${75 + i}\n` })); - }); - fakeSocket.emitOnMessage(producer.status('idle')); - if (markEndOfExecutionWithReplyMessage) { - // This message marks the completion of the message. - fakeSocket.emitOnMessage(producer.reply(1)); - } else { - // When VSC connects to a remote kernel session, we send a kernel info message, - // Getting a response for that marks the completion of the previous message. - fakeSocket.emitOnMessage(createMessageProducers(msgIdProducer).forKernelInfo().reply()); - } + // Now assume we closed VS Code, and then opened it again. + // At this point we need to resume the execution of the cell. + handler.dispose(); + request.dispose(); + + // Resume cell execution + const { handler: handler2 } = resumeExecution(cell, request.msg.header.msg_id); + + // Assume we start seeing outputs from 75 onwards, the others 50 to 74 are lost, as they were sent when vscode was closed. + Array.from({ length: 25 }, (_, i) => { + fakeSocket.emitOnMessage(producer.stream({ name: 'stdout', text: `${75 + i}\n` })); + }); + fakeSocket.emitOnMessage(producer.status('idle')); + if (markEndOfExecutionWithReplyMessage) { + // This message marks the completion of the message. + fakeSocket.emitOnMessage(producer.reply(1)); + } else { + // When VSC connects to a remote kernel session, we send a kernel info message, + // Getting a response for that marks the completion of the previous message. + fakeSocket.emitOnMessage(createMessageProducers(msgIdProducer).forKernelInfo().reply()); + } - await handler2.completed; - await waitForCondition( - () => cell.outputs[0].items.length === 75, - 100, - () => `Cell output should have 75 output items, but got ${cell.outputs[0].items.length}` - ); - - for (let index = 0; index < cell.outputs[0].items.length; index++) { - const item = cell.outputs[0].items[index]; - assert.strictEqual(item.mime, 'application/vnd.code.notebook.stdout'); - if (index >= 50) { - assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${25 + index}\n`); - } else { - assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${index}\n`); + await handler2.completed; + await waitForCondition( + () => cell.outputs[0].items.length === 75, + 100, + () => `Cell output should have 75 output items, but got ${cell.outputs[0].items.length}` + ); + + for (let index = 0; index < cell.outputs[0].items.length; index++) { + const item = cell.outputs[0].items[index]; + assert.strictEqual(item.mime, 'application/vnd.code.notebook.stdout'); + if (index >= 50) { + assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${25 + index}\n`); + } else { + assert.strictEqual(new TextDecoder().decode(item.data).toString(), `${index}\n`); + } + } } - } - } - function resumeExecution(cell: NotebookCell, msg_id: string) { - const handler = messageHandlerService.registerListenerForResumingExecution(cell, { - kernel, - msg_id, - cellExecution: createKernelController().createNotebookCellExecution(cell) + function resumeExecution(cell: NotebookCell, msg_id: string) { + const handler = messageHandlerService.registerListenerForResumingExecution(cell, { + kernel, + msg_id, + cellExecution: createKernelController().createNotebookCellExecution(cell) + }); + handler.onErrorHandlingExecuteRequestIOPubMessage( + (ex) => (messageHandlingFailure = ex.error), + undefined, + disposables + ); + disposables.push(handler); + return { handler }; + } }); - handler.onErrorHandlingExecuteRequestIOPubMessage( - (ex) => (messageHandlingFailure = ex.error), - undefined, - disposables - ); - disposables.push(handler); - return { handler }; } - }); + ); }); diff --git a/src/notebooks/controllers/preferredKernelConnectionService.unit.test.ts b/src/notebooks/controllers/preferredKernelConnectionService.unit.test.ts index cd32fde73d9..8f134bf6875 100644 --- a/src/notebooks/controllers/preferredKernelConnectionService.unit.test.ts +++ b/src/notebooks/controllers/preferredKernelConnectionService.unit.test.ts @@ -26,284 +26,315 @@ import { ServiceContainer } from '../../platform/ioc/container'; import { TestNotebookDocument } from '../../test/datascience/notebook/executionHelper'; import { PreferredKernelConnectionService } from './preferredKernelConnectionService'; import { JupyterConnection } from '../../kernels/jupyter/connection/jupyterConnection'; +import { mockedVSCodeNamespaces, resetVSCodeMocks } from '../../test/vscode-mock'; -suite('Preferred Kernel Connection', () => { - let preferredService: PreferredKernelConnectionService; - let serviceContainer: ServiceContainer; - let jupyterConnection: JupyterConnection; - let kernelFinder: IKernelFinder; - let remoteKernelFinder: IContributedKernelFinder; - let localKernelSpecFinder: IContributedKernelFinder; - let localPythonEnvFinder: IContributedKernelFinder; - let disposables: IDisposable[] = []; - let notebookMetadata: NotebookMetadata; - let notebook: NotebookDocument; - let preferredRemoteKernelProvider: PreferredRemoteKernelIdProvider; - let cancellation: CancellationTokenSource; - let onDidChangeRemoteKernels: EventEmitter<{ - added?: RemoteKernelConnectionMetadata[]; - removed?: RemoteKernelConnectionMetadata[]; - updated?: RemoteKernelConnectionMetadata[]; - }>; - let onDidChangeLocalKernels: EventEmitter<{ - added?: LocalKernelConnectionMetadata[]; - removed?: LocalKernelConnectionMetadata[]; - updated?: LocalKernelConnectionMetadata[]; - }>; - let onDidChangePythonKernels: EventEmitter<{ - added?: PythonKernelConnectionMetadata[]; - removed?: PythonKernelConnectionMetadata[]; - updated?: PythonKernelConnectionMetadata[]; - }>; - let interpreterService: IInterpreterService; - const serverProviderHandle1 = { handle: 'handle1', id: 'id1', extensionId: '' }; - const serverProviderHandle2 = { handle: 'handle2', id: 'id2', extensionId: '' }; - const remoteLiveKernelConnection1 = LiveRemoteKernelConnectionMetadata.create({ - baseUrl: '', - id: 'liveRemote1', - kernelModel: instance(mock()), - serverProviderHandle: serverProviderHandle1 - }); - const remoteLiveKernelConnection2 = LiveRemoteKernelConnectionMetadata.create({ - baseUrl: '', - id: 'liveRemote2', - kernelModel: instance(mock()), - serverProviderHandle: serverProviderHandle2 - }); - // const remoteLiveJavaKernelConnection = LiveRemoteKernelConnectionMetadata.create({ - // baseUrl: '', - // id: 'liveRemoteJava', - // kernelModel: { - // lastActivityTime: new Date(), - // model: { - // id: 'xyz', - // kernel: { - // name: 'java', - // id: 'xyz' - // }, - // path: 'baz/sample.ipynb', - // name: 'sample.ipynb', - // type: 'notebook' - // }, - // name: 'java', - // numberOfConnections: 1 - // }, - // serverProviderHandle: serverProviderHandle2 - // }); - const remoteJavaKernelSpec = RemoteKernelSpecConnectionMetadata.create({ - baseUrl: '', - id: 'remoteJavaKernelSpec', - kernelSpec: { - argv: [], - display_name: 'Java KernelSpec', - executable: '', - name: 'javaName', - language: 'java' - }, - serverProviderHandle: serverProviderHandle2 - }); - const localJavaKernelSpec = LocalKernelSpecConnectionMetadata.create({ - id: 'localJava', - kernelSpec: { - argv: [], - display_name: 'Java KernelSpec', - executable: '', - name: 'javaName', - language: 'java' - } - }); - let connection: IJupyterConnection; - setup(() => { - serviceContainer = mock(); - jupyterConnection = mock(JupyterConnection); - connection = mock(); - const iocStub = sinon.stub(ServiceContainer, 'instance').get(() => instance(serviceContainer)); - disposables.push(new Disposable(() => iocStub.restore())); - cancellation = new CancellationTokenSource(); - disposables.push(cancellation); - notebookMetadata = { - orig_nbformat: 4, - kernelspec: { - display_name: 'Kernel Spec', - name: 'kernelSpecName' - }, - language_info: { - name: 'languageName' - } - }; - notebook = new TestNotebookDocument(undefined, 'jupyter-notebook', { custom: { metadata: notebookMetadata } }); +[true, false].forEach((useCustomMetadata) => { + suite( + `Preferred Kernel Connection (${useCustomMetadata ? 'with custom metadata' : 'without custom metadata'})`, + + () => { + let preferredService: PreferredKernelConnectionService; + let serviceContainer: ServiceContainer; + let jupyterConnection: JupyterConnection; + let kernelFinder: IKernelFinder; + let remoteKernelFinder: IContributedKernelFinder; + let localKernelSpecFinder: IContributedKernelFinder; + let localPythonEnvFinder: IContributedKernelFinder; + let disposables: IDisposable[] = []; + let notebookMetadata: NotebookMetadata; + let notebook: NotebookDocument; + let preferredRemoteKernelProvider: PreferredRemoteKernelIdProvider; + let cancellation: CancellationTokenSource; + let onDidChangeRemoteKernels: EventEmitter<{ + added?: RemoteKernelConnectionMetadata[]; + removed?: RemoteKernelConnectionMetadata[]; + updated?: RemoteKernelConnectionMetadata[]; + }>; + let onDidChangeLocalKernels: EventEmitter<{ + added?: LocalKernelConnectionMetadata[]; + removed?: LocalKernelConnectionMetadata[]; + updated?: LocalKernelConnectionMetadata[]; + }>; + let onDidChangePythonKernels: EventEmitter<{ + added?: PythonKernelConnectionMetadata[]; + removed?: PythonKernelConnectionMetadata[]; + updated?: PythonKernelConnectionMetadata[]; + }>; + let interpreterService: IInterpreterService; + const serverProviderHandle1 = { handle: 'handle1', id: 'id1', extensionId: '' }; + const serverProviderHandle2 = { handle: 'handle2', id: 'id2', extensionId: '' }; + const remoteLiveKernelConnection1 = LiveRemoteKernelConnectionMetadata.create({ + baseUrl: '', + id: 'liveRemote1', + kernelModel: instance(mock()), + serverProviderHandle: serverProviderHandle1 + }); + const remoteLiveKernelConnection2 = LiveRemoteKernelConnectionMetadata.create({ + baseUrl: '', + id: 'liveRemote2', + kernelModel: instance(mock()), + serverProviderHandle: serverProviderHandle2 + }); + // const remoteLiveJavaKernelConnection = LiveRemoteKernelConnectionMetadata.create({ + // baseUrl: '', + // id: 'liveRemoteJava', + // kernelModel: { + // lastActivityTime: new Date(), + // model: { + // id: 'xyz', + // kernel: { + // name: 'java', + // id: 'xyz' + // }, + // path: 'baz/sample.ipynb', + // name: 'sample.ipynb', + // type: 'notebook' + // }, + // name: 'java', + // numberOfConnections: 1 + // }, + // serverProviderHandle: serverProviderHandle2 + // }); + const remoteJavaKernelSpec = RemoteKernelSpecConnectionMetadata.create({ + baseUrl: '', + id: 'remoteJavaKernelSpec', + kernelSpec: { + argv: [], + display_name: 'Java KernelSpec', + executable: '', + name: 'javaName', + language: 'java' + }, + serverProviderHandle: serverProviderHandle2 + }); + const localJavaKernelSpec = LocalKernelSpecConnectionMetadata.create({ + id: 'localJava', + kernelSpec: { + argv: [], + display_name: 'Java KernelSpec', + executable: '', + name: 'javaName', + language: 'java' + } + }); + let connection: IJupyterConnection; + setup(() => { + serviceContainer = mock(); + jupyterConnection = mock(JupyterConnection); + connection = mock(); + const iocStub = sinon.stub(ServiceContainer, 'instance').get(() => instance(serviceContainer)); + disposables.push(new Disposable(() => iocStub.restore())); + cancellation = new CancellationTokenSource(); + disposables.push(cancellation); + notebookMetadata = { + orig_nbformat: 4, + kernelspec: { + display_name: 'Kernel Spec', + name: 'kernelSpecName' + }, + language_info: { + name: 'languageName' + } + }; + notebook = new TestNotebookDocument( + undefined, + 'jupyter-notebook', + useCustomMetadata + ? { + custom: { metadata: notebookMetadata } + } + : { + metadata: notebookMetadata + } + ); - kernelFinder = mock(); - preferredRemoteKernelProvider = mock(); - remoteKernelFinder = mock>(); - localKernelSpecFinder = mock>(); - localPythonEnvFinder = mock>(); - interpreterService = mock(); + kernelFinder = mock(); + preferredRemoteKernelProvider = mock(); + remoteKernelFinder = mock>(); + localKernelSpecFinder = mock>(); + localPythonEnvFinder = mock>(); + interpreterService = mock(); - onDidChangeRemoteKernels = new EventEmitter<{ - added?: RemoteKernelConnectionMetadata[]; - removed?: RemoteKernelConnectionMetadata[]; - updated?: RemoteKernelConnectionMetadata[]; - }>(); - onDidChangeLocalKernels = new EventEmitter<{ - added?: LocalKernelConnectionMetadata[]; - removed?: LocalKernelConnectionMetadata[]; - updated?: LocalKernelConnectionMetadata[]; - }>(); - onDidChangePythonKernels = new EventEmitter<{ - added?: PythonKernelConnectionMetadata[]; - removed?: PythonKernelConnectionMetadata[]; - updated?: PythonKernelConnectionMetadata[]; - }>(); - disposables.push(onDidChangeRemoteKernels); - disposables.push(onDidChangeLocalKernels); - disposables.push(onDidChangePythonKernels); + onDidChangeRemoteKernels = new EventEmitter<{ + added?: RemoteKernelConnectionMetadata[]; + removed?: RemoteKernelConnectionMetadata[]; + updated?: RemoteKernelConnectionMetadata[]; + }>(); + onDidChangeLocalKernels = new EventEmitter<{ + added?: LocalKernelConnectionMetadata[]; + removed?: LocalKernelConnectionMetadata[]; + updated?: LocalKernelConnectionMetadata[]; + }>(); + onDidChangePythonKernels = new EventEmitter<{ + added?: PythonKernelConnectionMetadata[]; + removed?: PythonKernelConnectionMetadata[]; + updated?: PythonKernelConnectionMetadata[]; + }>(); + disposables.push(onDidChangeRemoteKernels); + disposables.push(onDidChangeLocalKernels); + disposables.push(onDidChangePythonKernels); - when(serviceContainer.get(PreferredRemoteKernelIdProvider)).thenReturn( - instance(preferredRemoteKernelProvider) - ); - when(serviceContainer.get(IKernelFinder)).thenReturn(instance(kernelFinder)); - when(serviceContainer.get(IInterpreterService)).thenReturn(instance(interpreterService)); - when(remoteKernelFinder.kind).thenReturn(ContributedKernelFinderKind.Remote); - when(remoteKernelFinder.onDidChangeKernels).thenReturn(onDidChangeRemoteKernels.event); - when(localKernelSpecFinder.kind).thenReturn(ContributedKernelFinderKind.LocalKernelSpec); - when(localKernelSpecFinder.onDidChangeKernels).thenReturn(onDidChangeLocalKernels.event); - when(localPythonEnvFinder.kind).thenReturn(ContributedKernelFinderKind.LocalPythonEnvironment); - when(localPythonEnvFinder.onDidChangeKernels).thenReturn(onDidChangePythonKernels.event); - when(interpreterService.getInterpreterHash(anything())).thenCall((id) => id); - when(kernelFinder.registered).thenReturn([ - instance(remoteKernelFinder), - instance(localKernelSpecFinder), - instance(localPythonEnvFinder) - ]); - (instance(connection) as any).then = undefined; - when(jupyterConnection.createConnectionInfo(anything())).thenResolve(instance(connection)); - preferredService = new PreferredKernelConnectionService(instance(jupyterConnection)); - disposables.push(preferredService); - }); - teardown(() => (disposables = dispose(disposables))); - suite('Live Remote Kernels (preferred match)', () => { - test('Find preferred kernel spec if there is no exact match for the live kernel connection (match kernel spec name)', async () => { - when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(notebook)).thenResolve( - remoteLiveKernelConnection2.id - ); - when(remoteKernelFinder.status).thenReturn('idle'); - when(remoteKernelFinder.kernels).thenReturn([remoteLiveKernelConnection1, remoteJavaKernelSpec]); - notebookMetadata.kernelspec!.name = remoteJavaKernelSpec.kernelSpec.name; + when(serviceContainer.get(PreferredRemoteKernelIdProvider)).thenReturn( + instance(preferredRemoteKernelProvider) + ); + when(serviceContainer.get(IKernelFinder)).thenReturn(instance(kernelFinder)); + when(serviceContainer.get(IInterpreterService)).thenReturn( + instance(interpreterService) + ); + when(remoteKernelFinder.kind).thenReturn(ContributedKernelFinderKind.Remote); + when(remoteKernelFinder.onDidChangeKernels).thenReturn(onDidChangeRemoteKernels.event); + when(localKernelSpecFinder.kind).thenReturn(ContributedKernelFinderKind.LocalKernelSpec); + when(localKernelSpecFinder.onDidChangeKernels).thenReturn(onDidChangeLocalKernels.event); + when(localPythonEnvFinder.kind).thenReturn(ContributedKernelFinderKind.LocalPythonEnvironment); + when(localPythonEnvFinder.onDidChangeKernels).thenReturn(onDidChangePythonKernels.event); + when(interpreterService.getInterpreterHash(anything())).thenCall((id) => id); + when(kernelFinder.registered).thenReturn([ + instance(remoteKernelFinder), + instance(localKernelSpecFinder), + instance(localPythonEnvFinder) + ]); + (instance(connection) as any).then = undefined; + when(jupyterConnection.createConnectionInfo(anything())).thenResolve(instance(connection)); + when(mockedVSCodeNamespaces.extensions.getExtension(anything())).thenCall((extensionId) => { + if (extensionId === 'vscode.ipynb') { + return { + exports: { + dropCustomMetadata: !useCustomMetadata + } + }; + } else { + return; + } + }); + disposables.push(new Disposable(() => resetVSCodeMocks())); + preferredService = new PreferredKernelConnectionService(instance(jupyterConnection)); + disposables.push(preferredService); + }); + teardown(() => (disposables = dispose(disposables))); + suite('Live Remote Kernels (preferred match)', () => { + test('Find preferred kernel spec if there is no exact match for the live kernel connection (match kernel spec name)', async () => { + when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(notebook)).thenResolve( + remoteLiveKernelConnection2.id + ); + when(remoteKernelFinder.status).thenReturn('idle'); + when(remoteKernelFinder.kernels).thenReturn([remoteLiveKernelConnection1, remoteJavaKernelSpec]); + notebookMetadata.kernelspec!.name = remoteJavaKernelSpec.kernelSpec.name; - const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( - notebook, - instance(remoteKernelFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( + notebook, + instance(remoteKernelFinder), + cancellation.token + ); - assert.strictEqual(preferredKernel, remoteJavaKernelSpec); - }); - test('Find preferred kernel spec if there is no exact match for the live kernel connection (match kernel spec language)', async () => { - when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(notebook)).thenResolve( - remoteLiveKernelConnection2.id - ); - when(remoteKernelFinder.status).thenReturn('idle'); - when(remoteKernelFinder.kernels).thenReturn([remoteLiveKernelConnection1, remoteJavaKernelSpec]); - notebookMetadata.language_info!.name = remoteJavaKernelSpec.kernelSpec.language!; + assert.strictEqual(preferredKernel, remoteJavaKernelSpec); + }); + test('Find preferred kernel spec if there is no exact match for the live kernel connection (match kernel spec language)', async () => { + when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(notebook)).thenResolve( + remoteLiveKernelConnection2.id + ); + when(remoteKernelFinder.status).thenReturn('idle'); + when(remoteKernelFinder.kernels).thenReturn([remoteLiveKernelConnection1, remoteJavaKernelSpec]); + notebookMetadata.language_info!.name = remoteJavaKernelSpec.kernelSpec.language!; - const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( - notebook, - instance(remoteKernelFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( + notebook, + instance(remoteKernelFinder), + cancellation.token + ); - assert.strictEqual(preferredKernel, remoteJavaKernelSpec); - }); - // test('Find existing session if there is an exact match for the notebook', async () => { - // notebook = new TestNotebookDocument(Uri.file('/foo/bar/baz/sample.ipynb'), 'jupyter-notebook', { - // custom: { metadata: notebookMetadata } - // }); + assert.strictEqual(preferredKernel, remoteJavaKernelSpec); + }); + // test('Find existing session if there is an exact match for the notebook', async () => { + // notebook = new TestNotebookDocument(Uri.file('/foo/bar/baz/sample.ipynb'), 'jupyter-notebook', { + // custom: { metadata: notebookMetadata } + // }); - // when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(uriEquals(notebook.uri))).thenResolve( - // undefined - // ); - // when(remoteKernelFinder.status).thenReturn('idle'); - // when(remoteKernelFinder.kernels).thenReturn([ - // remoteLiveKernelConnection1, - // remoteLiveJavaKernelConnection, - // remoteJavaKernelSpec - // ]); - // notebookMetadata.language_info!.name = remoteJavaKernelSpec.kernelSpec.language!; + // when(preferredRemoteKernelProvider.getPreferredRemoteKernelId(uriEquals(notebook.uri))).thenResolve( + // undefined + // ); + // when(remoteKernelFinder.status).thenReturn('idle'); + // when(remoteKernelFinder.kernels).thenReturn([ + // remoteLiveKernelConnection1, + // remoteLiveJavaKernelConnection, + // remoteJavaKernelSpec + // ]); + // notebookMetadata.language_info!.name = remoteJavaKernelSpec.kernelSpec.language!; - // const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( - // notebook, - // instance(remoteKernelFinder), - // cancellation.token - // ); + // const preferredKernel = await preferredService.findPreferredRemoteKernelConnection( + // notebook, + // instance(remoteKernelFinder), + // cancellation.token + // ); - // assert.strictEqual(preferredKernel, remoteLiveJavaKernelConnection); - // }); - }); - suite('Local Kernel Specs (preferred match)', () => { - test('No match for notebook when there are no kernels', async () => { - when(localKernelSpecFinder.status).thenReturn('idle'); - when(localKernelSpecFinder.kernels).thenReturn([]); + // assert.strictEqual(preferredKernel, remoteLiveJavaKernelConnection); + // }); + }); + suite('Local Kernel Specs (preferred match)', () => { + test('No match for notebook when there are no kernels', async () => { + when(localKernelSpecFinder.status).thenReturn('idle'); + when(localKernelSpecFinder.kernels).thenReturn([]); - const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( - notebook, - instance(localKernelSpecFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( + notebook, + instance(localKernelSpecFinder), + cancellation.token + ); - assert.isUndefined(preferredKernel); - }); - test('No matches for notebook when kernel spec name & languages do not match', async () => { - when(localKernelSpecFinder.status).thenReturn('idle'); - when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); + assert.isUndefined(preferredKernel); + }); + test('No matches for notebook when kernel spec name & languages do not match', async () => { + when(localKernelSpecFinder.status).thenReturn('idle'); + when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); - const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( - notebook, - instance(localKernelSpecFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( + notebook, + instance(localKernelSpecFinder), + cancellation.token + ); - assert.isUndefined(preferredKernel); - }); - test('Find match for notebook when kernel spec name matches', async () => { - when(localKernelSpecFinder.status).thenReturn('idle'); - when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); - notebookMetadata.kernelspec!.name = localJavaKernelSpec.kernelSpec.name; + assert.isUndefined(preferredKernel); + }); + test('Find match for notebook when kernel spec name matches', async () => { + when(localKernelSpecFinder.status).thenReturn('idle'); + when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); + notebookMetadata.kernelspec!.name = localJavaKernelSpec.kernelSpec.name; - const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( - notebook, - instance(localKernelSpecFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( + notebook, + instance(localKernelSpecFinder), + cancellation.token + ); - assert.strictEqual(preferredKernel, localJavaKernelSpec); - }); - test('Find match for notebook when kernel spec language matches', async () => { - when(localKernelSpecFinder.status).thenReturn('idle'); - when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); - notebookMetadata.language_info!.name = localJavaKernelSpec.kernelSpec.language!; + assert.strictEqual(preferredKernel, localJavaKernelSpec); + }); + test('Find match for notebook when kernel spec language matches', async () => { + when(localKernelSpecFinder.status).thenReturn('idle'); + when(localKernelSpecFinder.kernels).thenReturn([localJavaKernelSpec]); + notebookMetadata.language_info!.name = localJavaKernelSpec.kernelSpec.language!; - const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( - notebook, - instance(localKernelSpecFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredLocalKernelSpecConnection( + notebook, + instance(localKernelSpecFinder), + cancellation.token + ); - assert.strictEqual(preferredKernel, localJavaKernelSpec); - }); - }); - suite('Local Python Env (preferred match)', () => { - test('No matches for notebook when there are no kernels', async () => { - when(localPythonEnvFinder.status).thenReturn('idle'); - when(localPythonEnvFinder.kernels).thenReturn([]); + assert.strictEqual(preferredKernel, localJavaKernelSpec); + }); + }); + suite('Local Python Env (preferred match)', () => { + test('No matches for notebook when there are no kernels', async () => { + when(localPythonEnvFinder.status).thenReturn('idle'); + when(localPythonEnvFinder.kernels).thenReturn([]); - const preferredKernel = await preferredService.findPreferredPythonKernelConnection( - notebook, - instance(localPythonEnvFinder), - cancellation.token - ); + const preferredKernel = await preferredService.findPreferredPythonKernelConnection( + notebook, + instance(localPythonEnvFinder), + cancellation.token + ); - assert.isUndefined(preferredKernel); - }); - }); + assert.isUndefined(preferredKernel); + }); + }); + } + ); }); diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index ddfbd800a59..78c0839cecf 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -11,9 +11,9 @@ import { TextDocument, Uri, WorkspaceEdit, + extensions, workspace, - type NotebookCell, - type NotebookCellData + type NotebookCell } from 'vscode'; import { InteractiveWindowView, @@ -168,47 +168,71 @@ export type NotebookMetadata = nbformat.INotebookMetadata & { }; export function getNotebookMetadata(document: NotebookDocument | NotebookData): NotebookMetadata | undefined { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const notebookContent: undefined | Partial = document.metadata?.custom as any; - // Create a clone. - return JSON.parse(JSON.stringify(notebookContent?.metadata || {})); + if (useCustomMetadata()) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const notebookContent: undefined | Partial = document.metadata?.custom as any; + // Create a clone. + return JSON.parse(JSON.stringify(notebookContent?.metadata || {})); + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const notebookContent: undefined | Partial = document.metadata as any; + // Create a clone. + return JSON.parse(JSON.stringify(notebookContent?.metadata || {})); + } } export function getNotebookFormat(document: NotebookDocument): { nbformat: number | undefined; nbformat_minor: number | undefined; } { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const notebookContent: undefined | Partial = document.metadata?.custom as any; - // Create a clone. - return { - nbformat: notebookContent?.nbformat, - nbformat_minor: notebookContent?.nbformat_minor - }; + if (useCustomMetadata()) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const notebookContent: undefined | Partial = document.metadata?.custom as any; + // Create a clone. + return { + nbformat: notebookContent?.nbformat, + nbformat_minor: notebookContent?.nbformat_minor + }; + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const notebookContent: undefined | Partial = document.metadata as any; + // Create a clone. + return { + nbformat: notebookContent?.nbformat, + nbformat_minor: notebookContent?.nbformat_minor + }; + } } export async function updateNotebookMetadata(document: NotebookDocument, metadata: NotebookMetadata) { const edit = new WorkspaceEdit(); - // Create a clone. - const docMetadata = JSON.parse( - JSON.stringify( - (document.metadata as { - custom?: Exclude, 'cells'>; - }) || { custom: {} } - ) - ); - - docMetadata.custom = docMetadata.custom || {}; - docMetadata.custom.metadata = metadata; - - edit.set(document.uri, [ - NotebookEdit.updateNotebookMetadata( - sortObjectPropertiesRecursively({ - ...(document.metadata || {}), - custom: docMetadata.custom - }) - ) - ]); + if (useCustomMetadata()) { + // Create a clone. + const docMetadata: { + custom?: Exclude, 'cells'>; + } = JSON.parse(JSON.stringify(document.metadata || { custom: {} })); + + docMetadata.custom = docMetadata.custom || {}; + docMetadata.custom.metadata = metadata; + + edit.set(document.uri, [ + NotebookEdit.updateNotebookMetadata( + sortObjectPropertiesRecursively({ + ...(document.metadata || {}), + custom: docMetadata.custom + }) + ) + ]); + } else { + edit.set(document.uri, [ + NotebookEdit.updateNotebookMetadata( + sortObjectPropertiesRecursively({ + ...(document.metadata || {}), + metadata + }) + ) + ]); + } await workspace.applyEdit(edit); } @@ -430,21 +454,28 @@ type JupyterCellMetadata = Pick; -export function getCellMetadata(cell: NotebookCell | NotebookCellData): JupyterCellMetadata { - const metadata: JupyterCellMetadata = cell.metadata?.custom || {}; - const cellMetadata = metadata as nbformat.IRawCell; - // metadata property is never optional. - cellMetadata.metadata = cellMetadata.metadata || {}; +export function getCellMetadata(cell: NotebookCell): JupyterCellMetadata { + if (useCustomMetadata()) { + const metadata: JupyterCellMetadata = cell.metadata.custom || {}; + const cellMetadata = metadata as nbformat.IRawCell; + // metadata property is never optional. + cellMetadata.metadata = cellMetadata.metadata || {}; - return metadata; + return metadata; + } else { + const metadata: JupyterCellMetadata = cell.metadata.metadata || {}; + // metadata property is never optional. + metadata.metadata = metadata.metadata || {}; + return metadata; + } } -// function useCustomMetadata() { -// if (extensions.getExtension('vscode.ipynb')?.exports.dropCustomMetadata) { -// return false; -// } -// return true; -// } +export function useCustomMetadata() { + if (extensions.getExtension('vscode.ipynb')?.exports.dropCustomMetadata) { + return false; + } + return true; +} /** * Sort the JSON to minimize unnecessary SCM changes. diff --git a/src/standalone/import-export/importTracker.unit.test.ts b/src/standalone/import-export/importTracker.unit.test.ts index c7185b42edb..d226d920f88 100644 --- a/src/standalone/import-export/importTracker.unit.test.ts +++ b/src/standalone/import-export/importTracker.unit.test.ts @@ -31,184 +31,198 @@ import { waitForCondition } from '../../test/common'; import { createMockedNotebookDocument } from '../../test/datascience/editor-integration/helpers'; import { mockedVSCodeNamespaces } from '../../test/vscode-mock'; -suite('Import Tracker', async () => { - const oldValueOfVSC_JUPYTER_UNIT_TEST = isUnitTestExecution(); - const oldValueOfVSC_JUPYTER_CI_TEST = isTestExecution(); - let importTracker: ImportTracker; - let onDidChangeNotebookCellExecutionState: EventEmitter; - let onDidOpenNbEvent: EventEmitter; - let onDidCloseNbEvent: EventEmitter; - let onDidSaveNbEvent: EventEmitter; - let pandasHash: string; - let elephasHash: string; - let kerasHash: string; - let pysparkHash: string; - let sparkdlHash: string; - let numpyHash: string; - let scipyHash: string; - let sklearnHash: string; - let randomHash: string; - let disposables: IDisposable[] = []; - class Reporter { - public static eventNames: string[] = []; - public static properties: Record[] = []; - public static measures: {}[] = []; - - public static async expectHashes( - when: 'onExecution' | 'onOpenCloseOrSave' = 'onOpenCloseOrSave', - resourceType: ResourceTypeTelemetryProperty['resourceType'] = undefined, - ...hashes: string[] - ) { - if (hashes.length > 0) { - await waitForCondition( - async () => { - expect(Reporter.eventNames).to.contain(EventName.HASHED_PACKAGE_NAME); - return true; - }, - 1_000, - 'Hashed package name event not sent' - ); - expect(Reporter.eventNames).to.contain(EventName.HASHED_PACKAGE_NAME); - await waitForCondition( - async () => { - Reporter.properties.filter((item) => Object.keys(item).length).length === hashes.length; - return true; - }, - 1_000, - () => - `Incorrect number of hashed package name events sent. Expected ${hashes.length}, got ${ - Reporter.properties.filter((item) => Object.keys(item).length).length - }, with values ${JSON.stringify( - Reporter.properties.filter((item) => Object.keys(item).length) - )}` +[true, false].forEach((useCustomMetadata) => { + suite(`Import Tracker (${useCustomMetadata ? 'with custom metadata' : 'without custom metadata'})`, async () => { + const oldValueOfVSC_JUPYTER_UNIT_TEST = isUnitTestExecution(); + const oldValueOfVSC_JUPYTER_CI_TEST = isTestExecution(); + let importTracker: ImportTracker; + let onDidChangeNotebookCellExecutionState: EventEmitter; + let onDidOpenNbEvent: EventEmitter; + let onDidCloseNbEvent: EventEmitter; + let onDidSaveNbEvent: EventEmitter; + let pandasHash: string; + let elephasHash: string; + let kerasHash: string; + let pysparkHash: string; + let sparkdlHash: string; + let numpyHash: string; + let scipyHash: string; + let sklearnHash: string; + let randomHash: string; + let disposables: IDisposable[] = []; + class Reporter { + public static eventNames: string[] = []; + public static properties: Record[] = []; + public static measures: {}[] = []; + + public static async expectHashes( + when: 'onExecution' | 'onOpenCloseOrSave' = 'onOpenCloseOrSave', + resourceType: ResourceTypeTelemetryProperty['resourceType'] = undefined, + ...hashes: string[] + ) { + if (hashes.length > 0) { + await waitForCondition( + async () => { + expect(Reporter.eventNames).to.contain(EventName.HASHED_PACKAGE_NAME); + return true; + }, + 1_000, + 'Hashed package name event not sent' + ); + expect(Reporter.eventNames).to.contain(EventName.HASHED_PACKAGE_NAME); + await waitForCondition( + async () => { + Reporter.properties.filter((item) => Object.keys(item).length).length === hashes.length; + return true; + }, + 1_000, + () => + `Incorrect number of hashed package name events sent. Expected ${hashes.length}, got ${ + Reporter.properties.filter((item) => Object.keys(item).length).length + }, with values ${JSON.stringify( + Reporter.properties.filter((item) => Object.keys(item).length) + )}` + ); + } + const properties = Reporter.properties.filter((item) => Object.keys(item).length); + const expected = resourceType + ? hashes.map((hash) => ({ hashedNamev2: hash, when, resourceType })) + : hashes.map((hash) => ({ hashedNamev2: hash, when })); + assert.deepEqual( + properties.sort((a, b) => a.hashedNamev2.localeCompare(b.hashedNamev2)), + expected.sort((a, b) => a.hashedNamev2.localeCompare(b.hashedNamev2)), + `Hashes not sent correctly, expected ${JSON.stringify(expected)} but got ${JSON.stringify( + properties + )}` ); } - const properties = Reporter.properties.filter((item) => Object.keys(item).length); - const expected = resourceType - ? hashes.map((hash) => ({ hashedNamev2: hash, when, resourceType })) - : hashes.map((hash) => ({ hashedNamev2: hash, when })); - assert.deepEqual( - properties.sort((a, b) => a.hashedNamev2.localeCompare(b.hashedNamev2)), - expected.sort((a, b) => a.hashedNamev2.localeCompare(b.hashedNamev2)), - `Hashes not sent correctly, expected ${JSON.stringify(expected)} but got ${JSON.stringify(properties)}` - ); - } - public sendTelemetryEvent(eventName: string, properties?: {}, measures?: {}) { - Reporter.eventNames.push(eventName); - Reporter.properties.push(properties!); - Reporter.measures.push(measures!); + public sendTelemetryEvent(eventName: string, properties?: {}, measures?: {}) { + Reporter.eventNames.push(eventName); + Reporter.properties.push(properties!); + Reporter.measures.push(measures!); + } } - } - suiteSetup(async () => { - pandasHash = await getTelemetrySafeHashedString('pandas'); - elephasHash = await getTelemetrySafeHashedString('elephas'); - kerasHash = await getTelemetrySafeHashedString('keras'); - pysparkHash = await getTelemetrySafeHashedString('pyspark'); - sparkdlHash = await getTelemetrySafeHashedString('sparkdl'); - numpyHash = await getTelemetrySafeHashedString('numpy'); - scipyHash = await getTelemetrySafeHashedString('scipy'); - sklearnHash = await getTelemetrySafeHashedString('sklearn'); - randomHash = await getTelemetrySafeHashedString('random'); - }); - setup(() => { - const reporter = getTelemetryReporter(); - sinon.stub(reporter, 'sendTelemetryEvent').callsFake((eventName: string, properties?: {}, measures?: {}) => { - Reporter.eventNames.push(eventName); - Reporter.properties.push(properties!); - Reporter.measures.push(measures!); + suiteSetup(async () => { + pandasHash = await getTelemetrySafeHashedString('pandas'); + elephasHash = await getTelemetrySafeHashedString('elephas'); + kerasHash = await getTelemetrySafeHashedString('keras'); + pysparkHash = await getTelemetrySafeHashedString('pyspark'); + sparkdlHash = await getTelemetrySafeHashedString('sparkdl'); + numpyHash = await getTelemetrySafeHashedString('numpy'); + scipyHash = await getTelemetrySafeHashedString('scipy'); + sklearnHash = await getTelemetrySafeHashedString('sklearn'); + randomHash = await getTelemetrySafeHashedString('random'); + }); + setup(() => { + const reporter = getTelemetryReporter(); + sinon + .stub(reporter, 'sendTelemetryEvent') + .callsFake((eventName: string, properties?: {}, measures?: {}) => { + Reporter.eventNames.push(eventName); + Reporter.properties.push(properties!); + Reporter.measures.push(measures!); + }); + setTestExecution(false); + setUnitTestExecution(false); + + onDidOpenNbEvent = new EventEmitter(); + onDidCloseNbEvent = new EventEmitter(); + onDidSaveNbEvent = new EventEmitter(); + onDidChangeNotebookCellExecutionState = new EventEmitter(); + disposables.push(onDidOpenNbEvent); + disposables.push(onDidCloseNbEvent); + disposables.push(onDidSaveNbEvent); + when(mockedVSCodeNamespaces.workspace.onDidOpenNotebookDocument).thenReturn(onDidOpenNbEvent.event); + when(mockedVSCodeNamespaces.workspace.onDidCloseNotebookDocument).thenReturn(onDidCloseNbEvent.event); + when(mockedVSCodeNamespaces.workspace.onDidSaveNotebookDocument).thenReturn(onDidSaveNbEvent.event); + when(mockedVSCodeNamespaces.notebooks.onDidChangeNotebookCellExecutionState).thenReturn( + onDidChangeNotebookCellExecutionState.event + ); + when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([]); + when(mockedVSCodeNamespaces.workspace.getConfiguration('telemetry')).thenReturn({ + inspect: () => { + return { + key: 'enableTelemetry', + globalValue: true + }; + } + } as any); + importTracker = new ImportTracker(disposables); + }); + teardown(() => { + sinon.restore(); + setUnitTestExecution(oldValueOfVSC_JUPYTER_UNIT_TEST); + setTestExecution(oldValueOfVSC_JUPYTER_CI_TEST); + Reporter.properties = []; + Reporter.eventNames = []; + Reporter.measures = []; + disposables = dispose(disposables); }); - setTestExecution(false); - setUnitTestExecution(false); - - onDidOpenNbEvent = new EventEmitter(); - onDidCloseNbEvent = new EventEmitter(); - onDidSaveNbEvent = new EventEmitter(); - onDidChangeNotebookCellExecutionState = new EventEmitter(); - disposables.push(onDidOpenNbEvent); - disposables.push(onDidCloseNbEvent); - disposables.push(onDidSaveNbEvent); - when(mockedVSCodeNamespaces.workspace.onDidOpenNotebookDocument).thenReturn(onDidOpenNbEvent.event); - when(mockedVSCodeNamespaces.workspace.onDidCloseNotebookDocument).thenReturn(onDidCloseNbEvent.event); - when(mockedVSCodeNamespaces.workspace.onDidSaveNotebookDocument).thenReturn(onDidSaveNbEvent.event); - when(mockedVSCodeNamespaces.notebooks.onDidChangeNotebookCellExecutionState).thenReturn( - onDidChangeNotebookCellExecutionState.event - ); - when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([]); - when(mockedVSCodeNamespaces.workspace.getConfiguration('telemetry')).thenReturn({ - inspect: () => { - return { - key: 'enableTelemetry', - globalValue: true - }; - } - } as any); - importTracker = new ImportTracker(disposables); - }); - teardown(() => { - sinon.restore(); - setUnitTestExecution(oldValueOfVSC_JUPYTER_UNIT_TEST); - setTestExecution(oldValueOfVSC_JUPYTER_CI_TEST); - Reporter.properties = []; - Reporter.eventNames = []; - Reporter.measures = []; - disposables = dispose(disposables); - }); - test('Open document', async () => { - const code = `import pandas\r\n`; - const nb = createMockedNotebookDocument([{ kind: NotebookCellKind.Code, languageId: 'python', value: code }]); - onDidOpenNbEvent.fire(nb); + test('Open document', async () => { + const code = `import pandas\r\n`; + const nb = createMockedNotebookDocument(useCustomMetadata, [ + { kind: NotebookCellKind.Code, languageId: 'python', value: code } + ]); + onDidOpenNbEvent.fire(nb); - await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); - }); - test('Close document', async () => { - const code = `import pandas\r\n`; - const nb = createMockedNotebookDocument([{ kind: NotebookCellKind.Code, languageId: 'python', value: code }]); - onDidCloseNbEvent.fire(nb); + await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); + }); + test('Close document', async () => { + const code = `import pandas\r\n`; + const nb = createMockedNotebookDocument(useCustomMetadata, [ + { kind: NotebookCellKind.Code, languageId: 'python', value: code } + ]); + onDidCloseNbEvent.fire(nb); + + await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); + }); + test('Save document', async () => { + const code = `import pandas\r\n`; + const nb = createMockedNotebookDocument(useCustomMetadata, [ + { kind: NotebookCellKind.Code, languageId: 'python', value: code } + ]); + onDidSaveNbEvent.fire(nb); + + await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); + }); - await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); - }); - test('Save document', async () => { - const code = `import pandas\r\n`; - const nb = createMockedNotebookDocument([{ kind: NotebookCellKind.Code, languageId: 'python', value: code }]); - onDidSaveNbEvent.fire(nb); + test('Already opened documents', async () => { + const code = `import pandas\r\n`; + const nb = createMockedNotebookDocument(useCustomMetadata, [ + { kind: NotebookCellKind.Code, languageId: 'python', value: code } + ]); + when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([nb]); - await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); - }); + await importTracker.activate(); - test('Already opened documents', async () => { - const code = `import pandas\r\n`; - const nb = createMockedNotebookDocument([{ kind: NotebookCellKind.Code, languageId: 'python', value: code }]); - when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([nb]); + await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); + }); + async function testImports( + code: string, + notebookType: typeof JupyterNotebookView | typeof InteractiveWindowView, + ...expectedPackageHashes: string[] + ) { + const nb = createMockedNotebookDocument( + useCustomMetadata, + [{ kind: NotebookCellKind.Code, languageId: 'python', value: code }], + undefined, + undefined, + notebookType + ); + when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([nb]); - await importTracker.activate(); + await importTracker.activate(); - await Reporter.expectHashes('onOpenCloseOrSave', 'notebook', pandasHash); - }); - async function testImports( - code: string, - notebookType: typeof JupyterNotebookView | typeof InteractiveWindowView, - ...expectedPackageHashes: string[] - ) { - const nb = createMockedNotebookDocument( - [{ kind: NotebookCellKind.Code, languageId: 'python', value: code }], - undefined, - undefined, - notebookType - ); - when(mockedVSCodeNamespaces.workspace.notebookDocuments).thenReturn([nb]); - - await importTracker.activate(); - - await Reporter.expectHashes( - 'onOpenCloseOrSave', - notebookType === 'jupyter-notebook' ? 'notebook' : 'interactive', - ...expectedPackageHashes - ); - } - test('from ._ import _, _', async () => { - const code = ` + await Reporter.expectHashes( + 'onOpenCloseOrSave', + notebookType === 'jupyter-notebook' ? 'notebook' : 'interactive', + ...expectedPackageHashes + ); + } + test('from ._ import _, _', async () => { + const code = ` from elephas.java import java_classes, adapter from keras.models import Sequential from keras.layers import Dense @@ -227,11 +241,11 @@ suite('Import Tracker', async () => { weights = adapter.retrieve_keras_weights(java_model) model.set_weights(weights)`; - await testImports(code, 'jupyter-notebook', elephasHash, kerasHash); - }); + await testImports(code, 'jupyter-notebook', elephasHash, kerasHash); + }); - test('from ._ import _', async () => { - const code = `from pyspark.ml.classification import LogisticRegression + test('from ._ import _', async () => { + const code = `from pyspark.ml.classification import LogisticRegression from pyspark.ml.evaluation import MulticlassClassificationEvaluator from pyspark.ml import Pipeline from sparkdl import DeepImageFeaturizer @@ -248,11 +262,11 @@ suite('Import Tracker', async () => { evaluator = MulticlassClassificationEvaluator(metricName="accuracy") print("Training set accuracy = " + str(evaluator.evaluate(predictionAndLabels)))`; - await testImports(code, 'interactive', pysparkHash, sparkdlHash); - }); + await testImports(code, 'interactive', pysparkHash, sparkdlHash); + }); - test('import as _', async () => { - const code = `import pandas as pd + test('import as _', async () => { + const code = `import pandas as pd import numpy as np import random as rnd @@ -264,11 +278,11 @@ suite('Import Tracker', async () => { df.Age = categories return df`; - await testImports(code, 'interactive', pandasHash, numpyHash, randomHash); - }); + await testImports(code, 'interactive', pandasHash, numpyHash, randomHash); + }); - test('from import _', async () => { - const code = `from scipy import special + test('from import _', async () => { + const code = `from scipy import special def drumhead_height(n, k, distance, angle, t): kth_zero = special.jn_zeros(n, k)[-1] return np.cos(t) * np.cos(n*angle) * special.jn(n, distance*kth_zero) @@ -278,16 +292,16 @@ suite('Import Tracker', async () => { y = np.array([r * np.sin(theta) for r in radius]) z = np.array([drumhead_height(1, 1, r, theta, 0.5) for r in radius])`; - await testImports(code, 'interactive', scipyHash); - }); + await testImports(code, 'interactive', scipyHash); + }); - test('from import _ as _', async () => { - const code = `from pandas import DataFrame as df`; - await testImports(code, 'jupyter-notebook', pandasHash); - }); + test('from import _ as _', async () => { + const code = `from pandas import DataFrame as df`; + await testImports(code, 'jupyter-notebook', pandasHash); + }); - test('import , ', async () => { - const code = ` + test('import , ', async () => { + const code = ` def drumhead_height(n, k, distance, angle, t): import sklearn, pandas return np.cos(t) * np.cos(n*angle) * special.jn(n, distance*kth_zero) @@ -296,11 +310,11 @@ suite('Import Tracker', async () => { x = np.array([r * np.cos(theta) for r in radius]) y = np.array([r * np.sin(theta) for r in radius]) z = np.array([drumhead_height(1, 1, r, theta, 0.5) for r in radius])`; - await testImports(code, 'interactive', sklearnHash, pandasHash); - }); + await testImports(code, 'interactive', sklearnHash, pandasHash); + }); - test('Import from within a function', async () => { - const code = ` + test('Import from within a function', async () => { + const code = ` def drumhead_height(n, k, distance, angle, t): import sklearn as sk return np.cos(t) * np.cos(n*angle) * special.jn(n, distance*kth_zero) @@ -310,35 +324,53 @@ suite('Import Tracker', async () => { y = np.array([r * np.sin(theta) for r in radius]) z = np.array([drumhead_height(1, 1, r, theta, 0.5) for r in radius])`; - await testImports(code, 'interactive', sklearnHash); - }); + await testImports(code, 'interactive', sklearnHash); + }); - test('Do not send the same package twice', async () => { - const code = ` + test('Do not send the same package twice', async () => { + const code = ` import pandas import pandas`; - await testImports(code, 'interactive', pandasHash); - }); + await testImports(code, 'interactive', pandasHash); + }); - test('Ignore relative imports', async () => { - const code = 'from .pandas import not_real'; - await testImports(code, 'interactive'); - }); - test('Track packages when a cell is executed', async () => { - const code = `import numpy`; - const nb = createMockedNotebookDocument([{ kind: NotebookCellKind.Code, languageId: 'python', value: code }]); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Pending }); - - await Reporter.expectHashes('onExecution', 'notebook', numpyHash); - - // Executing the cell multiple will have no effect, the telemetry is only sent once. - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Pending }); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Executing }); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Idle }); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Pending }); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Executing }); - onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Idle }); - - await Reporter.expectHashes('onExecution', 'notebook', numpyHash); + test('Ignore relative imports', async () => { + const code = 'from .pandas import not_real'; + await testImports(code, 'interactive'); + }); + test('Track packages when a cell is executed', async () => { + const code = `import numpy`; + const nb = createMockedNotebookDocument(useCustomMetadata, [ + { kind: NotebookCellKind.Code, languageId: 'python', value: code } + ]); + onDidChangeNotebookCellExecutionState.fire({ + cell: nb.cellAt(0), + state: NotebookCellExecutionState.Pending + }); + + await Reporter.expectHashes('onExecution', 'notebook', numpyHash); + + // Executing the cell multiple will have no effect, the telemetry is only sent once. + onDidChangeNotebookCellExecutionState.fire({ + cell: nb.cellAt(0), + state: NotebookCellExecutionState.Pending + }); + onDidChangeNotebookCellExecutionState.fire({ + cell: nb.cellAt(0), + state: NotebookCellExecutionState.Executing + }); + onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Idle }); + onDidChangeNotebookCellExecutionState.fire({ + cell: nb.cellAt(0), + state: NotebookCellExecutionState.Pending + }); + onDidChangeNotebookCellExecutionState.fire({ + cell: nb.cellAt(0), + state: NotebookCellExecutionState.Executing + }); + onDidChangeNotebookCellExecutionState.fire({ cell: nb.cellAt(0), state: NotebookCellExecutionState.Idle }); + + await Reporter.expectHashes('onExecution', 'notebook', numpyHash); + }); }); }); diff --git a/src/standalone/import-export/jupyterExporter.ts b/src/standalone/import-export/jupyterExporter.ts index 14f36780686..a4d5a1e5a57 100644 --- a/src/standalone/import-export/jupyterExporter.ts +++ b/src/standalone/import-export/jupyterExporter.ts @@ -14,6 +14,7 @@ import { openAndShowNotebook } from '../../platform/common/utils/notebooks'; import { noop } from '../../platform/common/utils/misc'; import { IDataScienceErrorHandler } from '../../kernels/errors/types'; import { getVersion } from '../../platform/interpreter/helpers'; +import { useCustomMetadata } from '../../platform/common/utils'; /** * Provides export for the interactive window @@ -86,13 +87,19 @@ export class JupyterExporter implements INotebookExporter { throw new Error('vscode.ipynb extension not found'); } const notebook = new NotebookData(cells); - notebook.metadata = { - custom: { - metadata, - nbformat: defaultNotebookFormat.major, - nbformat_minor: defaultNotebookFormat.minor - } - }; + notebook.metadata = useCustomMetadata() + ? { + custom: { + metadata, + nbformat: defaultNotebookFormat.major, + nbformat_minor: defaultNotebookFormat.minor + } + } + : { + metadata, + nbformat: defaultNotebookFormat.major, + nbformat_minor: defaultNotebookFormat.minor + }; return ipynbMain.exportNotebook(notebook); } private extractPythonMainVersion = async (): Promise => { diff --git a/src/standalone/recommendation/extensionRecommendation.unit.test.ts b/src/standalone/recommendation/extensionRecommendation.unit.test.ts index 862a6a616a2..0c779722888 100644 --- a/src/standalone/recommendation/extensionRecommendation.unit.test.ts +++ b/src/standalone/recommendation/extensionRecommendation.unit.test.ts @@ -15,240 +15,261 @@ import { JupyterNotebookView } from '../../platform/common/constants'; import { IControllerRegistration } from '../../notebooks/controllers/types'; import { mockedVSCodeNamespaces, resetVSCodeMocks } from '../../test/vscode-mock'; -/* eslint-disable @typescript-eslint/no-explicit-any */ -suite('Extension Recommendation', () => { - ['kernelspec', 'language_info'].forEach((whereIsLanguageDefined) => { - ['csharp', 'fsharp', 'powershell'].forEach((languageToBeTested) => { - suite(`Notebook language '${languageToBeTested}' defined in ${whereIsLanguageDefined}`, () => { - let disposables: IDisposable[] = []; - let recommendation: ExtensionRecommendationService; - let memento: Memento; - let controllerRegistration: IControllerRegistration; - let onDidOpenNotebookDocument: EventEmitter; - let onNotebookControllerSelected: EventEmitter<{ - notebook: NotebookDocument; - controller: VSCodeNotebookController; - }>; - setup(() => { - startNewSession(); - }); - function startNewSession() { - resetVSCodeMocks(); - onDidOpenNotebookDocument = new EventEmitter(); - onNotebookControllerSelected = new EventEmitter<{ - notebook: NotebookDocument; - controller: VSCodeNotebookController; - }>(); - when(mockedVSCodeNamespaces.workspace.onDidOpenNotebookDocument).thenReturn( - onDidOpenNotebookDocument.event - ); - controllerRegistration = mock(); - when(controllerRegistration.onControllerSelected).thenReturn(onNotebookControllerSelected.event); - memento = mock(); - recommendation = new ExtensionRecommendationService( - instance(controllerRegistration), - disposables, - instance(memento) - ); +[true, false].forEach((useCustomMetadata) => { + /* eslint-disable @typescript-eslint/no-explicit-any */ + suite( + `Extension Recommendation (${useCustomMetadata ? 'with custom metadata' : 'without custom metadata'})`, + () => { + ['kernelspec', 'language_info'].forEach((whereIsLanguageDefined) => { + ['csharp', 'fsharp', 'powershell'].forEach((languageToBeTested) => { + suite(`Notebook language '${languageToBeTested}' defined in ${whereIsLanguageDefined}`, () => { + let disposables: IDisposable[] = []; + let recommendation: ExtensionRecommendationService; + let memento: Memento; + let controllerRegistration: IControllerRegistration; + let onDidOpenNotebookDocument: EventEmitter; + let onNotebookControllerSelected: EventEmitter<{ + notebook: NotebookDocument; + controller: VSCodeNotebookController; + }>; + setup(() => { + startNewSession(); + }); + function startNewSession() { + resetVSCodeMocks(); + onDidOpenNotebookDocument = new EventEmitter(); + onNotebookControllerSelected = new EventEmitter<{ + notebook: NotebookDocument; + controller: VSCodeNotebookController; + }>(); + when(mockedVSCodeNamespaces.workspace.onDidOpenNotebookDocument).thenReturn( + onDidOpenNotebookDocument.event + ); + controllerRegistration = mock(); + when(controllerRegistration.onControllerSelected).thenReturn( + onNotebookControllerSelected.event + ); + memento = mock(); + recommendation = new ExtensionRecommendationService( + instance(controllerRegistration), + disposables, + instance(memento) + ); - when( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).thenReturn(); - when(mockedVSCodeNamespaces.extensions.getExtension(anything())).thenReturn(); - when(memento.get(anything(), anything())).thenReturn([]); - recommendation.activate(); - } - teardown(() => { - disposables = dispose(disposables); - resetVSCodeMocks(); - }); - function createNotebook(language: string) { - const notebook = mock(); - const kernelSpecLanguage = whereIsLanguageDefined === 'kernelspec' ? language : undefined; - const languageInfoLanguage = whereIsLanguageDefined === 'kernelspec' ? '' : language; - const notebookContent: Partial = { - metadata: { - orig_nbformat: 1, - kernelspec: { - display_name: 'Hello', - name: 'hello', - language: kernelSpecLanguage - }, - language_info: { - name: languageInfoLanguage - } + when( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).thenReturn(); + when(mockedVSCodeNamespaces.extensions.getExtension(anything())).thenCall((extensionId) => { + if (extensionId === 'vscode.ipynb') { + return { + exports: { + dropCustomMetadata: !useCustomMetadata + } + }; + } else { + return; + } + }); + when(memento.get(anything(), anything())).thenReturn([]); + recommendation.activate(); } - }; - when(notebook.notebookType).thenReturn(JupyterNotebookView); - when(notebook.metadata).thenReturn({ custom: notebookContent } as any); - return instance(notebook); - } - function createController(language: string) { - const controller = mock(); - const kernelSpec: IJupyterKernelSpec = { - language - } as any; - when(controller.connection).thenReturn( - LocalKernelSpecConnectionMetadata.create({ kernelSpec, id: '' }) - ); - return instance(controller); - } - test('No recommendations for python Notebooks', async () => { - const nb = createNotebook('python'); - onDidOpenNotebookDocument.fire(nb); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).never(); - }); - test('No recommendations for python kernels', async () => { - const notebook = createNotebook(''); - const controller = createController('python'); - onNotebookControllerSelected.fire({ notebook, controller }); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).never(); - }); - test('No recommendations for julia Notebooks', async () => { - const nb = createNotebook('julia'); - onDidOpenNotebookDocument.fire(nb); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).never(); - }); - test('No recommendations for julia kernels', async () => { - const notebook = createNotebook(''); - const controller = createController('julia'); - onNotebookControllerSelected.fire({ notebook, controller }); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).never(); - }); - test(`Got recommendations once per session when opening a notebook`, async () => { - const nb = createNotebook(languageToBeTested); - const nb2 = createNotebook(languageToBeTested); - onDidOpenNotebookDocument.fire(nb); - onDidOpenNotebookDocument.fire(nb); - onDidOpenNotebookDocument.fire(nb2); - onDidOpenNotebookDocument.fire(nb2); + teardown(() => { + disposables = dispose(disposables); + resetVSCodeMocks(); + }); + function createNotebook(language: string) { + const notebook = mock(); + const kernelSpecLanguage = whereIsLanguageDefined === 'kernelspec' ? language : undefined; + const languageInfoLanguage = whereIsLanguageDefined === 'kernelspec' ? '' : language; + const notebookContent: Partial = { + metadata: { + orig_nbformat: 1, + kernelspec: { + display_name: 'Hello', + name: 'hello', + language: kernelSpecLanguage + }, + language_info: { + name: languageInfoLanguage + } + } + }; + when(notebook.notebookType).thenReturn(JupyterNotebookView); + when(notebook.metadata).thenReturn( + useCustomMetadata ? ({ custom: notebookContent } as any) : notebookContent + ); + return instance(notebook); + } + function createController(language: string) { + const controller = mock(); + const kernelSpec: IJupyterKernelSpec = { + language + } as any; + when(controller.connection).thenReturn( + LocalKernelSpecConnectionMetadata.create({ kernelSpec, id: '' }) + ); + return instance(controller); + } + test('No recommendations for python Notebooks', async () => { + const nb = createNotebook('python'); + onDidOpenNotebookDocument.fire(nb); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).never(); + }); + test('No recommendations for python kernels', async () => { + const notebook = createNotebook(''); + const controller = createController('python'); + onNotebookControllerSelected.fire({ notebook, controller }); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).never(); + }); + test('No recommendations for julia Notebooks', async () => { + const nb = createNotebook('julia'); + onDidOpenNotebookDocument.fire(nb); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).never(); + }); + test('No recommendations for julia kernels', async () => { + const notebook = createNotebook(''); + const controller = createController('julia'); + onNotebookControllerSelected.fire({ notebook, controller }); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).never(); + }); + test(`Got recommendations once per session when opening a notebook`, async () => { + const nb = createNotebook(languageToBeTested); + const nb2 = createNotebook(languageToBeTested); + onDidOpenNotebookDocument.fire(nb); + onDidOpenNotebookDocument.fire(nb); + onDidOpenNotebookDocument.fire(nb2); + onDidOpenNotebookDocument.fire(nb2); - // Only one prompt regardless of how many notebooks were opened. - const expectedMessage = `The [.NET Interactive Notebooks Preview](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension is recommended for notebooks targeting the language '${languageToBeTested}'`; - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - expectedMessage, - Common.bannerLabelYes, - Common.bannerLabelNo, - Common.doNotShowAgain - ) - ).once(); - }); - test(`Got recommendations once per session when selecting a kernel`, async () => { - const notebook = createNotebook('python'); - const notebook2 = createNotebook('python'); - const controller = createController(languageToBeTested); - const controller2 = createController(languageToBeTested); - onNotebookControllerSelected.fire({ notebook, controller }); - onNotebookControllerSelected.fire({ notebook: notebook2, controller: controller2 }); - // Only one prompt regardless of how many notebooks were opened. - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).once(); - }); - test(`Never show prompt again when opening a notebook in a new session`, async () => { - when( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).thenResolve(Common.doNotShowAgain as any); + // Only one prompt regardless of how many notebooks were opened. + const expectedMessage = `The [.NET Interactive Notebooks Preview](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension is recommended for notebooks targeting the language '${languageToBeTested}'`; + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + expectedMessage, + Common.bannerLabelYes, + Common.bannerLabelNo, + Common.doNotShowAgain + ) + ).once(); + }); + test(`Got recommendations once per session when selecting a kernel`, async () => { + const notebook = createNotebook('python'); + const notebook2 = createNotebook('python'); + const controller = createController(languageToBeTested); + const controller2 = createController(languageToBeTested); + onNotebookControllerSelected.fire({ notebook, controller }); + onNotebookControllerSelected.fire({ notebook: notebook2, controller: controller2 }); + // Only one prompt regardless of how many notebooks were opened. + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).once(); + }); + test(`Never show prompt again when opening a notebook in a new session`, async () => { + when( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).thenResolve(Common.doNotShowAgain as any); - const nb = createNotebook(languageToBeTested); - onDidOpenNotebookDocument.fire(nb); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).once(); + const nb = createNotebook(languageToBeTested); + onDidOpenNotebookDocument.fire(nb); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).once(); - // New session - startNewSession(); - when(memento.get(anything(), anything())).thenReturn(['ms-dotnettools.dotnet-interactive-vscode']); - const nb2 = createNotebook(languageToBeTested); - onDidOpenNotebookDocument.fire(nb2); - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).never(); - }); - test(`Open extension page to install the recommended extension`, async () => { - when( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).thenResolve(Common.bannerLabelYes as any); - when(mockedVSCodeNamespaces.commands.executeCommand(anything(), anything())).thenResolve(); + // New session + startNewSession(); + when(memento.get(anything(), anything())).thenReturn([ + 'ms-dotnettools.dotnet-interactive-vscode' + ]); + const nb2 = createNotebook(languageToBeTested); + onDidOpenNotebookDocument.fire(nb2); + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).never(); + }); + test(`Open extension page to install the recommended extension`, async () => { + when( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).thenResolve(Common.bannerLabelYes as any); + when(mockedVSCodeNamespaces.commands.executeCommand(anything(), anything())).thenResolve(); - const nb = createNotebook(languageToBeTested); - onDidOpenNotebookDocument.fire(nb); - await sleep(0); // wait for even loop to process pending async calls. - verify( - mockedVSCodeNamespaces.window.showInformationMessage( - anything(), - anything(), - anything(), - anything() - ) - ).once(); - verify( - mockedVSCodeNamespaces.commands.executeCommand( - 'extension.open', - 'ms-dotnettools.dotnet-interactive-vscode' - ) - ).once(); + const nb = createNotebook(languageToBeTested); + onDidOpenNotebookDocument.fire(nb); + await sleep(0); // wait for even loop to process pending async calls. + verify( + mockedVSCodeNamespaces.window.showInformationMessage( + anything(), + anything(), + anything(), + anything() + ) + ).once(); + verify( + mockedVSCodeNamespaces.commands.executeCommand( + 'extension.open', + 'ms-dotnettools.dotnet-interactive-vscode' + ) + ).once(); + }); + }); }); }); - }); - }); + } + ); }); diff --git a/src/test/datascience/editor-integration/helpers.ts b/src/test/datascience/editor-integration/helpers.ts index 66fe470910f..427b15e900e 100644 --- a/src/test/datascience/editor-integration/helpers.ts +++ b/src/test/datascience/editor-integration/helpers.ts @@ -157,6 +157,7 @@ const defaultMetadata = { } }; export function createMockedNotebookDocument( + useCustomMetadata: boolean, cells: NotebookCellData[], metadata: Partial = defaultMetadata, uri: Uri = Uri.file('foo.ipynb'), @@ -178,7 +179,9 @@ export function createMockedNotebookDocument( }; when(notebook.notebookType).thenReturn(notebookType); when(notebook.uri).thenReturn(uri); - when(notebook.metadata).thenReturn({ custom: notebookContent } as never); + when(notebook.metadata).thenReturn( + useCustomMetadata ? ({ custom: notebookContent } as never) : (notebookContent as never) + ); const nbCells = cells.map((data, index) => { const cell = mock(); diff --git a/src/test/datascience/notebook/executionHelper.ts b/src/test/datascience/notebook/executionHelper.ts index d2f94e539ec..34713e37b51 100644 --- a/src/test/datascience/notebook/executionHelper.ts +++ b/src/test/datascience/notebook/executionHelper.ts @@ -21,6 +21,7 @@ import { IKernelController } from '../../../kernels/types'; import { InteractiveWindowView, JupyterNotebookView, PYTHON_LANGUAGE } from '../../../platform/common/constants'; import { ReadWrite } from '../../../platform/common/types'; import { MockNotebookDocuments } from './helper'; +import { useCustomMetadata } from '../../../platform/common/utils'; export function createKernelController(controllerId = '1'): IKernelController { return { @@ -104,7 +105,7 @@ export class TestNotebookDocument implements NotebookDocument { constructor( public readonly uri: Uri = Uri.file(`untitled${Date.now()}.ipynb`), public readonly notebookType: typeof JupyterNotebookView | typeof InteractiveWindowView = JupyterNotebookView, - public metadata = { custom: {} }, + public metadata: {} = useCustomMetadata() ? { custom: {} } : {}, public isUntitled = true, public version: number = 0, public isDirty = false, diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index 60fe9d57a47..de39cfb7839 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -101,6 +101,7 @@ import { ControllerPreferredService } from './controllerPreferredService'; import { JupyterConnection } from '../../../kernels/jupyter/connection/jupyterConnection'; import { JupyterLabHelper } from '../../../kernels/jupyter/session/jupyterLabHelper'; import { getRootFolder } from '../../../platform/common/application/workspace.base'; +import { useCustomMetadata } from '../../../platform/common/utils'; // Running in Conda environments, things can be a little slower. export const defaultNotebookTestTimeout = 60_000; @@ -863,18 +864,29 @@ export async function createNewNotebook() { const language = PYTHON_LANGUAGE; const cell = new NotebookCellData(NotebookCellKind.Code, '', language); const data = new NotebookData([cell]); - data.metadata = { - custom: { - cells: [], - metadata: { - language_info: { - name: language - } - }, - nbformat: defaultNotebookFormat.major, - nbformat_minor: defaultNotebookFormat.minor - } - }; + data.metadata = useCustomMetadata() + ? { + custom: { + cells: [], + metadata: { + language_info: { + name: language + } + }, + nbformat: defaultNotebookFormat.major, + nbformat_minor: defaultNotebookFormat.minor + } + } + : { + cells: [], + metadata: { + language_info: { + name: language + } + }, + nbformat: defaultNotebookFormat.major, + nbformat_minor: defaultNotebookFormat.minor + }; const doc = await workspace.openNotebookDocument(JupyterNotebookView, data); return window.showNotebookDocument(doc); } diff --git a/src/test/standardTest.node.ts b/src/test/standardTest.node.ts index e03c79bfc24..005437f18ee 100644 --- a/src/test/standardTest.node.ts +++ b/src/test/standardTest.node.ts @@ -129,7 +129,9 @@ async function createSettings(): Promise { // To get widgets working. 'jupyter.widgetScriptSources': ['jsdelivr.com', 'unpkg.com'], // New Kernel Picker. - 'notebook.kernelPicker.type': 'mru' + 'notebook.kernelPicker.type': 'mru', + 'jupyter.experimental.dropCustomMetadata': true, + 'jupyter.drop.custom.property': true }; if (IS_SMOKE_TEST()) { defaultSettings['python.languageServer'] = 'None'; From bb6bd7c627b9d55088e1972d47d50ce7ba74a828 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:16:13 +1100 Subject: [PATCH 02/19] Misc changes --- src/platform/common/utils.ts | 11 ++++++++--- src/test/datascience/notebook/helper.ts | 3 ++- src/test/initialize.ts | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 78c0839cecf..3e62dbc65a2 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -471,10 +471,15 @@ export function getCellMetadata(cell: NotebookCell): JupyterCellMetadata { } export function useCustomMetadata() { - if (extensions.getExtension('vscode.ipynb')?.exports.dropCustomMetadata) { - return false; + const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); + return ext?.exports.dropCustomMetadata ? false : true; +} + +export async function activateIPynbExtension() { + const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); + if (ext && !ext.isActive) { + await ext.activate(); } - return true; } /** diff --git a/src/test/datascience/notebook/helper.ts b/src/test/datascience/notebook/helper.ts index de39cfb7839..75e5969740b 100644 --- a/src/test/datascience/notebook/helper.ts +++ b/src/test/datascience/notebook/helper.ts @@ -101,7 +101,7 @@ import { ControllerPreferredService } from './controllerPreferredService'; import { JupyterConnection } from '../../../kernels/jupyter/connection/jupyterConnection'; import { JupyterLabHelper } from '../../../kernels/jupyter/session/jupyterLabHelper'; import { getRootFolder } from '../../../platform/common/application/workspace.base'; -import { useCustomMetadata } from '../../../platform/common/utils'; +import { activateIPynbExtension, useCustomMetadata } from '../../../platform/common/utils'; // Running in Conda environments, things can be a little slower. export const defaultNotebookTestTimeout = 60_000; @@ -835,6 +835,7 @@ export async function prewarmNotebooks() { if (prewarmNotebooksDone.done) { return; } + await activateIPynbExtension(); const { serviceContainer } = await getServices(); await closeActiveWindows(); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index b2b90fecc1b..10c51f899e2 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -24,6 +24,11 @@ export async function initialize(): Promise { } export async function activateExtension() { + // Temporarily until we drop the `custom` property from metadata. + const ext = vscode.extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); + if (ext && !ext.isActive) { + await ext.activate(); + } const extension = vscode.extensions.getExtension(JVSC_EXTENSION_ID_FOR_TESTS)!; const api = await extension.activate(); // Wait until its ready to use. From d3b25e0ffef842253a84702b3e0de3b960a0fa50 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:21:48 +1100 Subject: [PATCH 03/19] Misc --- src/platform/common/utils.ts | 3 ++- src/test/initialize.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 3e62dbc65a2..bf788aeabad 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -22,6 +22,7 @@ import { WIDGET_STATE_MIMETYPE } from './constants'; import { splitLines } from './helpers'; +import { noop } from './utils/misc'; // Can't figure out a better way to do this. Enumerate // the allowed keys of different output formats. @@ -478,7 +479,7 @@ export function useCustomMetadata() { export async function activateIPynbExtension() { const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); if (ext && !ext.isActive) { - await ext.activate(); + await ext.activate().then(noop, noop); } } diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 10c51f899e2..adc04f5a828 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -27,7 +27,7 @@ export async function activateExtension() { // Temporarily until we drop the `custom` property from metadata. const ext = vscode.extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); if (ext && !ext.isActive) { - await ext.activate(); + await ext.activate().then(noop, noop); } const extension = vscode.extensions.getExtension(JVSC_EXTENSION_ID_FOR_TESTS)!; const api = await extension.activate(); From f17243b3dd3b7d716fcab7500c664e982388ce5a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:38:35 +1100 Subject: [PATCH 04/19] Fixces --- src/platform/common/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index bf788aeabad..f98037d8a39 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -473,7 +473,10 @@ export function getCellMetadata(cell: NotebookCell): JupyterCellMetadata { export function useCustomMetadata() { const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); - return ext?.exports.dropCustomMetadata ? false : true; + if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { + return ext.exports.dropCustomMetadata ? false : true; + } + return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); } export async function activateIPynbExtension() { From 4806ff1b1ee534a67696d832aa10f23aeb5608ea Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:39:41 +1100 Subject: [PATCH 05/19] Misc --- src/platform/common/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index f98037d8a39..78c35c67093 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -476,6 +476,8 @@ export function useCustomMetadata() { if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { return ext.exports.dropCustomMetadata ? false : true; } + // Means ipynb extension has not yet been activated. + // Does not matter, we can just check the setting. return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); } From c426f548e8aa55e399aa7d7c6f992bd148faac42 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 11:46:21 +1100 Subject: [PATCH 06/19] misc --- src/platform/common/utils.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 78c35c67093..d12556a1715 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -476,9 +476,14 @@ export function useCustomMetadata() { if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { return ext.exports.dropCustomMetadata ? false : true; } - // Means ipynb extension has not yet been activated. - // Does not matter, we can just check the setting. - return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); + try { + // Means ipynb extension has not yet been activated. + // Does not matter, we can just check the setting. + return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); + } catch { + // This happens in unit tests, in this case just return `true`. + return true; + } } export async function activateIPynbExtension() { From b7eb93213be20b06de2d1dc145d1836140d4b1a6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 12:39:41 +1100 Subject: [PATCH 07/19] More logging --- .../executionAnalysis/symbols.vscode.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index a7574446e85..bc6b72bc5f6 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -494,6 +494,7 @@ function closeAllEditors(): Thenable { (vscode.extensions.getExtension(PylanceExtension) ? suite : suite.skip)('Cell Analysis - Pylance', () => { test('Advanced type dependencies', async () => { + console.error('Step1'); const document = await vscode.workspace.openNotebookDocument( 'jupyter-notebook', new vscode.NotebookData([ @@ -505,8 +506,11 @@ function closeAllEditors(): Thenable { ]) ); + console.error('Step2'); const editor = await vscode.window.showNotebookDocument(document); + console.error('Step3'); const referencesProvider = await activatePylance(); + console.error('Step4'); if (!referencesProvider) { assert.fail('Pylance not found'); } @@ -514,14 +518,18 @@ function closeAllEditors(): Thenable { const documentSymbolTracker = new NotebookDocumentSymbolTracker(editor, referencesProvider); { + console.error('Step5'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(1)); + console.error('Step6'); assert.equal(precedentCellRanges.length, 1); assert.equal(precedentCellRanges[0].start, 0); assert.equal(precedentCellRanges[0].end, 2); } { + console.error('Step7'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(4)); + console.error('Step8'); assert.equal(precedentCellRanges.length, 2); assert.equal(precedentCellRanges[0].start, 2); assert.equal(precedentCellRanges[0].end, 3); @@ -530,13 +538,17 @@ function closeAllEditors(): Thenable { } { + console.error('Step9'); const successorCellRanges = await documentSymbolTracker.getSuccessorCells(document.cellAt(0)); + console.error('Step10'); assert.equal(successorCellRanges.length, 1); assert.equal(successorCellRanges[0].start, 0); assert.equal(successorCellRanges[0].end, 2); } + console.error('Step11'); await closeAllEditors(); + console.error('Step12'); }); test('Advanced type dependencies 2', async () => { From 79cab66905a434cb3af12bc85b987b62d98bb395 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 12:47:28 +1100 Subject: [PATCH 08/19] misc --- src/platform/common/utils.ts | 2 +- src/test/initialize.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index d12556a1715..29e3f2b857b 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -488,7 +488,7 @@ export function useCustomMetadata() { export async function activateIPynbExtension() { const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); - if (ext && !ext.isActive) { + if (ext && ext.isActive === false) { await ext.activate().then(noop, noop); } } diff --git a/src/test/initialize.ts b/src/test/initialize.ts index adc04f5a828..e84811d9422 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -26,7 +26,7 @@ export async function initialize(): Promise { export async function activateExtension() { // Temporarily until we drop the `custom` property from metadata. const ext = vscode.extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); - if (ext && !ext.isActive) { + if (ext && ext.isActive === false) { await ext.activate().then(noop, noop); } const extension = vscode.extensions.getExtension(JVSC_EXTENSION_ID_FOR_TESTS)!; From c6a52c8cfbfdecc1477d62fe48b969f169f3f884 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 12:51:18 +1100 Subject: [PATCH 09/19] test again --- .../executionAnalysis/symbols.vscode.test.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index bc6b72bc5f6..26d057b97bf 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -494,7 +494,7 @@ function closeAllEditors(): Thenable { (vscode.extensions.getExtension(PylanceExtension) ? suite : suite.skip)('Cell Analysis - Pylance', () => { test('Advanced type dependencies', async () => { - console.error('Step1'); + console.error('Step.Pylance.1'); const document = await vscode.workspace.openNotebookDocument( 'jupyter-notebook', new vscode.NotebookData([ @@ -506,11 +506,11 @@ function closeAllEditors(): Thenable { ]) ); - console.error('Step2'); + console.error('Step.Pylance.2'); const editor = await vscode.window.showNotebookDocument(document); - console.error('Step3'); + console.error('Step.Pylance.3'); const referencesProvider = await activatePylance(); - console.error('Step4'); + console.error('Step.Pylance.4'); if (!referencesProvider) { assert.fail('Pylance not found'); } @@ -518,18 +518,18 @@ function closeAllEditors(): Thenable { const documentSymbolTracker = new NotebookDocumentSymbolTracker(editor, referencesProvider); { - console.error('Step5'); + console.error('Step.Pylance.5'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(1)); - console.error('Step6'); + console.error('Step.Pylance.6'); assert.equal(precedentCellRanges.length, 1); assert.equal(precedentCellRanges[0].start, 0); assert.equal(precedentCellRanges[0].end, 2); } { - console.error('Step7'); + console.error('Step.Pylance.7'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(4)); - console.error('Step8'); + console.error('Step.Pylance.8'); assert.equal(precedentCellRanges.length, 2); assert.equal(precedentCellRanges[0].start, 2); assert.equal(precedentCellRanges[0].end, 3); @@ -538,17 +538,17 @@ function closeAllEditors(): Thenable { } { - console.error('Step9'); + console.error('Step.Pylance.9'); const successorCellRanges = await documentSymbolTracker.getSuccessorCells(document.cellAt(0)); - console.error('Step10'); + console.error('Step.Pylance.10'); assert.equal(successorCellRanges.length, 1); assert.equal(successorCellRanges[0].start, 0); assert.equal(successorCellRanges[0].end, 2); } - console.error('Step11'); + console.error('Step.Pylance.11'); await closeAllEditors(); - console.error('Step12'); + console.error('Step.Pylance.12'); }); test('Advanced type dependencies 2', async () => { From 5933f5fd8da0c15bd625da6a066aa6f8dd232f9f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:01:54 +1100 Subject: [PATCH 10/19] even more logging --- src/standalone/executionAnalysis/symbols.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/standalone/executionAnalysis/symbols.ts b/src/standalone/executionAnalysis/symbols.ts index 6bb9bd67daa..a1165ad657b 100644 --- a/src/standalone/executionAnalysis/symbols.ts +++ b/src/standalone/executionAnalysis/symbols.ts @@ -282,6 +282,7 @@ export class NotebookDocumentSymbolTracker { for (const key of _pendingRequestsKeys) { const cell = this._notebookEditor.notebook.getCells().find((cell) => cell.document.uri.fragment === key); if (cell) { + console.error('Step.Pylance.5.2', cell.index); await this._requestCellSymbols(cell, true); } } @@ -318,7 +319,9 @@ export class NotebookDocumentSymbolTracker { } async getPrecedentCells(cell: vscode.NotebookCell) { + console.error('Step.Pylance.5.1'); await this.requestCellSymbolsSync(); + console.error('Step.Pylance.6.0'); const analysis = new CellAnalysis(this._notebookEditor.notebook, this._cellExecution, this._cellRefs); let precedentCells: vscode.NotebookCell[] = []; @@ -433,7 +436,9 @@ export class NotebookDocumentSymbolTracker { if (synchronous) { const cancellationTokenSource = new vscode.CancellationTokenSource(); + console.error('Step.Pylance.5.3', cell.index); await this._doRequestCellSymbols(cell, cancellationTokenSource.token); + console.error('Step.Pylance.5.3.End', cell.index); cancellationTokenSource.dispose(); return; } @@ -469,19 +474,27 @@ export class NotebookDocumentSymbolTracker { } private async _doRequestCellSymbols(cell: vscode.NotebookCell, token: vscode.CancellationToken) { + console.error('Step.Pylance.5.4', cell.index); const symbols = await this._getDocumentSymbols(cell); + console.error('Step.Pylance.5.5', cell.index); if (!symbols) { return; } const references: (LocationWithReferenceKind & { associatedSymbol: ISymbol })[] = []; for (const symbol of symbols) { + console.error( + 'Step.Pylance.5.6', + cell.index, + `${symbol.name}:${symbol.range.start.line}.${symbol.range.start.character}` + ); const symbolReferences = await this._client.getReferences( cell.document, symbol.selectionRange.start, { includeDeclaration: true }, token ); + console.error('Step.Pylance.5.7'); if (symbolReferences) { references.push( ...symbolReferences From a0bbf3089fa5bf85fd3d1b8324be51cb1abbb834 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:04:12 +1100 Subject: [PATCH 11/19] more logging --- src/standalone/executionAnalysis/pylance.ts | 27 ++++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/standalone/executionAnalysis/pylance.ts b/src/standalone/executionAnalysis/pylance.ts index f8d03c08774..93e6c43c8bc 100644 --- a/src/standalone/executionAnalysis/pylance.ts +++ b/src/standalone/executionAnalysis/pylance.ts @@ -72,20 +72,23 @@ export async function activatePylance(): Promise { runPylance(pylanceExtension) - .then(async (client) => { - if (!client) { - console.error('Could not start Pylance'); - reject(); - return; - } + .then( + async (client) => { + if (!client) { + console.error('Could not start Pylance'); + reject(); + return; + } - if (client.client) { - await client.client.start(); - } + if (client.client) { + await client.client.start(); + } - _client = client.notebook; - resolve(client.notebook); - }) + _client = client.notebook; + resolve(client.notebook); + }, + (ex) => console.error(`Failed to start Pylance`, ex) + ) .then(noop, noop); }); } From 9ebeb3d42268a2aa0fd364a4d706e718b93e8a9a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:10:14 +1100 Subject: [PATCH 12/19] misc --- src/standalone/executionAnalysis/symbols.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/standalone/executionAnalysis/symbols.ts b/src/standalone/executionAnalysis/symbols.ts index a1165ad657b..cda7fb1fa7c 100644 --- a/src/standalone/executionAnalysis/symbols.ts +++ b/src/standalone/executionAnalysis/symbols.ts @@ -463,9 +463,11 @@ export class NotebookDocumentSymbolTracker { private async _getDocumentSymbols(cell: vscode.NotebookCell) { if (this._client.getDocumentSymbols) { + console.error('Step.Pylance.5.4 with Pylance', cell.index); const tokenSource = new vscode.CancellationTokenSource(); return this._client.getDocumentSymbols(cell.document, tokenSource.token); } else { + console.error('Step.Pylance.5.4 without Pylance', cell.index); return vscode.commands.executeCommand<(vscode.SymbolInformation & vscode.DocumentSymbol)[] | undefined>( 'vscode.executeDocumentSymbolProvider', cell.document.uri From 69a5bc7820071556db93aab4d0a6fb0593e57683 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:15:40 +1100 Subject: [PATCH 13/19] misc --- src/standalone/executionAnalysis/symbols.vscode.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index 26d057b97bf..fd0d1c1b01d 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -506,6 +506,8 @@ function closeAllEditors(): Thenable { ]) ); + console.error('Step.Pylance.2', JSON.stringify(document.metadata)); + console.error('Step.Pylance.2', JSON.stringify(document.getCells().map((c) => c.metadata))); console.error('Step.Pylance.2'); const editor = await vscode.window.showNotebookDocument(document); console.error('Step.Pylance.3'); From a5a0bf41e27b6ac34a2403c7091527f7be996779 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:16:32 +1100 Subject: [PATCH 14/19] misc --- src/standalone/executionAnalysis/symbols.vscode.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index fd0d1c1b01d..b4a65e03ed2 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -7,6 +7,7 @@ import { anything, instance, mock, when } from 'ts-mockito'; import { CellAnalysis, ICellExecution, ILocationWithReferenceKind, NotebookDocumentSymbolTracker } from './symbols'; import { PylanceExtension } from './common'; import { activatePylance } from './pylance'; +import { sleep } from '../../test/core'; function withNotebookCells(data: [string, string][], fileName: string) { const cells: vscode.NotebookCell[] = data.map((cellDto) => { @@ -511,6 +512,7 @@ function closeAllEditors(): Thenable { console.error('Step.Pylance.2'); const editor = await vscode.window.showNotebookDocument(document); console.error('Step.Pylance.3'); + await sleep(5_000); const referencesProvider = await activatePylance(); console.error('Step.Pylance.4'); if (!referencesProvider) { @@ -595,7 +597,7 @@ function closeAllEditors(): Thenable { } await closeAllEditors(); - }); + }).timeout(30_000); test('Advanced type dependencies 3', async () => { const document = await vscode.workspace.openNotebookDocument( From 6c05f6acc231c219577f523c5181c46f1631948f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:25:14 +1100 Subject: [PATCH 15/19] misc --- src/standalone/executionAnalysis/symbols.vscode.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index b4a65e03ed2..3daf75420c0 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -496,6 +496,7 @@ function closeAllEditors(): Thenable { (vscode.extensions.getExtension(PylanceExtension) ? suite : suite.skip)('Cell Analysis - Pylance', () => { test('Advanced type dependencies', async () => { console.error('Step.Pylance.1'); + await sleep(10_000); const document = await vscode.workspace.openNotebookDocument( 'jupyter-notebook', new vscode.NotebookData([ @@ -512,7 +513,7 @@ function closeAllEditors(): Thenable { console.error('Step.Pylance.2'); const editor = await vscode.window.showNotebookDocument(document); console.error('Step.Pylance.3'); - await sleep(5_000); + await sleep(10_000); const referencesProvider = await activatePylance(); console.error('Step.Pylance.4'); if (!referencesProvider) { @@ -553,7 +554,7 @@ function closeAllEditors(): Thenable { console.error('Step.Pylance.11'); await closeAllEditors(); console.error('Step.Pylance.12'); - }); + }).timeout(60_000); test('Advanced type dependencies 2', async () => { const document = await vscode.workspace.openNotebookDocument( From 5ba24087fd6196a088d9689f49c01e7b813f4c11 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 13:28:23 +1100 Subject: [PATCH 16/19] test --- src/platform/common/utils.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 29e3f2b857b..805acc55e0e 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -472,18 +472,19 @@ export function getCellMetadata(cell: NotebookCell): JupyterCellMetadata { } export function useCustomMetadata() { - const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); - if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { - return ext.exports.dropCustomMetadata ? false : true; - } - try { - // Means ipynb extension has not yet been activated. - // Does not matter, we can just check the setting. - return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); - } catch { - // This happens in unit tests, in this case just return `true`. - return true; - } + return true; + // const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); + // if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { + // return ext.exports.dropCustomMetadata ? false : true; + // } + // try { + // // Means ipynb extension has not yet been activated. + // // Does not matter, we can just check the setting. + // return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); + // } catch { + // // This happens in unit tests, in this case just return `true`. + // return true; + // } } export async function activateIPynbExtension() { From 615430115295c6cd52a61595c5644688c46470c8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 14:40:25 +1100 Subject: [PATCH 17/19] fix tests --- src/platform/common/utils.ts | 25 +++-- src/standalone/executionAnalysis/symbols.ts | 15 --- .../executionAnalysis/symbols.vscode.test.ts | 102 +++++++++--------- 3 files changed, 66 insertions(+), 76 deletions(-) diff --git a/src/platform/common/utils.ts b/src/platform/common/utils.ts index 805acc55e0e..29e3f2b857b 100644 --- a/src/platform/common/utils.ts +++ b/src/platform/common/utils.ts @@ -472,19 +472,18 @@ export function getCellMetadata(cell: NotebookCell): JupyterCellMetadata { } export function useCustomMetadata() { - return true; - // const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); - // if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { - // return ext.exports.dropCustomMetadata ? false : true; - // } - // try { - // // Means ipynb extension has not yet been activated. - // // Does not matter, we can just check the setting. - // return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); - // } catch { - // // This happens in unit tests, in this case just return `true`. - // return true; - // } + const ext = extensions.getExtension<{ dropCustomMetadata: boolean }>('vscode.ipynb'); + if (ext && typeof ext.exports.dropCustomMetadata === 'boolean') { + return ext.exports.dropCustomMetadata ? false : true; + } + try { + // Means ipynb extension has not yet been activated. + // Does not matter, we can just check the setting. + return !workspace.getConfiguration('jupyter', undefined).get('experimental.dropCustomMetadata', false); + } catch { + // This happens in unit tests, in this case just return `true`. + return true; + } } export async function activateIPynbExtension() { diff --git a/src/standalone/executionAnalysis/symbols.ts b/src/standalone/executionAnalysis/symbols.ts index cda7fb1fa7c..6bb9bd67daa 100644 --- a/src/standalone/executionAnalysis/symbols.ts +++ b/src/standalone/executionAnalysis/symbols.ts @@ -282,7 +282,6 @@ export class NotebookDocumentSymbolTracker { for (const key of _pendingRequestsKeys) { const cell = this._notebookEditor.notebook.getCells().find((cell) => cell.document.uri.fragment === key); if (cell) { - console.error('Step.Pylance.5.2', cell.index); await this._requestCellSymbols(cell, true); } } @@ -319,9 +318,7 @@ export class NotebookDocumentSymbolTracker { } async getPrecedentCells(cell: vscode.NotebookCell) { - console.error('Step.Pylance.5.1'); await this.requestCellSymbolsSync(); - console.error('Step.Pylance.6.0'); const analysis = new CellAnalysis(this._notebookEditor.notebook, this._cellExecution, this._cellRefs); let precedentCells: vscode.NotebookCell[] = []; @@ -436,9 +433,7 @@ export class NotebookDocumentSymbolTracker { if (synchronous) { const cancellationTokenSource = new vscode.CancellationTokenSource(); - console.error('Step.Pylance.5.3', cell.index); await this._doRequestCellSymbols(cell, cancellationTokenSource.token); - console.error('Step.Pylance.5.3.End', cell.index); cancellationTokenSource.dispose(); return; } @@ -463,11 +458,9 @@ export class NotebookDocumentSymbolTracker { private async _getDocumentSymbols(cell: vscode.NotebookCell) { if (this._client.getDocumentSymbols) { - console.error('Step.Pylance.5.4 with Pylance', cell.index); const tokenSource = new vscode.CancellationTokenSource(); return this._client.getDocumentSymbols(cell.document, tokenSource.token); } else { - console.error('Step.Pylance.5.4 without Pylance', cell.index); return vscode.commands.executeCommand<(vscode.SymbolInformation & vscode.DocumentSymbol)[] | undefined>( 'vscode.executeDocumentSymbolProvider', cell.document.uri @@ -476,27 +469,19 @@ export class NotebookDocumentSymbolTracker { } private async _doRequestCellSymbols(cell: vscode.NotebookCell, token: vscode.CancellationToken) { - console.error('Step.Pylance.5.4', cell.index); const symbols = await this._getDocumentSymbols(cell); - console.error('Step.Pylance.5.5', cell.index); if (!symbols) { return; } const references: (LocationWithReferenceKind & { associatedSymbol: ISymbol })[] = []; for (const symbol of symbols) { - console.error( - 'Step.Pylance.5.6', - cell.index, - `${symbol.name}:${symbol.range.start.line}.${symbol.range.start.character}` - ); const symbolReferences = await this._client.getReferences( cell.document, symbol.selectionRange.start, { includeDeclaration: true }, token ); - console.error('Step.Pylance.5.7'); if (symbolReferences) { references.push( ...symbolReferences diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index 3daf75420c0..3fbbb4d3beb 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -7,7 +7,7 @@ import { anything, instance, mock, when } from 'ts-mockito'; import { CellAnalysis, ICellExecution, ILocationWithReferenceKind, NotebookDocumentSymbolTracker } from './symbols'; import { PylanceExtension } from './common'; import { activatePylance } from './pylance'; -import { sleep } from '../../test/core'; +import { useCustomMetadata } from '../../platform/common/utils'; function withNotebookCells(data: [string, string][], fileName: string) { const cells: vscode.NotebookCell[] = data.map((cellDto) => { @@ -495,27 +495,27 @@ function closeAllEditors(): Thenable { (vscode.extensions.getExtension(PylanceExtension) ? suite : suite.skip)('Cell Analysis - Pylance', () => { test('Advanced type dependencies', async () => { - console.error('Step.Pylance.1'); - await sleep(10_000); - const document = await vscode.workspace.openNotebookDocument( - 'jupyter-notebook', - new vscode.NotebookData([ - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import pandas as pd', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'df = pd.DataFrame()', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'mylist = [1, 2, 3, 4]', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'mylist2 = [2, 3, 4, 5]', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(mylist)', 'python') - ]) - ); + const nb = new vscode.NotebookData([ + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import pandas as pd', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'df = pd.DataFrame()', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'mylist = [1, 2, 3, 4]', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'mylist2 = [2, 3, 4, 5]', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(mylist)', 'python') + ]); + + if (!useCustomMetadata()) { + nb.metadata = { + custom: { + metadata: { + cellLanguage: 'python' + } + } + }; + } + const document = await vscode.workspace.openNotebookDocument('jupyter-notebook', nb); - console.error('Step.Pylance.2', JSON.stringify(document.metadata)); - console.error('Step.Pylance.2', JSON.stringify(document.getCells().map((c) => c.metadata))); - console.error('Step.Pylance.2'); - const editor = await vscode.window.showNotebookDocument(document); - console.error('Step.Pylance.3'); - await sleep(10_000); + const editor = await await vscode.window.showNotebookDocument(document); const referencesProvider = await activatePylance(); - console.error('Step.Pylance.4'); if (!referencesProvider) { assert.fail('Pylance not found'); } @@ -523,18 +523,14 @@ function closeAllEditors(): Thenable { const documentSymbolTracker = new NotebookDocumentSymbolTracker(editor, referencesProvider); { - console.error('Step.Pylance.5'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(1)); - console.error('Step.Pylance.6'); assert.equal(precedentCellRanges.length, 1); assert.equal(precedentCellRanges[0].start, 0); assert.equal(precedentCellRanges[0].end, 2); } { - console.error('Step.Pylance.7'); const precedentCellRanges = await documentSymbolTracker.getPrecedentCells(document.cellAt(4)); - console.error('Step.Pylance.8'); assert.equal(precedentCellRanges.length, 2); assert.equal(precedentCellRanges[0].start, 2); assert.equal(precedentCellRanges[0].end, 3); @@ -543,29 +539,32 @@ function closeAllEditors(): Thenable { } { - console.error('Step.Pylance.9'); const successorCellRanges = await documentSymbolTracker.getSuccessorCells(document.cellAt(0)); - console.error('Step.Pylance.10'); assert.equal(successorCellRanges.length, 1); assert.equal(successorCellRanges[0].start, 0); assert.equal(successorCellRanges[0].end, 2); } - console.error('Step.Pylance.11'); await closeAllEditors(); - console.error('Step.Pylance.12'); - }).timeout(60_000); + }); test('Advanced type dependencies 2', async () => { - const document = await vscode.workspace.openNotebookDocument( - 'jupyter-notebook', - new vscode.NotebookData([ - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import numpy as np', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'arr = np.array([1, 2, 3, 4])', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'arr2 = np.array([2, 3, 4, 5])', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(arr)', 'python') - ]) - ); + const nb = new vscode.NotebookData([ + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import numpy as np', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'arr = np.array([1, 2, 3, 4])', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'arr2 = np.array([2, 3, 4, 5])', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(arr)', 'python') + ]); + if (!useCustomMetadata()) { + nb.metadata = { + custom: { + metadata: { + cellLanguage: 'python' + } + } + }; + } + const document = await vscode.workspace.openNotebookDocument('jupyter-notebook', nb); const editor = await vscode.window.showNotebookDocument(document); const referencesProvider = await activatePylance(); if (!referencesProvider) { @@ -598,18 +597,25 @@ function closeAllEditors(): Thenable { } await closeAllEditors(); - }).timeout(30_000); + }); test('Advanced type dependencies 3', async () => { - const document = await vscode.workspace.openNotebookDocument( - 'jupyter-notebook', - new vscode.NotebookData([ - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import matplotlib.pyplot as plt', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'x = [1, 2, 3, 4]', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'y = [2, 3, 4, 5]', 'python'), - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'plt.plot(x, y)', 'python') - ]) - ); + const nb = new vscode.NotebookData([ + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'import matplotlib.pyplot as plt', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'x = [1, 2, 3, 4]', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'y = [2, 3, 4, 5]', 'python'), + new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'plt.plot(x, y)', 'python') + ]); + if (!useCustomMetadata()) { + nb.metadata = { + custom: { + metadata: { + cellLanguage: 'python' + } + } + }; + } + const document = await vscode.workspace.openNotebookDocument('jupyter-notebook', nb); const editor = await vscode.window.showNotebookDocument(document); const referencesProvider = await activatePylance(); if (!referencesProvider) { From 4f3efaf16f0fa0f59a59d8e66bf3079df442f71d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 14:50:36 +1100 Subject: [PATCH 18/19] Misc --- src/standalone/executionAnalysis/pylance.ts | 27 +++++++++---------- .../executionAnalysis/symbols.vscode.test.ts | 3 +++ src/test/standardTest.node.ts | 4 +-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/standalone/executionAnalysis/pylance.ts b/src/standalone/executionAnalysis/pylance.ts index 93e6c43c8bc..f8d03c08774 100644 --- a/src/standalone/executionAnalysis/pylance.ts +++ b/src/standalone/executionAnalysis/pylance.ts @@ -72,23 +72,20 @@ export async function activatePylance(): Promise { runPylance(pylanceExtension) - .then( - async (client) => { - if (!client) { - console.error('Could not start Pylance'); - reject(); - return; - } + .then(async (client) => { + if (!client) { + console.error('Could not start Pylance'); + reject(); + return; + } - if (client.client) { - await client.client.start(); - } + if (client.client) { + await client.client.start(); + } - _client = client.notebook; - resolve(client.notebook); - }, - (ex) => console.error(`Failed to start Pylance`, ex) - ) + _client = client.notebook; + resolve(client.notebook); + }) .then(noop, noop); }); } diff --git a/src/standalone/executionAnalysis/symbols.vscode.test.ts b/src/standalone/executionAnalysis/symbols.vscode.test.ts index 3fbbb4d3beb..17b0030939a 100644 --- a/src/standalone/executionAnalysis/symbols.vscode.test.ts +++ b/src/standalone/executionAnalysis/symbols.vscode.test.ts @@ -503,6 +503,7 @@ function closeAllEditors(): Thenable { new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(mylist)', 'python') ]); + // Temporary, until Pylance is fixed if (!useCustomMetadata()) { nb.metadata = { custom: { @@ -555,6 +556,7 @@ function closeAllEditors(): Thenable { new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'arr2 = np.array([2, 3, 4, 5])', 'python'), new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'print(arr)', 'python') ]); + // Temporary, until Pylance is fixed if (!useCustomMetadata()) { nb.metadata = { custom: { @@ -606,6 +608,7 @@ function closeAllEditors(): Thenable { new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'y = [2, 3, 4, 5]', 'python'), new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'plt.plot(x, y)', 'python') ]); + // Temporary, until Pylance is fixed if (!useCustomMetadata()) { nb.metadata = { custom: { diff --git a/src/test/standardTest.node.ts b/src/test/standardTest.node.ts index 005437f18ee..fed7ddd5766 100644 --- a/src/test/standardTest.node.ts +++ b/src/test/standardTest.node.ts @@ -130,8 +130,8 @@ async function createSettings(): Promise { 'jupyter.widgetScriptSources': ['jsdelivr.com', 'unpkg.com'], // New Kernel Picker. 'notebook.kernelPicker.type': 'mru', - 'jupyter.experimental.dropCustomMetadata': true, - 'jupyter.drop.custom.property': true + 'jupyter.experimental.dropCustomMetadata': false, + 'jupyter.drop.custom.property': false }; if (IS_SMOKE_TEST()) { defaultSettings['python.languageServer'] = 'None'; From 37f3a3425cd92738c3e4dcce4a5faa5458ce5500 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 20 Mar 2024 14:59:46 +1100 Subject: [PATCH 19/19] always disable custom metadata on ci --- src/test/standardTest.node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/standardTest.node.ts b/src/test/standardTest.node.ts index fed7ddd5766..2e8f6e67c5c 100644 --- a/src/test/standardTest.node.ts +++ b/src/test/standardTest.node.ts @@ -130,8 +130,8 @@ async function createSettings(): Promise { 'jupyter.widgetScriptSources': ['jsdelivr.com', 'unpkg.com'], // New Kernel Picker. 'notebook.kernelPicker.type': 'mru', - 'jupyter.experimental.dropCustomMetadata': false, - 'jupyter.drop.custom.property': false + 'jupyter.experimental.dropCustomMetadata': true, // On CI always run without custom metadata. + 'jupyter.drop.custom.property': true // On CI always run without custom metadata. }; if (IS_SMOKE_TEST()) { defaultSettings['python.languageServer'] = 'None';