forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelper.ts
More file actions
273 lines (255 loc) · 12.1 KB
/
helper.ts
File metadata and controls
273 lines (255 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// tslint:disable: no-var-requires no-require-imports no-invalid-this no-any
import { nbformat } from '@jupyterlab/coreutils';
import { assert, expect } from 'chai';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as sinon from 'sinon';
import * as tmp from 'tmp';
import { commands } from 'vscode';
import { NotebookCell } from '../../../../types/vscode-proposed';
import { CellDisplayOutput } from '../../../../typings/vscode-proposed';
import { IApplicationEnvironment, IVSCodeNotebook } from '../../../client/common/application/types';
import { MARKDOWN_LANGUAGE, PYTHON_LANGUAGE } from '../../../client/common/constants';
import { IDisposable } from '../../../client/common/types';
import { noop, swallowExceptions } from '../../../client/common/utils/misc';
import { NotebookContentProvider } from '../../../client/datascience/notebook/contentProvider';
import { ICell, INotebookEditorProvider, INotebookProvider } from '../../../client/datascience/types';
import { createEventHandler, waitForCondition } from '../../common';
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../constants';
import { closeActiveWindows, initialize } from '../../initialize';
const vscodeNotebookEnums = require('vscode') as typeof import('vscode-proposed');
async function getServices() {
const api = await initialize();
return {
contentProvider: api.serviceContainer.get<NotebookContentProvider>(NotebookContentProvider),
vscodeNotebook: api.serviceContainer.get<IVSCodeNotebook>(IVSCodeNotebook),
editorProvider: api.serviceContainer.get<INotebookEditorProvider>(INotebookEditorProvider)
};
}
export async function insertMarkdownCell(source: string, index: number = 0) {
const { vscodeNotebook, editorProvider } = await getServices();
const vscEditor = vscodeNotebook.activeNotebookEditor;
const nbEditor = editorProvider.activeEditor;
const cellCount = nbEditor?.model?.cells.length ?? 0;
await new Promise((resolve) =>
vscEditor?.edit((builder) => {
builder.insert(index, source, MARKDOWN_LANGUAGE, vscodeNotebookEnums.CellKind.Markdown, [], undefined);
resolve();
})
);
return {
waitForCellToGetAdded: () =>
waitForCondition(async () => nbEditor?.model?.cells.length === cellCount + 1, 1_000, 'Cell not inserted')
};
}
export async function insertPythonCell(source: string, index: number = 0) {
const { vscodeNotebook, editorProvider } = await getServices();
const vscEditor = vscodeNotebook.activeNotebookEditor;
const nbEditor = editorProvider.activeEditor;
const oldCellCount = vscEditor?.document.cells.length ?? 0;
await new Promise((resolve) =>
vscEditor?.edit((builder) => {
builder.insert(index, source, PYTHON_LANGUAGE, vscodeNotebookEnums.CellKind.Code, [], undefined);
resolve();
})
);
// When a cell is added we need to wait for it to get added in our INotebookModel.
// We also need to wait for it to get assigned a cell id.
return {
waitForCellToGetAdded: async () => {
await waitForCondition(
async () =>
nbEditor?.model?.cells.length === oldCellCount + 1 &&
nbEditor?.model?.cells.length === vscEditor?.document.cells.length,
1_000,
'Cell not inserted'
);
// All cells must have same cell id as in INotebookModel.
await waitForCondition(
async () =>
vscEditor?.document.cells.map((cell) => cell.metadata.custom?.cellId || '').join('') ===
nbEditor?.model?.cells.map((cell) => cell.id).join(''),
1_000,
'Cell not assigned a cell Id'
);
}
};
}
export async function insertPythonCellAndWait(source: string, index: number = 0) {
await (await insertPythonCell(source, index)).waitForCellToGetAdded();
}
export async function insertMarkdownCellAndWait(source: string, index: number = 0) {
await (await insertMarkdownCell(source, index)).waitForCellToGetAdded();
}
export async function deleteCell(index: number = 0) {
const { vscodeNotebook } = await getServices();
const activeEditor = vscodeNotebook.activeNotebookEditor;
await new Promise((resolve) =>
activeEditor?.edit((builder) => {
builder.delete(index);
resolve();
})
);
}
export async function deleteAllCellsAndWait(index: number = 0) {
const { vscodeNotebook, editorProvider } = await getServices();
const activeEditor = vscodeNotebook.activeNotebookEditor;
const vscCells = activeEditor?.document.cells!;
const modelCells = editorProvider.activeEditor?.model?.cells!;
let previousCellOut = vscCells.length;
while (previousCellOut) {
await new Promise((resolve) =>
activeEditor?.edit((builder) => {
builder.delete(index);
resolve();
})
);
// Wait for cell to get deleted.
await waitForCondition(async () => vscCells.length === previousCellOut - 1, 1_000, 'Cell not deleted');
previousCellOut = vscCells.length;
}
await waitForCondition(
async () => vscCells.length === modelCells.length && vscCells.length === 0,
5_000,
'All cells were not deleted'
);
}
export async function createTemporaryFile(options: {
templateFile: string;
dir: string;
}): Promise<{ file: string } & IDisposable> {
const extension = path.extname(options.templateFile);
const tempFile = tmp.tmpNameSync({ postfix: extension, dir: options.dir });
await fs.copyFile(options.templateFile, tempFile);
return { file: tempFile, dispose: () => swallowExceptions(() => fs.unlinkSync(tempFile)) };
}
export async function createTemporaryNotebook(templateFile: string, disposables: IDisposable[]): Promise<string> {
const extension = path.extname(templateFile);
const tempFile = tmp.tmpNameSync({ postfix: extension, dir: path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'tmp') });
await fs.copyFile(templateFile, tempFile);
disposables.push({ dispose: () => swallowExceptions(() => fs.unlinkSync(tempFile)) });
return tempFile;
}
export function disposeAllDisposables(disposables: IDisposable[]) {
while (disposables.length) {
disposables.pop()?.dispose(); // NOSONAR;
}
}
export async function canRunTests() {
const api = await initialize();
const appEnv = api.serviceContainer.get<IApplicationEnvironment>(IApplicationEnvironment);
return appEnv.extensionChannel !== 'stable';
}
/**
* We will be editing notebooks, to close notebooks them we need to ensure changes are saved.
* Else when we close notebooks as part of teardown in tests, things will not work as nbs are dirty.
* Solution - swallow saves this way when VSC fires save, we resolve and VSC thinks nb got saved and marked as not dirty.
*/
export async function swallowSavingOfNotebooks() {
const api = await initialize();
// We will be editing notebooks, to close notebooks them we need to ensure changes are saved.
const contentProvider = api.serviceContainer.get<NotebookContentProvider>(NotebookContentProvider);
sinon.stub(contentProvider, 'saveNotebook').callsFake(noop as any);
sinon.stub(contentProvider, 'saveNotebookAs').callsFake(noop as any);
}
export async function shutdownAllNotebooks() {
const api = await initialize();
const notebookProvider = api.serviceContainer.get<INotebookProvider>(INotebookProvider);
await Promise.all(notebookProvider.activeNotebooks.map(async (item) => (await item).dispose()));
}
export async function closeNotebooksAndCleanUpAfterTests(disposables: IDisposable[] = []) {
// We cannot close notebooks if there are any uncommitted changes (UI could hang with prompts etc).
await commands.executeCommand('workbench.action.files.saveAll');
await closeActiveWindows();
disposeAllDisposables(disposables);
await shutdownAllNotebooks();
sinon.restore();
}
export async function startJupyter() {
const { contentProvider, editorProvider } = await getServices();
// We cannot close notebooks if there are any uncommitted changes (UI could hang with prompts etc).
await commands.executeCommand('workbench.action.files.saveAll');
await closeActiveWindows();
// Create a new nb, add a python cell and execute it.
// Doing that will start jupyter.
await editorProvider.createNew();
await (await insertPythonCell('print("Hello World")', 0)).waitForCellToGetAdded();
const model = editorProvider.activeEditor?.model;
editorProvider.activeEditor?.runAllCells();
// Wait for 15s for Jupyter to start.
await waitForCondition(async () => (model?.cells[0].data.outputs as []).length > 0, 15_000, 'Cell not executed');
const saveStub = sinon.stub(contentProvider, 'saveNotebook');
const saveAsStub = sinon.stub(contentProvider, 'saveNotebookAs');
try {
// We cannot close notebooks if there are any uncommitted changes (UI could hang with prompts etc).
saveStub.callsFake(noop as any);
saveAsStub.callsFake(noop as any);
await commands.executeCommand('workbench.action.files.saveAll');
await closeActiveWindows();
} finally {
saveStub.restore();
saveAsStub.restore();
}
}
export function assertHasExecutionCompletedSuccessfully(cell: NotebookCell) {
return (
(cell.metadata.executionOrder ?? 0) > 0 &&
cell.metadata.runState === vscodeNotebookEnums.NotebookCellRunState.Success
);
}
export function assertHasExecutionCompletedWithErrors(cell: NotebookCell) {
return (
(cell.metadata.executionOrder ?? 0) > 0 &&
cell.metadata.runState === vscodeNotebookEnums.NotebookCellRunState.Error
);
}
export function hasOutputInVSCode(cell: NotebookCell) {
assert.ok(cell.outputs.length, 'No output');
}
export function hasOutputInICell(cell: ICell) {
assert.ok((cell.data.outputs as nbformat.IOutput[]).length, 'No output');
}
export function assertHasTextOutputInVSCode(cell: NotebookCell, text: string, index: number, isExactMatch = true) {
const cellOutputs = cell.outputs;
assert.ok(cellOutputs, 'No output');
assert.equal(cellOutputs[index].outputKind, vscodeNotebookEnums.CellOutputKind.Rich, 'Incorrect output kind');
const outputText = (cellOutputs[index] as CellDisplayOutput).data['text/plain'].trim();
if (isExactMatch) {
assert.equal(outputText, text, 'Incorrect output');
} else {
expect(outputText).to.include(text, 'Output does not contain provided text');
}
}
export function assertHasTextOutputInICell(cell: ICell, text: string, index: number) {
const cellOutputs = cell.data.outputs as nbformat.IOutput[];
assert.ok(cellOutputs, 'No output');
assert.equal((cellOutputs[index].text as string).trim(), text, 'Incorrect output');
}
export function assertVSCCellIsRunning(cell: NotebookCell) {
assert.equal(cell.metadata.runState, vscodeNotebookEnums.NotebookCellRunState.Running);
return true;
}
export function assertVSCCellIsIdle(cell: NotebookCell) {
assert.equal(cell.metadata.runState, vscodeNotebookEnums.NotebookCellRunState.Idle);
return true;
}
export function assertVSCCellHasErrors(cell: NotebookCell) {
assert.equal(cell.metadata.runState, vscodeNotebookEnums.NotebookCellRunState.Error);
return true;
}
export function assertVSCCellHasErrorOutput(cell: NotebookCell) {
assert.ok(
cell.outputs.filter((output) => output.outputKind === vscodeNotebookEnums.CellOutputKind.Error).length,
'No error output in cell'
);
return true;
}
export async function saveActiveNotebook(disposables: IDisposable[]) {
const api = await initialize();
const editorProvider = api.serviceContainer.get<INotebookEditorProvider>(INotebookEditorProvider);
const savedEvent = createEventHandler(editorProvider.activeEditor!.model!, 'changed', disposables);
await commands.executeCommand('workbench.action.files.saveAll');
await waitForCondition(async () => savedEvent.all.some((e) => e.kind === 'save'), 5_000, 'Not saved');
}