Skip to content

Commit c248197

Browse files
authored
Treat Native notebook tests as VS Code tests (#14282)
1 parent bbcfaa0 commit c248197

17 files changed

+118
-88
lines changed

.eslintignore

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ src/test/common/insidersBuild/insidersExtensionService.unit.test.ts
366366
src/test/pythonFiles/formatting/dummy.ts
367367

368368
src/test/format/extension.dispatch.test.ts
369-
src/test/format/extension.format.ds.test.ts
369+
src/test/format/extension.format.native.vscode.test.ts
370370
src/test/format/extension.onTypeFormat.test.ts
371371
src/test/format/extension.lineFormatter.test.ts
372372
src/test/format/extension.sort.test.ts
@@ -574,20 +574,20 @@ src/test/datascience/notebook.functional.test.ts
574574
src/test/datascience/mockLanguageClient.ts
575575
src/test/datascience/errorHandler.functional.test.tsx
576576
src/test/datascience/notebook/notebookStorage.unit.test.ts
577-
src/test/datascience/notebook/notebookTrust.ds.test.ts
577+
src/test/datascience/notebook/notebookTrust.native.vscode.test.ts
578578
src/test/datascience/notebook/rendererExtensionDownloader.unit.test.ts
579579
src/test/datascience/notebook/survey.unit.test.ts
580-
src/test/datascience/notebook/interrupRestart.ds.test.ts
581-
src/test/datascience/notebook/contentProvider.ds.test.ts
580+
src/test/datascience/notebook/interrupRestart.native.vscode.test.ts
581+
src/test/datascience/notebook/contentProvider.native.vscode.test.ts
582582
src/test/datascience/notebook/helper.ts
583583
src/test/datascience/notebook/contentProvider.unit.test.ts
584-
src/test/datascience/notebook/edit.ds.test.ts
584+
src/test/datascience/notebook/edit.native.vscode.test.ts
585585
src/test/datascience/notebook/rendererExension.unit.test.ts
586-
src/test/datascience/notebook/saving.ds.test.ts
587-
src/test/datascience/notebook/notebookEditorProvider.ds.test.ts
586+
src/test/datascience/notebook/saving.native.vscode.test.ts
587+
src/test/datascience/notebook/notebookEditorProvider.native.vscode.test.ts
588588
src/test/datascience/notebook/helpers.unit.test.ts
589-
src/test/datascience/notebook/executionService.ds.test.ts
590-
src/test/datascience/notebook/cellOutput.ds.test.ts
589+
src/test/datascience/notebook/executionService.native.vscode.test.ts
590+
src/test/datascience/notebook/cellOutput.native.vscode.test.ts
591591
src/test/datascience/interactiveWindowTestHelpers.tsx
592592
src/test/datascience/export/exportUtil.test.ts
593593
src/test/datascience/export/exportToHTML.test.ts

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@
152152
"skipFiles": ["<node_internals>/**"]
153153
},
154154
{
155-
"name": "Tests (DataScience, *.ds.test.ts)",
155+
"name": "Tests (DataScience, *.native.vscode.test.ts)",
156156
"type": "extensionHost",
157157
"request": "launch",
158158
"runtimeExecutable": "${execPath}",
@@ -169,7 +169,7 @@
169169
"CI_PYTHON_PATH": "<PythonPath>", // Initialize this to invert the grep (exclude tests with value defined in grep).
170170

171171
"VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE": "true",
172-
"TEST_FILES_SUFFIX": "ds.test"
172+
"TEST_FILES_SUFFIX": "native.vscode.test"
173173
},
174174
"stopOnEntry": false,
175175
"sourceMaps": true,

build/ci/templates/test_phases.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ steps:
452452
DISPLAY: :10
453453
VSC_PYTHON_CI_TEST_VSC_CHANNEL: 'insiders'
454454
VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE: 'true'
455-
TEST_FILES_SUFFIX: 'ds.test'
455+
TEST_FILES_SUFFIX: 'native.vscode.test'
456456
457457
# Run the single workspace tests for DS in VS Code Stable.
458458
- script: |

