Skip to content

Commit 3dad6c9

Browse files
authored
Add tests for native REPL (#23729)
Resolves: #23519
1 parent 3641652 commit 3dad6c9

File tree

6 files changed

+226
-7
lines changed

6 files changed

+226
-7
lines changed

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
"description": "%walkthrough.pythonWelcome.description%",
8888
"when": "workspacePlatform != webworker",
8989
"steps": [
90-
{
90+
{
9191
"id": "python.createPythonFolder",
9292
"title": "%walkthrough.step.python.createPythonFolder.title%",
9393
"description": "%walkthrough.step.python.createPythonFolder.description%",

src/client/common/application/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ interface ICommandNameWithoutArgumentTypeMapping {
3838
[Commands.Enable_SourceMap_Support]: [];
3939
[Commands.Exec_Selection_In_Terminal]: [];
4040
[Commands.Exec_Selection_In_Django_Shell]: [];
41-
[Commands.Exec_In_REPL]: [];
42-
[Commands.Exec_In_REPL_Enter]: [];
4341
[Commands.Create_Terminal]: [];
4442
[Commands.PickLocalProcess]: [];
4543
[Commands.ClearStorage]: [];
@@ -98,6 +96,8 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
9896
['workbench.action.openIssueReporter']: [{ extensionId: string; issueBody: string }];
9997
[Commands.GetSelectedInterpreterPath]: [{ workspaceFolder: string } | string[]];
10098
[Commands.TriggerEnvironmentSelection]: [undefined | Uri];
99+
[Commands.Exec_In_REPL]: [undefined | Uri];
100+
[Commands.Exec_In_REPL_Enter]: [undefined | Uri];
101101
[Commands.Exec_In_Terminal]: [undefined, Uri];
102102
[Commands.Exec_In_Terminal_Icon]: [undefined, Uri];
103103
[Commands.Debug_In_Terminal]: [Uri];

src/client/extensionActivation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ export function activateFeatures(ext: ExtensionState, _components: Components):
107107
pathUtils,
108108
);
109109
const executionHelper = ext.legacyIOC.serviceContainer.get<ICodeExecutionHelper>(ICodeExecutionHelper);
110-
registerReplCommands(ext.disposables, interpreterService, executionHelper);
111-
registerReplExecuteOnEnter(ext.disposables, interpreterService);
110+
const commandManager = ext.legacyIOC.serviceContainer.get<ICommandManager>(ICommandManager);
111+
registerReplCommands(ext.disposables, interpreterService, executionHelper, commandManager);
112+
registerReplExecuteOnEnter(ext.disposables, interpreterService, commandManager);
112113
}
113114

114115
/// //////////////////////////

src/client/repl/replCommands.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { commands, Uri, window } from 'vscode';
22
import { Disposable } from 'vscode-jsonrpc';
3+
import { ICommandManager } from '../common/application/types';
34
import { Commands } from '../common/constants';
45
import { noop } from '../common/utils/misc';
56
import { IInterpreterService } from '../interpreter/contracts';
@@ -24,9 +25,10 @@ export async function registerReplCommands(
2425
disposables: Disposable[],
2526
interpreterService: IInterpreterService,
2627
executionHelper: ICodeExecutionHelper,
28+
commandManager: ICommandManager,
2729
): Promise<void> {
2830
disposables.push(
29-
commands.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => {
31+
commandManager.registerCommand(Commands.Exec_In_REPL, async (uri: Uri) => {
3032
const nativeREPLSetting = getSendToNativeREPLSetting();
3133

3234
if (!nativeREPLSetting) {
@@ -64,9 +66,10 @@ export async function registerReplCommands(
6466
export async function registerReplExecuteOnEnter(
6567
disposables: Disposable[],
6668
interpreterService: IInterpreterService,
69+
commandManager: ICommandManager,
6770
): Promise<void> {
6871
disposables.push(
69-
commands.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => {
72+
commandManager.registerCommand(Commands.Exec_In_REPL_Enter, async (uri: Uri) => {
7073
const interpreter = await interpreterService.getActiveInterpreter(uri);
7174
if (!interpreter) {
7275
commands.executeCommand(Commands.TriggerEnvironmentSelection, uri).then(noop, noop);

src/test/repl/replCommand.test.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Create test suite and test cases for the `replUtils` module
2+
import * as TypeMoq from 'typemoq';
3+
import { Disposable } from 'vscode';
4+
import * as sinon from 'sinon';
5+
import { expect } from 'chai';
6+
import { IInterpreterService } from '../../client/interpreter/contracts';
7+
import { ICommandManager } from '../../client/common/application/types';
8+
import { ICodeExecutionHelper } from '../../client/terminals/types';
9+
import * as replCommands from '../../client/repl/replCommands';
10+
import * as replUtils from '../../client/repl/replUtils';
11+
import * as nativeRepl from '../../client/repl/nativeRepl';
12+
import { Commands } from '../../client/common/constants';
13+
import { PythonEnvironment } from '../../client/pythonEnvironments/info';
14+
15+
suite('REPL - register native repl command', () => {
16+
let interpreterService: TypeMoq.IMock<IInterpreterService>;
17+
let commandManager: TypeMoq.IMock<ICommandManager>;
18+
let executionHelper: TypeMoq.IMock<ICodeExecutionHelper>;
19+
let getSendToNativeREPLSettingStub: sinon.SinonStub;
20+
// @ts-ignore: TS6133
21+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
22+
let registerCommandSpy: sinon.SinonSpy;
23+
let executeInTerminalStub: sinon.SinonStub;
24+
let getNativeReplStub: sinon.SinonStub;
25+
let disposable: TypeMoq.IMock<Disposable>;
26+
let disposableArray: Disposable[] = [];
27+
setup(() => {
28+
interpreterService = TypeMoq.Mock.ofType<IInterpreterService>();
29+
commandManager = TypeMoq.Mock.ofType<ICommandManager>();
30+
executionHelper = TypeMoq.Mock.ofType<ICodeExecutionHelper>();
31+
commandManager
32+
.setup((cm) => cm.registerCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
33+
.returns(() => TypeMoq.Mock.ofType<Disposable>().object);
34+
35+
getSendToNativeREPLSettingStub = sinon.stub(replUtils, 'getSendToNativeREPLSetting');
36+
getSendToNativeREPLSettingStub.returns(false);
37+
executeInTerminalStub = sinon.stub(replUtils, 'executeInTerminal');
38+
executeInTerminalStub.returns(Promise.resolve());
39+
registerCommandSpy = sinon.spy(commandManager.object, 'registerCommand');
40+
disposable = TypeMoq.Mock.ofType<Disposable>();
41+
disposableArray = [disposable.object];
42+
});
43+
44+
teardown(() => {
45+
sinon.restore();
46+
disposableArray.forEach((d) => {
47+
if (d) {
48+
d.dispose();
49+
}
50+
});
51+
52+
disposableArray = [];
53+
});
54+
55+
test('Ensure repl command is registered', async () => {
56+
interpreterService
57+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
58+
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
59+
60+
await replCommands.registerReplCommands(
61+
disposableArray,
62+
interpreterService.object,
63+
executionHelper.object,
64+
commandManager.object,
65+
);
66+
67+
commandManager.verify(
68+
(c) => c.registerCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny()),
69+
TypeMoq.Times.atLeastOnce(),
70+
);
71+
});
72+
73+
test('Ensure getSendToNativeREPLSetting is called', async () => {
74+
interpreterService
75+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
76+
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
77+
78+
let commandHandler: undefined | (() => Promise<void>);
79+
commandManager
80+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81+
.setup((c) => c.registerCommand as any)
82+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
83+
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
84+
if (command === Commands.Exec_In_REPL) {
85+
commandHandler = callback;
86+
}
87+
// eslint-disable-next-line no-void
88+
return { dispose: () => void 0 };
89+
});
90+
replCommands.registerReplCommands(
91+
disposableArray,
92+
interpreterService.object,
93+
executionHelper.object,
94+
commandManager.object,
95+
);
96+
97+
expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');
98+
99+
await commandHandler!();
100+
101+
sinon.assert.calledOnce(getSendToNativeREPLSettingStub);
102+
});
103+
104+
test('Ensure executeInTerminal is called when getSendToNativeREPLSetting returns false', async () => {
105+
interpreterService
106+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
107+
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
108+
getSendToNativeREPLSettingStub.returns(false);
109+
110+
let commandHandler: undefined | (() => Promise<void>);
111+
commandManager
112+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113+
.setup((c) => c.registerCommand as any)
114+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115+
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
116+
if (command === Commands.Exec_In_REPL) {
117+
commandHandler = callback;
118+
}
119+
// eslint-disable-next-line no-void
120+
return { dispose: () => void 0 };
121+
});
122+
replCommands.registerReplCommands(
123+
disposableArray,
124+
interpreterService.object,
125+
executionHelper.object,
126+
commandManager.object,
127+
);
128+
129+
expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');
130+
131+
await commandHandler!();
132+
133+
sinon.assert.calledOnce(executeInTerminalStub);
134+
});
135+
136+
test('Ensure we call getNativeREPL() when interpreter exist', async () => {
137+
interpreterService
138+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
139+
.returns(() => Promise.resolve(({ path: 'ps' } as unknown) as PythonEnvironment));
140+
getSendToNativeREPLSettingStub.returns(true);
141+
getNativeReplStub = sinon.stub(nativeRepl, 'getNativeRepl');
142+
143+
let commandHandler: undefined | ((uri: string) => Promise<void>);
144+
commandManager
145+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
146+
.setup((c) => c.registerCommand as any)
147+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
148+
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
149+
if (command === Commands.Exec_In_REPL) {
150+
commandHandler = callback;
151+
}
152+
// eslint-disable-next-line no-void
153+
return { dispose: () => void 0 };
154+
});
155+
replCommands.registerReplCommands(
156+
disposableArray,
157+
interpreterService.object,
158+
executionHelper.object,
159+
commandManager.object,
160+
);
161+
162+
expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');
163+
164+
await commandHandler!('uri');
165+
sinon.assert.calledOnce(getNativeReplStub);
166+
});
167+
168+
test('Ensure we do not call getNativeREPL() when interpreter does not exist', async () => {
169+
getNativeReplStub = sinon.stub(nativeRepl, 'getNativeRepl');
170+
getSendToNativeREPLSettingStub.returns(true);
171+
172+
interpreterService
173+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
174+
.returns(() => Promise.resolve(undefined));
175+
176+
let commandHandler: undefined | ((uri: string) => Promise<void>);
177+
commandManager
178+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
179+
.setup((c) => c.registerCommand as any)
180+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181+
.returns(() => (command: string, callback: (...args: any[]) => any, _thisArg?: any) => {
182+
if (command === Commands.Exec_In_REPL) {
183+
commandHandler = callback;
184+
}
185+
// eslint-disable-next-line no-void
186+
return { dispose: () => void 0 };
187+
});
188+
interpreterService
189+
.setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny()))
190+
.returns(() => Promise.resolve(undefined));
191+
192+
replCommands.registerReplCommands(
193+
disposableArray,
194+
interpreterService.object,
195+
executionHelper.object,
196+
commandManager.object,
197+
);
198+
199+
expect(commandHandler).not.to.be.an('undefined', 'Command handler not initialized');
200+
201+
await commandHandler!('uri');
202+
sinon.assert.notCalled(getNativeReplStub);
203+
});
204+
});

0 commit comments

Comments
 (0)