Skip to content

Commit

Permalink
Fix changing notebook Python intepreter also changing console (#5307)
Browse files Browse the repository at this point in the history
Addresses #5305. I also added unit tests for the Python runtime session
class. They're not necessary for this specific change, but the setup
will be useful moving forward.

---------

Signed-off-by: Wasim Lorgat <[email protected]>
Co-authored-by: Nick Strayer <[email protected]>
  • Loading branch information
seeM and nstrayer authored Nov 9, 2024
1 parent 090c599 commit 83d5161
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 6 deletions.
14 changes: 8 additions & 6 deletions extensions/positron-python/src/client/positron/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,14 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
// Ensure that the ipykernel module is installed for the interpreter.
await this._installIpykernel();

// Update the active environment in the Python extension.
this._interpreterPathService.update(
undefined,
vscode.ConfigurationTarget.WorkspaceFolder,
this.interpreter.path,
);
if (this.metadata.sessionMode === positron.LanguageRuntimeSessionMode.Console) {
// Update the active environment in the Python extension.
this._interpreterPathService.update(
undefined,
vscode.ConfigurationTarget.WorkspaceFolder,
this.interpreter.path,
);
}

// Register for console width changes, if we haven't already
if (!this._consoleWidthDisposable) {
Expand Down
10 changes: 10 additions & 0 deletions extensions/positron-python/src/test/mocks/pst/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

export enum LanguageRuntimeSessionMode {
Console = 'console',
Notebook = 'notebook',
Background = 'background',
}
172 changes: 172 additions & 0 deletions extensions/positron-python/src/test/positron/session.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/* eslint-disable @typescript-eslint/no-empty-function */
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

// eslint-disable-next-line import/no-unresolved
import * as positron from 'positron';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { ILanguageServerOutputChannel } from '../../client/activation/types';
import { IWorkspaceService } from '../../client/common/application/types';
import {
IConfigurationService,
IInstaller,
IInterpreterPathService,
InstallerResponse,
IPythonSettings,
ProductInstallStatus,
} from '../../client/common/types';
import { IEnvironmentVariablesProvider } from '../../client/common/variables/types';
import { IInterpreterService } from '../../client/interpreter/contracts';
import { IServiceContainer } from '../../client/ioc/types';
import { JupyterAdapterApi, JupyterKernelSpec, JupyterLanguageRuntimeSession } from '../../client/jupyter-adapter.d';
import { PythonRuntimeSession } from '../../client/positron/session';
import { PythonEnvironment } from '../../client/pythonEnvironments/info';

suite('Python Runtime Session', () => {
let runtimeMetadata: positron.LanguageRuntimeMetadata;
let interpreterPathService: IInterpreterPathService;
let interpreter: PythonEnvironment;
let serviceContainer: IServiceContainer;
let kernelSpec: JupyterKernelSpec;
let consoleSession: positron.LanguageRuntimeSession;
let notebookSession: positron.LanguageRuntimeSession;

setup(() => {
interpreterPathService = ({
update: () => Promise.resolve(),
} as Partial<IInterpreterPathService>) as IInterpreterPathService;

interpreter = {
id: 'pythonEnvironmentId',
path: '/path/to/python',
} as PythonEnvironment;

runtimeMetadata = {
extraRuntimeData: { pythonEnvironmentId: interpreter.id },
} as positron.LanguageRuntimeMetadata;

const interpreterService = {
getInterpreters: () => [interpreter],
} as IInterpreterService;

const installer = ({
isInstalled: () => Promise.resolve(true),
promptToInstall: () => Promise.resolve(InstallerResponse.Installed),
isProductVersionCompatible: () => Promise.resolve(ProductInstallStatus.Installed),
} as Partial<IInstaller>) as IInstaller;

const outputChannel = {} as ILanguageServerOutputChannel;

const workspaceService = ({
workspaceFolders: undefined,
getWorkspaceFolder: () => undefined,
} as Partial<IWorkspaceService>) as IWorkspaceService;

const pythonSettings = ({ autoComplete: { extraPaths: [] } } as Partial<IPythonSettings>) as IPythonSettings;

const configService = {
getSettings: () => pythonSettings,
} as IConfigurationService;

const envVarsProvider = ({
onDidEnvironmentVariablesChange: () => ({ dispose() {} }),
} as Partial<IEnvironmentVariablesProvider>) as IEnvironmentVariablesProvider;

serviceContainer = {
get: (serviceIdentifier) => {
switch (serviceIdentifier) {
case IInterpreterService:
return interpreterService;
case IInterpreterPathService:
return interpreterPathService;
case IInstaller:
return installer;
case ILanguageServerOutputChannel:
return outputChannel;
case IWorkspaceService:
return workspaceService;
case IEnvironmentVariablesProvider:
return envVarsProvider;
case IConfigurationService:
return configService;
default:
return undefined;
}
},
} as IServiceContainer;

kernelSpec = {} as JupyterKernelSpec;

const kernel = ({
onDidChangeRuntimeState: () => ({ dispose() {} }),
onDidReceiveRuntimeMessage: () => ({ dispose() {} }),
onDidEndSession: () => ({ dispose() {} }),
start() {
return Promise.resolve();
},
} as Partial<JupyterLanguageRuntimeSession>) as JupyterLanguageRuntimeSession;

const adapterApi = ({
createSession: sinon.stub().resolves(kernel),
} as Partial<JupyterAdapterApi>) as JupyterAdapterApi;

sinon.stub(vscode.extensions, 'getExtension').callsFake((extensionId) => {
if (extensionId === 'vscode.kallichore-adapter' || extensionId === 'vscode.jupyter-adapter') {
return {
id: '',
extensionPath: '',
extensionKind: vscode.ExtensionKind.UI,
isActive: true,
packageJSON: {},
exports: adapterApi,
extensionUri: vscode.Uri.parse(''),
activate: () => Promise.resolve(adapterApi),
};
}
return undefined;
});

const nullConfig = ({ get: () => undefined } as Partial<
vscode.WorkspaceConfiguration
>) as vscode.WorkspaceConfiguration;
vscode.workspace.getConfiguration = () => nullConfig;

const consoleMetadata = {
sessionMode: positron.LanguageRuntimeSessionMode.Console,
} as positron.RuntimeSessionMetadata;
consoleSession = new PythonRuntimeSession(runtimeMetadata, consoleMetadata, serviceContainer, kernelSpec);

const notebookMetadata = {
sessionMode: positron.LanguageRuntimeSessionMode.Notebook,
} as positron.RuntimeSessionMetadata;
notebookSession = new PythonRuntimeSession(runtimeMetadata, notebookMetadata, serviceContainer, kernelSpec);
});

teardown(() => {
sinon.restore();
});

test('Start: updates the active interpreter for console sessions', async () => {
const target = sinon.spy(interpreterPathService, 'update');

await consoleSession.start();

sinon.assert.calledOnceWithExactly(
target,
undefined,
vscode.ConfigurationTarget.WorkspaceFolder,
interpreter.path,
);
});

test('Start: does not update the active interpreter for notebook sessions', async () => {
const target = sinon.spy(interpreterPathService, 'update');

await notebookSession.start();

sinon.assert.notCalled(target);
});
});
5 changes: 5 additions & 0 deletions extensions/positron-python/src/test/vscode-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const mockedVSCodeNamespaces: { [P in keyof VSCode]?: VSCode[P] } = {};
const originalLoad = Module._load;

// --- Start Positron ---
import * as positronMocks from './mocks/pst';

// Import Positron for its type (the actual module is mocked below).
import * as positron from 'positron';

Expand Down Expand Up @@ -174,3 +176,6 @@ mockedVSCode.LogLevel = vscodeMocks.LogLevel;
(mockedVSCode as any).ProtocolTypeHierarchyItem = vscodeMocks.vscMockExtHostedTypes.ProtocolTypeHierarchyItem;
(mockedVSCode as any).CancellationError = vscodeMocks.vscMockExtHostedTypes.CancellationError;
(mockedVSCode as any).LSPCancellationError = vscodeMocks.vscMockExtHostedTypes.LSPCancellationError;
// --- Start Positron ---
mockedPositron.LanguageRuntimeSessionMode = positronMocks.LanguageRuntimeSessionMode;
// --- End Positron ---

0 comments on commit 83d5161

Please sign in to comment.