Skip to content

Commit

Permalink
Add tests for native REPL (microsoft#23729)
Browse files Browse the repository at this point in the history
Resolves: microsoft#23519
  • Loading branch information
anthonykim1 authored Jul 31, 2024
1 parent 3641652 commit 3dad6c9
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 7 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"description": "%walkthrough.pythonWelcome.description%",
"when": "workspacePlatform != webworker",
"steps": [
{
{
"id": "python.createPythonFolder",
"title": "%walkthrough.step.python.createPythonFolder.title%",
"description": "%walkthrough.step.python.createPythonFolder.description%",
Expand Down
4 changes: 2 additions & 2 deletions src/client/common/application/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ interface ICommandNameWithoutArgumentTypeMapping {
[Commands.Enable_SourceMap_Support]: [];
[Commands.Exec_Selection_In_Terminal]: [];
[Commands.Exec_Selection_In_Django_Shell]: [];
[Commands.Exec_In_REPL]: [];
[Commands.Exec_In_REPL_Enter]: [];
[Commands.Create_Terminal]: [];
[Commands.PickLocalProcess]: [];
[Commands.ClearStorage]: [];
Expand Down Expand Up @@ -98,6 +96,8 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
['workbench.action.openIssueReporter']: [{ extensionId: string; issueBody: string }];
[Commands.GetSelectedInterpreterPath]: [{ workspaceFolder: string } | string[]];
[Commands.TriggerEnvironmentSelection]: [undefined | Uri];
[Commands.Exec_In_REPL]: [undefined | Uri];
[Commands.Exec_In_REPL_Enter]: [undefined | Uri];
[Commands.Exec_In_Terminal]: [undefined, Uri];
[Commands.Exec_In_Terminal_Icon]: [undefined, Uri];
[Commands.Debug_In_Terminal]: [Uri];
Expand Down
5 changes: 3 additions & 2 deletions src/client/extensionActivation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
pathUtils,
);
const executionHelper = ext.legacyIOC.serviceContainer.get<ICodeExecutionHelper>(ICodeExecutionHelper);
registerReplCommands(ext.disposables, interpreterService, executionHelper);
registerReplExecuteOnEnter(ext.disposables, interpreterService);
const commandManager = ext.legacyIOC.serviceContainer.get<ICommandManager>(ICommandManager);
registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager);
registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager);
}

/// //////////////////////////
Expand Down
7 changes: 5 additions & 2 deletions src/client/repl/replCommands.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { commands, Uri, window } from 'vscode';
import { Disposable } from 'vscode-jsonrpc';
import { ICommandManager } from '../common/application/types';
import { Commands } from '../common/constants';
import { noop } from '../common/utils/misc';
import { IInterpreterService } from '../interpreter/contracts';
Expand All @@ -24,9 +25,10 @@ export async function registerReplCommands(
disposables: Disposable[],
interpreterService: IInterpreterService,
executionHelper: ICodeExecutionHelper,
commandManager: ICommandManager,
): Promise<void> {
disposables.push(
commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => {
commandManager.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => {
const nativeREPLSetting = getSendToNativeREPLSetting();

if (!nativeREPLSetting) {
Expand Down Expand Up @@ -64,9 +66,10 @@ export async function registerReplCommands(
export async function registerReplExecuteOnEnter(
disposables: Disposable[],
interpreterService: IInterpreterService,
commandManager: ICommandManager,
): Promise<void> {
disposables.push(
commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => {
commandManager.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => {
const interpreter = await interpreterService.getActiveInterpreter(uri);
if (!interpreter) {
commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop);
Expand Down
204 changes: 204 additions & 0 deletions src/test/repl/replCommand.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Create test suite and test cases for the `replUtils` module
import * as TypeMoq from 'typemoq';
import { Disposable } from 'vscode';
import * as sinon from 'sinon';
import { expect } from 'chai';
import { IInterpreterService } from '../../client/interpreter/contracts';
import { ICommandManager } from '../../client/common/application/types';
import { ICodeExecutionHelper } from '../../client/terminals/types';
import * as replCommands from '../../client/repl/replCommands';
import * as replUtils from '../../client/repl/replUtils';
import * as nativeRepl from '../../client/repl/nativeRepl';
import { Commands } from '../../client/common/constants';
import { PythonEnvironment } from '../../client/pythonEnvironments/info';

suite('REPL - register native repl command', () => {
let interpreterService: TypeMoq.IMock<IInterpreterService>;
let commandManager: TypeMoq.IMock<ICommandManager>;
let executionHelper: TypeMoq.IMock<ICodeExecutionHelper>;
let getSendToNativeREPLSettingStub: sinon.SinonStub;
// @ts-ignore: TS6133
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let registerCommandSpy: sinon.SinonSpy;
let executeInTerminalStub: sinon.SinonStub;
let getNativeReplStub: sinon.SinonStub;
let disposable: TypeMoq.IMock<Disposable>;
let disposableArray: Disposable[] = [];
setup(() => {
interpreterService = TypeMoq.Mock.ofType<IInterpreterService>();
commandManager = TypeMoq.Mock.ofType<ICommandManager>();
executionHelper = TypeMoq.Mock.ofType<ICodeExecutionHelper>();
commandManager
.setup((cm) => cm.registerCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => TypeMoq.Mock.ofType<Disposable>().object);

getSendToNativeREPLSettingStub = sinon.stub(replUtils, 'getSendToNativeREPLSetting');
getSendToNativeREPLSettingStub.returns(false);
executeInTerminalStub = sinon.stub(replUtils, 'executeInTerminal');
executeInTerminalStub.returns(Promise.resolve());
registerCommandSpy = sinon.spy(commandManager.object, 'registerCommand');
disposable = TypeMoq.Mock.ofType<Disposable>();
disposableArray = [disposable.object];
});

teardown(() => {
sinon.restore();
disposableArray.forEach((d) => {
if (d) {
d.dispose();
}
});

disposableArray = [];
});

test('Ensure repl command is registered', async () => {
interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));

await replCommands.registerReplCommands(
disposableArray,
interpreterService.object,
executionHelper.object,
commandManager.object,
);

commandManager.verify(
(c) => c.registerCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny()),
TypeMoq.Times.atLeastOnce(),
);
});

test('Ensure getSendToNativeREPLSetting is called', async () => {
interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));

let commandHandler: undefined | (() => Promise<void>);
commandManager
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.setup((c) => c.registerCommand as any)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
if (command === Commands.Exec_In_REPL) {
commandHandler = callback;
}
// eslint-disable-next-line no-void
return { dispose: () => void 0 };
});
replCommands.registerReplCommands(
disposableArray,
interpreterService.object,
executionHelper.object,
commandManager.object,
);

expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');

await commandHandler!();

sinon.assert.calledOnce(getSendToNativeREPLSettingStub);
});

test('Ensure executeInTerminal is called when getSendToNativeREPLSetting returns false', async () => {
interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
getSendToNativeREPLSettingStub.returns(false);

let commandHandler: undefined | (() => Promise<void>);
commandManager
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.setup((c) => c.registerCommand as any)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
if (command === Commands.Exec_In_REPL) {
commandHandler = callback;
}
// eslint-disable-next-line no-void
return { dispose: () => void 0 };
});
replCommands.registerReplCommands(
disposableArray,
interpreterService.object,
executionHelper.object,
commandManager.object,
);

expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');

await commandHandler!();

sinon.assert.calledOnce(executeInTerminalStub);
});

test('Ensure we call getNativeREPL() when interpreter exist', async () => {
interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
getSendToNativeREPLSettingStub.returns(true);
getNativeReplStub = sinon.stub(nativeRepl, 'getNativeRepl');

let commandHandler: undefined | ((uri: string) => Promise<void>);
commandManager
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.setup((c) => c.registerCommand as any)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
if (command === Commands.Exec_In_REPL) {
commandHandler = callback;
}
// eslint-disable-next-line no-void
return { dispose: () => void 0 };
});
replCommands.registerReplCommands(
disposableArray,
interpreterService.object,
executionHelper.object,
commandManager.object,
);

expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');

await commandHandler!('uri');
sinon.assert.calledOnce(getNativeReplStub);
});

test('Ensure we do not call getNativeREPL() when interpreter does not exist', async () => {
getNativeReplStub = sinon.stub(nativeRepl, 'getNativeRepl');
getSendToNativeREPLSettingStub.returns(true);

interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(undefined));

let commandHandler: undefined | ((uri: string) => Promise<void>);
commandManager
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.setup((c) => c.registerCommand as any)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
if (command === Commands.Exec_In_REPL) {
commandHandler = callback;
}
// eslint-disable-next-line no-void
return { dispose: () => void 0 };
});
interpreterService
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
.returns(() => Promise.resolve(undefined));

replCommands.registerReplCommands(
disposableArray,
interpreterService.object,
executionHelper.object,
commandManager.object,
);

expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');

await commandHandler!('uri');
sinon.assert.notCalled(getNativeReplStub);
});
});

0 comments on commit 3dad6c9

Please sign in to comment.