build/ci/vscode-python-ci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ stages:
138138
'Debugger':
139139
TestsToRun: 'testDebugger'
140140
NeedsPythonTestReqs: true
141+
'DataScience in VSCode':
142+
TestsToRun: 'testDataScienceInVSCode'
143+
NeedsPythonTestReqs: true
144+
NeedsPythonFunctionalReqs: true
141145
'Smoke':
142146
TestsToRun: 'testSmoke'
143147
NeedsPythonTestReqs: true

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3471,7 +3471,7 @@
34713471
"preTestJediLSP": "node ./out/test/languageServers/jedi/lspSetup.js",
34723472
"testJediLSP": "node ./out/test/languageServers/jedi/lspSetup.js && cross-env CODE_TESTS_WORKSPACE=src/test VSC_PYTHON_CI_TEST_GREP='Language Server:' node ./out/test/testBootstrap.js ./out/test/standardTest.js && node ./out/test/languageServers/jedi/lspTeardown.js",
34733473
"pretestDataScience": "node ./out/test/datascience/dsTestSetup.js",
3474-
"testDataScience": "cross-env CODE_TESTS_WORKSPACE=src/test/datascience VSC_PYTHON_CI_TEST_VSC_CHANNEL=insiders TEST_FILES_SUFFIX=ds.test VSC_PYTHON_FORCE_LOGGING=1 VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE=true node ./out/test/testBootstrap.js ./out/test/standardTest.js",
3474+
"testDataScience": "cross-env CODE_TESTS_WORKSPACE=src/test/datascience VSC_PYTHON_CI_TEST_VSC_CHANNEL=insiders TEST_FILES_SUFFIX=native.vscode.test VSC_PYTHON_FORCE_LOGGING=1 VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE=true node ./out/test/testBootstrap.js ./out/test/standardTest.js",
34753475
"pretestDataScienceInVSCode": "node ./out/test/datascience/dsTestSetup.js",
34763476
"testDataScienceInVSCode": "cross-env CODE_TESTS_WORKSPACE=src/test/datascience VSC_PYTHON_CI_TEST_VSC_CHANNEL=stable TEST_FILES_SUFFIX=vscode.test VSC_PYTHON_FORCE_LOGGING=1 VSC_PYTHON_LOAD_EXPERIMENTS_FROM_FILE=true node ./out/test/testBootstrap.js ./out/test/standardTest.js",
34773477
"testMultiWorkspace": "node ./out/test/testBootstrap.js ./out/test/multiRootTest.js",

src/client/datascience/jupyter/kernels/cellExecution.ts

