Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guard against invalid Python versions #15240

Merged
merged 1 commit into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 28 additions & 23 deletions src/notebooks/controllers/vscodeNotebookController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,28 +389,6 @@ export class VSCodeNotebookController implements Disposable, IVSCodeNotebookCont
// Notebook is trusted. Continue to execute cells
await Promise.all(cells.map((cell) => this.executeCell(notebook, cell)));
}
private async warnWhenUsingOutdatedPython() {
const pyVersion = await getVersion(this.kernelConnection.interpreter);
if (
!pyVersion ||
(pyVersion.major || 0) >= 4 ||
(this.kernelConnection.kind !== 'startUsingLocalKernelSpec' &&
this.kernelConnection.kind !== 'startUsingPythonInterpreter')
) {
return;
}

if ((pyVersion.major || 0) < 3 || (pyVersion.major === 3 && (pyVersion.minor || 0) <= 5)) {
window
.showWarningMessage(DataScience.warnWhenSelectingKernelWithUnSupportedPythonVersion, Common.learnMore)
.then((selection) => {
if (selection !== Common.learnMore) {
return;
}
return openInBrowser('https://aka.ms/jupyterUnSupportedPythonKernelVersions');
}, noop);
}
}
private async onDidChangeSelectedNotebooks(event: { notebook: NotebookDocument; selected: boolean }) {
traceInfoIfCI(
`NotebookController selection event called for notebook ${event.notebook.uri.toString()} & controller ${
Expand Down Expand Up @@ -446,7 +424,7 @@ export class VSCodeNotebookController implements Disposable, IVSCodeNotebookCont
if (!workspace.isTrusted) {
return;
}
await this.warnWhenUsingOutdatedPython();
void warnWhenUsingOutdatedPython(this.kernelConnection);
const deferred = createDeferred<void>();
traceInfoIfCI(
`Controller ${this.connection.kind}:${this.id} associated with nb ${getDisplayPath(event.notebook.uri)}`
Expand Down Expand Up @@ -763,3 +741,30 @@ async function updateNotebookDocumentMetadata(
await workspace.applyEdit(edit);
}
}

export async function warnWhenUsingOutdatedPython(kernelConnection: KernelConnectionMetadata) {
const pyVersion = await getVersion(kernelConnection.interpreter);
const major = pyVersion?.major || 0;
const minor = pyVersion?.minor || 0;
if (
!pyVersion ||
major >= 4 ||
major <= 0 || // Invalid versions from Python extension
minor <= -1 || // Invalid versions from Python extension
(kernelConnection.kind !== 'startUsingLocalKernelSpec' &&
kernelConnection.kind !== 'startUsingPythonInterpreter')
) {
return;
}

if (major < 3 || (major === 3 && minor <= 5)) {
window
.showWarningMessage(DataScience.warnWhenSelectingKernelWithUnSupportedPythonVersion, Common.learnMore)
.then((selection) => {
if (selection !== Common.learnMore) {
return;
}
return openInBrowser('https://aka.ms/jupyterUnSupportedPythonKernelVersions');
}, noop);
}
}
298 changes: 296 additions & 2 deletions src/notebooks/controllers/vscodeNotebookController.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@
import { assert } from 'chai';
import * as fakeTimers from '@sinonjs/fake-timers';
import { NotebookDocument, EventEmitter, NotebookController, Uri, Disposable } from 'vscode';
import { VSCodeNotebookController } from './vscodeNotebookController';
import { IKernel, IKernelProvider, KernelConnectionMetadata, LocalKernelConnectionMetadata } from '../../kernels/types';
import { VSCodeNotebookController, warnWhenUsingOutdatedPython } from './vscodeNotebookController';
import {
IKernel,
IKernelProvider,
KernelConnectionMetadata,
LiveRemoteKernelConnectionMetadata,
LocalKernelConnectionMetadata,
LocalKernelSpecConnectionMetadata,
RemoteKernelSpecConnectionMetadata
} from '../../kernels/types';
import { anything, instance, mock, verify, when } from 'ts-mockito';
import {
IConfigurationService,
Expand All @@ -33,6 +41,8 @@ import { IConnectionDisplayDataProvider } from './types';
import { ConnectionDisplayDataProvider } from './connectionDisplayData.node';
import { mockedVSCodeNamespaces, resetVSCodeMocks } from '../../test/vscode-mock';
import { IJupyterVariables } from '../../kernels/variables/types';
import { Environment, PythonExtension } from '@vscode/python-extension';
import { setPythonApi } from '../../platform/interpreter/helpers';

suite(`Notebook Controller`, function () {
let controller: NotebookController;
Expand Down Expand Up @@ -272,4 +282,288 @@ suite(`Notebook Controller`, function () {

verify(mockedVSCodeNamespaces.workspace.applyEdit(anything())).once();
});
suite('Unsupported Python Versions', () => {
let pythonApi: PythonExtension;
let environments: PythonExtension['environments'];
setup(() => {
pythonApi = mock<PythonExtension>();
environments = mock<PythonExtension['environments']>();
when(pythonApi.environments).thenReturn(instance(environments));
setPythonApi(instance(pythonApi));
when(mockedVSCodeNamespaces.window.showWarningMessage(anything(), anything())).thenResolve(undefined);
});
teardown(() => {
setPythonApi(undefined as any);
resetVSCodeMocks();
});
test('No warnings when Python is not used', async () => {
const kernels = [
RemoteKernelSpecConnectionMetadata.create({
baseUrl: 'http://localhost:8888/',
id: '1234',
kernelSpec: {
argv: [],
display_name: '',
executable: '',
name: ''
},
serverProviderHandle: {
extensionId: '',
handle: '',
id: ''
}
}),
LiveRemoteKernelConnectionMetadata.create({
baseUrl: 'http://localhost:8888/',
id: '1234',
kernelModel: {
name: '',
lastActivityTime: '',
model: undefined,
numberOfConnections: 1
},
serverProviderHandle: {
extensionId: '',
handle: '',
id: ''
}
}),
LocalKernelSpecConnectionMetadata.create({
id: '1234',
kernelSpec: {
argv: [],
display_name: '',
executable: '',
name: ''
}
})
];

for (const kernel of kernels) {
await warnWhenUsingOutdatedPython(kernel);
verify(mockedVSCodeNamespaces.window.showWarningMessage(anything(), anything())).never();
}
});
const validVersionsOfPython: Environment['version'][] = [
{
major: 3,
minor: 6,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 3,
minor: 7,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 3,
minor: 8,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 3,
minor: 12,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 4,
minor: 0,
micro: 0,
release: undefined,
sysVersion: undefined
}
];

validVersionsOfPython.forEach((version) => {
test(`No warnings when Python version is valid ${version?.major}.${version?.minor}.${version?.micro}`, async () => {
const kernel = LocalKernelSpecConnectionMetadata.create({
id: '1234',
kernelSpec: {
argv: [],
display_name: '',
executable: '',
name: ''
},
interpreter: {
id: 'version',
uri: Uri.file('')
}
});
when(environments.known).thenReturn([
{
environment: {
folderUri: Uri.file(''),
name: '',
type: '',
workspaceFolder: undefined
},
executable: {
bitness: undefined,
sysPrefix: undefined,
uri: undefined
},
id: 'version',
path: '',
tools: [],
version
}
]);
await warnWhenUsingOutdatedPython(kernel);
verify(mockedVSCodeNamespaces.window.showWarningMessage(anything(), anything())).never();
});
});
const invalidVersionsOfPython: Environment['version'][] = [
{
major: 3,
minor: -6,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: -3,
minor: 7,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: -1,
minor: 8,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 0,
minor: 0,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 0,
minor: 1,
micro: 0,
release: undefined,
sysVersion: undefined
}
];

invalidVersionsOfPython.forEach((version) => {
test(`No warnings when Python version is invalid ${version?.major}.${version?.minor}.${version?.micro}`, async () => {
const kernel = LocalKernelSpecConnectionMetadata.create({
id: '1234',
kernelSpec: {
argv: [],
display_name: '',
executable: '',
name: ''
},
interpreter: {
id: 'version',
uri: Uri.file('')
}
});
when(environments.known).thenReturn([
{
environment: {
folderUri: Uri.file(''),
name: '',
type: '',
workspaceFolder: undefined
},
executable: {
bitness: undefined,
sysPrefix: undefined,
uri: undefined
},
id: 'version',
path: '',
tools: [],
version
}
]);
await warnWhenUsingOutdatedPython(kernel);
verify(mockedVSCodeNamespaces.window.showWarningMessage(anything(), anything())).never();
});
});
const unsupportedVersionsOfPython: Environment['version'][] = [
{
major: 3,
minor: 5,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 3,
minor: 4,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 2,
minor: 7,
micro: 0,
release: undefined,
sysVersion: undefined
},
{
major: 2,
minor: 5,
micro: 0,
release: undefined,
sysVersion: undefined
}
];

unsupportedVersionsOfPython.forEach((version) => {
test(`Warnings when Python version is not supported ${version?.major}.${version?.minor}.${version?.micro}`, async () => {
const kernel = LocalKernelSpecConnectionMetadata.create({
id: '1234',
kernelSpec: {
argv: [],
display_name: '',
executable: '',
name: ''
},
interpreter: {
id: 'version',
uri: Uri.file('')
}
});
when(environments.known).thenReturn([
{
environment: {
folderUri: Uri.file(''),
name: '',
type: '',
workspaceFolder: undefined
},
executable: {
bitness: undefined,
sysPrefix: undefined,
uri: undefined
},
id: 'version',
path: '',
tools: [],
version
}
]);
await warnWhenUsingOutdatedPython(kernel);
verify(mockedVSCodeNamespaces.window.showWarningMessage(anything(), anything())).once();
});
});
});
});
Loading