Skip to content

Commit 7833aad

Browse files
authored
Separate notebook kernel and api test (microsoft#156946)
* Separate notebook kernel and api test. * no need to test reopen dirty document
1 parent f86beb1 commit 7833aad

File tree

4 files changed

+397
-345
lines changed

4 files changed

+397
-345
lines changed

extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as assert from 'assert';
77
import 'mocha';
88
import * as vscode from 'vscode';
99
import { disposeAll } from '../utils';
10-
import { Kernel, saveAllFilesAndCloseAll } from './notebook.test';
10+
import { Kernel, saveAllFilesAndCloseAll } from './notebook.api.test';
1111

1212
export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor };
1313

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as assert from 'assert';
7+
import 'mocha';
8+
import { TextDecoder, TextEncoder } from 'util';
9+
import * as vscode from 'vscode';
10+
import { asPromise, assertNoRpc, closeAllEditors, createRandomFile, disposeAll, revertAllDirty, saveAllEditors } from '../utils';
11+
12+
async function createRandomNotebookFile() {
13+
return createRandomFile('', undefined, '.vsctestnb');
14+
}
15+
16+
async function openRandomNotebookDocument() {
17+
const uri = await createRandomNotebookFile();
18+
return vscode.workspace.openNotebookDocument(uri);
19+
}
20+
21+
export async function saveAllFilesAndCloseAll() {
22+
await saveAllEditors();
23+
await closeAllEditors();
24+
}
25+
26+
27+
function sleep(ms: number): Promise<void> {
28+
return new Promise(resolve => {
29+
setTimeout(resolve, ms);
30+
});
31+
}
32+
33+
export class Kernel {
34+
35+
readonly controller: vscode.NotebookController;
36+
37+
readonly associatedNotebooks = new Set<string>();
38+
39+
constructor(id: string, label: string, viewType: string = 'notebookCoreTest') {
40+
this.controller = vscode.notebooks.createNotebookController(id, viewType, label);
41+
this.controller.executeHandler = this._execute.bind(this);
42+
this.controller.supportsExecutionOrder = true;
43+
this.controller.supportedLanguages = ['typescript', 'javascript'];
44+
this.controller.onDidChangeSelectedNotebooks(e => {
45+
if (e.selected) {
46+
this.associatedNotebooks.add(e.notebook.uri.toString());
47+
} else {
48+
this.associatedNotebooks.delete(e.notebook.uri.toString());
49+
}
50+
});
51+
}
52+
53+
protected async _execute(cells: vscode.NotebookCell[]): Promise<void> {
54+
for (const cell of cells) {
55+
await this._runCell(cell);
56+
}
57+
}
58+
59+
protected async _runCell(cell: vscode.NotebookCell) {
60+
// create a single output with exec order 1 and output is plain/text
61+
// of either the cell itself or (iff empty) the cell's document's uri
62+
const task = this.controller.createNotebookCellExecution(cell);
63+
task.start(Date.now());
64+
task.executionOrder = 1;
65+
await sleep(10); // Force to be take some time
66+
await task.replaceOutput([new vscode.NotebookCellOutput([
67+
vscode.NotebookCellOutputItem.text(cell.document.getText() || cell.document.uri.toString(), 'text/plain')
68+
])]);
69+
task.end(true);
70+
}
71+
}
72+
73+
74+
function getFocusedCell(editor?: vscode.NotebookEditor) {
75+
return editor ? editor.notebook.cellAt(editor.selections[0].start) : undefined;
76+
}
77+
78+
const apiTestContentProvider: vscode.NotebookContentProvider = {
79+
openNotebook: async (resource: vscode.Uri): Promise<vscode.NotebookData> => {
80+
if (/.*empty\-.*\.vsctestnb$/.test(resource.path)) {
81+
return {
82+
metadata: {},
83+
cells: []
84+
};
85+
}
86+
87+
const dto: vscode.NotebookData = {
88+
metadata: { custom: { testMetadata: false } },
89+
cells: [
90+
{
91+
value: 'test',
92+
languageId: 'typescript',
93+
kind: vscode.NotebookCellKind.Code,
94+
outputs: [],
95+
metadata: { custom: { testCellMetadata: 123 } },
96+
executionSummary: { timing: { startTime: 10, endTime: 20 } }
97+
},
98+
{
99+
value: 'test2',
100+
languageId: 'typescript',
101+
kind: vscode.NotebookCellKind.Code,
102+
outputs: [
103+
new vscode.NotebookCellOutput([
104+
vscode.NotebookCellOutputItem.text('Hello World', 'text/plain')
105+
],
106+
{
107+
testOutputMetadata: true,
108+
['text/plain']: { testOutputItemMetadata: true }
109+
})
110+
],
111+
executionSummary: { executionOrder: 5, success: true },
112+
metadata: { custom: { testCellMetadata: 456 } }
113+
}
114+
]
115+
};
116+
return dto;
117+
},
118+
saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
119+
return;
120+
},
121+
saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
122+
return;
123+
},
124+
backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => {
125+
return {
126+
id: '1',
127+
delete: () => { }
128+
};
129+
}
130+
};
131+
132+
(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests', function () {
133+
134+
const testDisposables: vscode.Disposable[] = [];
135+
const suiteDisposables: vscode.Disposable[] = [];
136+
137+
suiteTeardown(async function () {
138+
139+
assertNoRpc();
140+
141+
await revertAllDirty();
142+
await closeAllEditors();
143+
144+
disposeAll(suiteDisposables);
145+
suiteDisposables.length = 0;
146+
});
147+
148+
suiteSetup(function () {
149+
suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider));
150+
});
151+
152+
let defaultKernel: Kernel;
153+
154+
setup(async function () {
155+
// there should be ONE default kernel in this suite
156+
defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel');
157+
testDisposables.push(defaultKernel.controller);
158+
await saveAllFilesAndCloseAll();
159+
});
160+
161+
teardown(async function () {
162+
disposeAll(testDisposables);
163+
testDisposables.length = 0;
164+
await saveAllFilesAndCloseAll();
165+
});
166+
167+
test('edit API batch edits', async function () {
168+
const notebook = await openRandomNotebookDocument();
169+
170+
const edit = new vscode.WorkspaceEdit();
171+
const metdataEdit = vscode.NotebookEdit.updateNotebookMetadata({ ...notebook.metadata, custom: { ...(notebook.metadata.custom || {}), extraNotebookMetadata: true } });
172+
edit.set(notebook.uri, [metdataEdit]);
173+
const success = await vscode.workspace.applyEdit(edit);
174+
assert.equal(success, true);
175+
assert.ok(notebook.metadata.custom.extraNotebookMetadata, `Test metadata not found`);
176+
});
177+
178+
test('notebook open', async function () {
179+
const notebook = await openRandomNotebookDocument();
180+
const editor = await vscode.window.showNotebookDocument(notebook);
181+
assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test');
182+
assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript');
183+
184+
const secondCell = editor.notebook.cellAt(1);
185+
assert.strictEqual(secondCell.outputs.length, 1);
186+
assert.deepStrictEqual(secondCell.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } });
187+
assert.strictEqual(secondCell.outputs[0].items.length, 1);
188+
assert.strictEqual(secondCell.outputs[0].items[0].mime, 'text/plain');
189+
assert.strictEqual(new TextDecoder().decode(secondCell.outputs[0].items[0].data), 'Hello World');
190+
assert.strictEqual(secondCell.executionSummary?.executionOrder, 5);
191+
assert.strictEqual(secondCell.executionSummary?.success, true);
192+
});
193+
194+
test('multiple tabs: different editors with same document', async function () {
195+
const notebook = await openRandomNotebookDocument();
196+
const firstNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.One });
197+
const secondNotebookEditor = await vscode.window.showNotebookDocument(notebook, { viewColumn: vscode.ViewColumn.Beside });
198+
assert.notStrictEqual(firstNotebookEditor, secondNotebookEditor);
199+
assert.strictEqual(firstNotebookEditor?.notebook, secondNotebookEditor?.notebook, 'split notebook editors share the same document');
200+
});
201+
202+
test.skip('#106657. Opening a notebook from markers view is broken ', async function () {
203+
204+
const document = await openRandomNotebookDocument();
205+
const [cell] = document.getCells();
206+
207+
assert.strictEqual(vscode.window.activeNotebookEditor, undefined);
208+
209+
// opening a cell-uri opens a notebook editor
210+
await vscode.window.showTextDocument(cell.document, { viewColumn: vscode.ViewColumn.Active });
211+
// await vscode.commands.executeCommand('vscode.open', cell.document.uri, vscode.ViewColumn.Active);
212+
213+
assert.strictEqual(!!vscode.window.activeNotebookEditor, true);
214+
assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString());
215+
});
216+
217+
test('Cannot open notebook from cell-uri with vscode.open-command', async function () {
218+
219+
const document = await openRandomNotebookDocument();
220+
const [cell] = document.getCells();
221+
222+
await saveAllFilesAndCloseAll();
223+
assert.strictEqual(vscode.window.activeNotebookEditor, undefined);
224+
225+
// BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69)
226+
// removes the fragment if it matches something numeric. For notebooks that's not wanted...
227+
// opening a cell-uri opens a notebook editor
228+
await vscode.commands.executeCommand('vscode.open', cell.document.uri);
229+
230+
assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString());
231+
});
232+
233+
test('#97830, #97764. Support switch to other editor types', async function () {
234+
const notebook = await openRandomNotebookDocument();
235+
const editor = await vscode.window.showNotebookDocument(notebook);
236+
const edit = new vscode.WorkspaceEdit();
237+
const focusedCell = getFocusedCell(editor);
238+
assert.ok(focusedCell);
239+
edit.replace(focusedCell.document.uri, focusedCell.document.lineAt(0).range, 'var abc = 0;');
240+
await vscode.workspace.applyEdit(edit);
241+
242+
assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'var abc = 0;');
243+
244+
// no kernel -> no default language
245+
assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript');
246+
247+
await vscode.commands.executeCommand('vscode.openWith', notebook.uri, 'default');
248+
assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, notebook.uri.path);
249+
});
250+
251+
test('#102411 - untitled notebook creation failed', async function () {
252+
await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' });
253+
assert.notStrictEqual(vscode.window.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined');
254+
255+
await closeAllEditors();
256+
});
257+
258+
test('#115855 onDidSaveNotebookDocument', async function () {
259+
const resource = await createRandomNotebookFile();
260+
const notebook = await vscode.workspace.openNotebookDocument(resource);
261+
262+
const notebookEdit = new vscode.NotebookEdit(new vscode.NotebookRange(1, 1), [new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'test 2', 'javascript')]);
263+
const edit = new vscode.WorkspaceEdit();
264+
edit.set(notebook.uri, [notebookEdit]);
265+
await vscode.workspace.applyEdit(edit);
266+
assert.strictEqual(notebook.isDirty, true);
267+
268+
const saveEvent = asPromise(vscode.workspace.onDidSaveNotebookDocument);
269+
await notebook.save();
270+
await saveEvent;
271+
272+
assert.strictEqual(notebook.isDirty, false);
273+
});
274+
});
275+
276+
(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('statusbar', () => {
277+
const emitter = new vscode.EventEmitter<vscode.NotebookCell>();
278+
const onDidCallProvide = emitter.event;
279+
const suiteDisposables: vscode.Disposable[] = [];
280+
suiteTeardown(async function () {
281+
assertNoRpc();
282+
283+
await revertAllDirty();
284+
await closeAllEditors();
285+
286+
disposeAll(suiteDisposables);
287+
suiteDisposables.length = 0;
288+
});
289+
290+
suiteSetup(() => {
291+
suiteDisposables.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider('notebookCoreTest', {
292+
async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise<vscode.NotebookCellStatusBarItem[]> {
293+
emitter.fire(cell);
294+
return [];
295+
}
296+
}));
297+
298+
suiteDisposables.push(vscode.workspace.registerNotebookContentProvider('notebookCoreTest', apiTestContentProvider));
299+
});
300+
301+
test.skip('provideCellStatusBarItems called on metadata change', async function () { // TODO@roblourens https://github.com/microsoft/vscode/issues/139324
302+
const provideCalled = asPromise(onDidCallProvide);
303+
const notebook = await openRandomNotebookDocument();
304+
await vscode.window.showNotebookDocument(notebook);
305+
await provideCalled;
306+
307+
const edit = new vscode.WorkspaceEdit();
308+
edit.replaceNotebookCellMetadata(notebook.uri, 0, { inputCollapsed: true });
309+
vscode.workspace.applyEdit(edit);
310+
await provideCalled;
311+
});
312+
});
313+
314+
suite('Notebook & LiveShare', function () {
315+
316+
const suiteDisposables: vscode.Disposable[] = [];
317+
const notebookType = 'vsls-testing';
318+
319+
suiteTeardown(() => {
320+
vscode.Disposable.from(...suiteDisposables).dispose();
321+
});
322+
323+
suiteSetup(function () {
324+
325+
suiteDisposables.push(vscode.workspace.registerNotebookSerializer(notebookType, new class implements vscode.NotebookSerializer {
326+
deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): vscode.NotebookData | Thenable<vscode.NotebookData> {
327+
const value = new TextDecoder().decode(content);
328+
const cell1 = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang');
329+
cell1.outputs = [new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(value)])];
330+
return new vscode.NotebookData([cell1]);
331+
}
332+
serializeNotebook(data: vscode.NotebookData, _token: vscode.CancellationToken): Uint8Array | Thenable<Uint8Array> {
333+
return new TextEncoder().encode(data.cells[0].value);
334+
}
335+
}, {}, {
336+
displayName: 'LS',
337+
filenamePattern: ['*'],
338+
}));
339+
});
340+
341+
test('command: vscode.resolveNotebookContentProviders', async function () {
342+
343+
type Info = { viewType: string; displayName: string; filenamePattern: string[] };
344+
345+
const info = await vscode.commands.executeCommand<Info[]>('vscode.resolveNotebookContentProviders');
346+
assert.strictEqual(Array.isArray(info), true);
347+
348+
const item = info.find(item => item.viewType === notebookType);
349+
assert.ok(item);
350+
assert.strictEqual(item?.viewType, notebookType);
351+
});
352+
353+
test('command: vscode.executeDataToNotebook', async function () {
354+
const value = 'dataToNotebook';
355+
const data = await vscode.commands.executeCommand<vscode.NotebookData>('vscode.executeDataToNotebook', notebookType, new TextEncoder().encode(value));
356+
assert.ok(data instanceof vscode.NotebookData);
357+
assert.strictEqual(data.cells.length, 1);
358+
assert.strictEqual(data.cells[0].value, value);
359+
assert.strictEqual(new TextDecoder().decode(data.cells[0].outputs![0].items[0].data), value);
360+
});
361+
362+
test('command: vscode.executeNotebookToData', async function () {
363+
const value = 'notebookToData';
364+
const notebook = new vscode.NotebookData([new vscode.NotebookCellData(vscode.NotebookCellKind.Code, value, 'fooLang')]);
365+
const data = await vscode.commands.executeCommand<Uint8Array>('vscode.executeNotebookToData', notebookType, notebook);
366+
assert.ok(data instanceof Uint8Array);
367+
assert.deepStrictEqual(new TextDecoder().decode(data), value);
368+
});
369+
});

0 commit comments

Comments
 (0)