Lines changed: 70 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export class CellExecution {
9797
*/
9898
private previousUpdatedToCellHasCompleted = Promise.resolve();
9999
private disposables: IDisposable[] = [];
100+
private cancelHandled = false;
100101

101102
private constructor(
102103
public readonly editor: VSCNotebookEditor,
@@ -146,6 +147,9 @@ export class CellExecution {
146147
* If execution has commenced, then interrupt (via cancellation token) else dequeue from execution.
147148
*/
148149
public async cancel() {
150+
if (this.cancelHandled) {
151+
return;
152+
}
149153
await this.initPromise;
150154
// We need to notify cancellation only if execution is in progress,
151155
// coz if not, we can safely reset the states.
@@ -156,7 +160,7 @@ export class CellExecution {
156160
if (!this.started) {
157161
await this.dequeue();
158162
}
159-
this._result.resolve(this.cell.metadata.runState);
163+
await this.completedDurToCancellation();
160164
this.dispose();
161165
}
162166
private dispose() {
@@ -213,6 +217,24 @@ export class CellExecution {
213217
this._result.resolve(this.cell.metadata.runState);
214218
}
215219

220+
private async completedDurToCancellation() {
221+
await updateCellExecutionTimes(this.editor, this.cell, {
222+
startTime: this.cell.metadata.runStartTime,
223+
lastRunDuration: this.stopWatch.elapsedTime
224+
});
225+
226+
await this.editor.edit((edit) =>
227+
edit.replaceCellMetadata(this.cell.index, {
228+
...this.cell.metadata,
229+
runState: vscodeNotebookEnums.NotebookCellRunState.Idle,
230+
statusMessage: ''
231+
})
232+
);
233+
234+
this._completed = true;
235+
this._result.resolve(this.cell.metadata.runState);
236+
}
237+
216238
/**
217239
* Notify other parts of extension about the cell execution.
218240
*/
@@ -270,7 +292,7 @@ export class CellExecution {
270292
}
271293
}
272294

273-
private execute(session: IJupyterSession, loggers: INotebookExecutionLogger[]) {
295+
private async execute(session: IJupyterSession, loggers: INotebookExecutionLogger[]) {
274296
// Generate metadata from our cell (some kernels expect this.)
275297
const metadata = {
276298
...this.cell.metadata,
@@ -281,59 +303,58 @@ export class CellExecution {
281303
const code = this.cell.document.getText();
282304

283305
// Skip if no code to execute
284-
if (code.trim().length > 0) {
285-
const request = session.requestExecute(
286-
{
287-
code,
288-
silent: false,
289-
stop_on_error: false,
290-
allow_stdin: true,
291-
store_history: true // Silent actually means don't output anything. Store_history is what affects execution_count
292-
},
293-
false,
294-
metadata
295-
);
306+
if (code.trim().length === 0) {
307+
return this.completedSuccessfully().then(noop, noop);
308+
}
296309

297-
// Listen to messages and update our cell execution state appropriately
310+
const request = session.requestExecute(
311+
{
312+
code,
313+
silent: false,
314+
stop_on_error: false,
315+
allow_stdin: true,
316+
store_history: true // Silent actually means don't output anything. Store_history is what affects execution_count
317+
},
318+
false,
319+
metadata
320+
);
298321

299-
// Keep track of our clear state
300-
const clearState = new RefBool(false);
322+
// Listen to messages and update our cell execution state appropriately
301323

302-
// Listen to the reponse messages and update state as we go
303-
if (request) {
304-
// Stop handling the request if the subscriber is canceled.
305-
const cancelDisposable = this.token.onCancellationRequested(() => {
306-
request.onIOPub = noop;
307-
request.onStdin = noop;
308-
request.onReply = noop;
309-
});
324+
// Keep track of our clear state
325+
const clearState = new RefBool(false);
310326

311-
// Listen to messages.
312-
request.onIOPub = this.handleIOPub.bind(this, clearState, loggers);
313-
request.onStdin = this.handleInputRequest.bind(this, session);
314-
request.onReply = this.handleReply.bind(this, clearState);
315-
316-
// When the request finishes we are done
317-
request.done
318-
.then(() => this.completedSuccessfully())
319-
.catch(async (e) => {
320-
// @jupyterlab/services throws a `Canceled` error when the kernel is interrupted.
321-
// Such an error must be ignored.
322-
if (e && e instanceof Error && e.message === 'Canceled') {
323-
await this.completedSuccessfully();
324-
} else {
325-
await this.completedWithErrors(e);
326-
}
327-
})
328-
.finally(() => {
329-
cancelDisposable.dispose();
330-
})
331-
.ignoreErrors();
327+
// Listen to the reponse messages and update state as we go
328+
if (!request) {
329+
return this.completedWithErrors(new Error('Session cannot generate requests')).then(noop, noop);
330+
}
331+
332+
// Stop handling the request if the subscriber is canceled.
333+
const cancelDisposable = this.token.onCancellationRequested(() => {
334+
request.onIOPub = noop;
335+
request.onStdin = noop;
336+
request.onReply = noop;
337+
});
338+
339+
// Listen to messages.
340+
request.onIOPub = this.handleIOPub.bind(this, clearState, loggers);
341+
request.onStdin = this.handleInputRequest.bind(this, session);
342+
request.onReply = this.handleReply.bind(this, clearState);
343+
344+
// When the request finishes we are done
345+
try {
346+
await request.done;
347+
await this.completedSuccessfully();
348+
} catch (ex) {
349+
// @jupyterlab/services throws a `Canceled` error when the kernel is interrupted.
350+
// Such an error must be ignored.
351+
if (ex && ex instanceof Error && ex.message === 'Canceled') {
352+
await this.completedSuccessfully();
332353
} else {
333-
this.completedWithErrors(new Error('Session cannot generate requrests')).then(noop, noop);
354+
await this.completedWithErrors(ex);
334355
}
335-
} else {
336-
this.completedSuccessfully().then(noop, noop);
356+
} finally {
357+
cancelDisposable.dispose();
337358
}
338359
}
339360

src/test/datascience/commands/notebookCommands.functional.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ suite('DataScience - Notebook Commands', () => {
217217
)
218218
).never();
219219
});
220-
test('Should switch kernel using the provided notebookxxx', async () => {
220+
test('Should switch kernel using the provided notebook', async () => {
221221
const notebook = createNotebookMock();
222222
when((notebook as any).then).thenReturn(undefined);
223223
const uri = Uri.file('test.ipynb');

src/test/datascience/dsTestSetup.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ function updateTestsForOldNotebooks() {
7171

7272
if (!IS_CI_SERVER) {
7373
// Noop.
74-
} else if (process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL === 'insiders') {
74+
} else if (
75+
process.env.VSC_PYTHON_CI_TEST_VSC_CHANNEL === 'insiders' &&
76+
process.env.TEST_FILES_SUFFIX === 'native.vscode.test'
77+
) {
7578
updateTestsForNativeNotebooks();
7679
} else {
7780
updateTestsForOldNotebooks();

src/test/datascience/jupyter/kernels/installationPrompts.vscode.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { IApplicationShell } from '../../../../client/common/application/types';
99
import { ProductNames } from '../../../../client/common/installer/productNames';
1010
import { BufferDecoder } from '../../../../client/common/process/decoder';
1111
import { ProcessService } from '../../../../client/common/process/proc';
12-
import { IInstaller, InstallerResponse, Product } from '../../../../client/common/types';
12+
import { IDisposable, IInstaller, InstallerResponse, Product } from '../../../../client/common/types';
1313
import { createDeferred } from '../../../../client/common/utils/async';
1414
import { Common, DataScience } from '../../../../client/common/utils/localize';
1515
import { INotebookEditorProvider } from '../../../../client/datascience/types';
@@ -22,6 +22,7 @@ import { closeNotebooksAndCleanUpAfterTests } from '../../notebook/helper';
2222

2323
// tslint:disable: no-invalid-this max-func-body-length no-function-expression no-any
2424
suite('DataScience Install IPyKernel (slow) (install)', () => {
25+
const disposables: IDisposable[] = [];
2526
const nbFile = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src/test/datascience/jupyter/kernels/nbWithKernel.ipynb');
2627
const executable = getOSType() === OSType.Windows ? 'Scripts/python.exe' : 'bin/python'; // If running locally on Windows box.
2728
const venvPythonPath = path.join(EXTENSION_ROOT_DIR_FOR_TESTS, 'src/test/datascience/.venvnokernel', executable);
@@ -55,7 +56,7 @@ suite('DataScience Install IPyKernel (slow) (install)', () => {
5556
});
5657

5758
setup(closeActiveWindows);
58-
teardown(closeNotebooksAndCleanUpAfterTests);
59+
teardown(() => closeNotebooksAndCleanUpAfterTests(disposables));
5960

6061
test('Test Install IPyKernel prompt message', async () => {
6162
// Confirm the message has not changed.
@@ -72,7 +73,7 @@ suite('DataScience Install IPyKernel (slow) (install)', () => {
7273
const installed = createDeferred();
7374

7475
// Confirm it is installed.
75-
sinon.stub(installer, 'install').callsFake(async function (product: Product) {
76+
const showInformationMessage = sinon.stub(installer, 'install').callsFake(async function (product: Product) {
7677
// Call original method
7778
const result: InstallerResponse = await ((installer.install as any).wrappedMethod.apply(
7879
installer,
@@ -83,6 +84,7 @@ suite('DataScience Install IPyKernel (slow) (install)', () => {
8384
}
8485
return result;
8586
});
87+
disposables.push({ dispose: () => showInformationMessage.restore() });
8688

8789
// Confirm message is displayed & we click 'Install` button.
8890
sinon.stub(appShell, 'showErrorMessage').callsFake(function (message: string) {

src/test/datascience/notebook/interrupRestart.ds.test.ts renamed to src/test/datascience/notebook/interrupRestart.native.vscode.test.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { assert } from 'chai';
77
import * as sinon from 'sinon';
88
import { commands, NotebookEditor as VSCNotebookEditor } from 'vscode';
99
import { IApplicationShell, IVSCodeNotebook } from '../../../client/common/application/types';
10-
import { IConfigurationService, IDataScienceSettings, IDisposable } from '../../../client/common/types';
10+
import { IDisposable } from '../../../client/common/types';
1111
import { createDeferredFromPromise } from '../../../client/common/utils/async';
12+
import { DataScience } from '../../../client/common/utils/localize';
1213
import { noop } from '../../../client/common/utils/misc';
1314
import { IKernelProvider } from '../../../client/datascience/jupyter/kernels/types';
1415
import { INotebookEditorProvider } from '../../../client/datascience/types';
@@ -27,9 +28,8 @@ import {
2728
trustAllNotebooks,
2829
waitForTextOutputInVSCode
2930
} from './helper';
30-
// tslint:disable-next-line: no-var-requires no-require-imports
3131

32-
// tslint:disable: no-any no-invalid-this
32+
// tslint:disable: no-any no-invalid-this no-function-expression
3333
/*
3434
* This test focuses on interrupting, restarting kernels.
3535
* We will not use actual kernels, just ensure the appropriate methods are invoked on the appropriate classes.
@@ -45,8 +45,6 @@ suite('DataScience - VSCode Notebook - Restart/Interrupt/Cancel/Errors (slow)',
4545
let vscEditor: VSCNotebookEditor;
4646
let vscodeNotebook: IVSCodeNotebook;
4747
const suiteDisposables: IDisposable[] = [];
48-
let oldAskForRestart: boolean | undefined;
49-
let dsSettings: IDataScienceSettings;
5048
suiteSetup(async function () {
5149
this.timeout(60_000);
5250
api = await initialize();
@@ -59,11 +57,6 @@ suite('DataScience - VSCode Notebook - Restart/Interrupt/Cancel/Errors (slow)',
5957
editorProvider = api.serviceContainer.get<INotebookEditorProvider>(INotebookEditorProvider);
6058
editorProvider = api.serviceContainer.get<INotebookEditorProvider>(INotebookEditorProvider);
6159
kernelProvider = api.serviceContainer.get<IKernelProvider>(IKernelProvider);
62-
dsSettings = api.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(undefined)
63-
.datascience;
64-
oldAskForRestart = dsSettings.askForKernelRestart;
65-
// Disable the prompt (when attempting to restart kernel).
66-
dsSettings.askForKernelRestart = false;
6760
});
6861
setup(async () => {
6962
sinon.restore();
@@ -74,12 +67,7 @@ suite('DataScience - VSCode Notebook - Restart/Interrupt/Cancel/Errors (slow)',
7467
vscEditor = vscodeNotebook.activeNotebookEditor!;
7568
});
7669
teardown(() => closeNotebooks(disposables));
77-
suiteTeardown(async () => {
78-
oldAskForRestart = dsSettings.askForKernelRestart;
79-
// Restore.
80-
dsSettings.askForKernelRestart = oldAskForRestart;
81-
await closeNotebooksAndCleanUpAfterTests(disposables.concat(suiteDisposables));
82-
});
70+
suiteTeardown(async () => closeNotebooksAndCleanUpAfterTests(disposables.concat(suiteDisposables)));
8371

8472
test('Cancelling token will cancel cell execution', async () => {
8573
await insertPythonCell('import time\nfor i in range(10000):\n print(i)\n time.sleep(0.1)', 0);
@@ -118,7 +106,20 @@ suite('DataScience - VSCode Notebook - Restart/Interrupt/Cancel/Errors (slow)',
118106
test('Restarting kernel will cancel cell execution & we can re-run a cell', async () => {
119107
await insertPythonCell('import time\nfor i in range(10000):\n print(i)\n time.sleep(0.1)', 0);
120108
const cell = vscEditor.document.cells[0];
109+
// Ensure we click `Yes` when prompted to restart the kernel.
110+
const appShell = api.serviceContainer.get<IApplicationShell>(IApplicationShell);
111+
const showInformationMessage = sinon
112+
.stub(appShell, 'showInformationMessage')
113+
.callsFake(function (message: string) {
114+
if (message === DataScience.restartKernelMessage()) {
115+
// User clicked ok to restart it.
116+
return DataScience.restartKernelMessageYes();
117+
}
118+
return (appShell.showInformationMessage as any).wrappedMethod.apply(appShell, arguments);
119+
});
120+
disposables.push({ dispose: () => showInformationMessage.restore() });
121121

122+
(editorProvider.activeEditor as any).shouldAskForRestart = () => Promise.resolve(false);
122123
await executeActiveDocument();
123124

124125
// Wait for cell to get busy.

0 commit comments

Comments
 (0)