From 72f874c084906b7b64248f94eab470cb022a5fa0 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Mon, 13 Mar 2023 14:25:55 +0100 Subject: [PATCH 01/10] Add nushell support to venv activation --- src/client/common/serviceRegistry.ts | 6 +++ .../environmentActivationProviders/nushell.ts | 41 +++++++++++++++++++ src/client/common/terminal/helper.ts | 7 +++- src/client/common/terminal/types.ts | 2 + 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/client/common/terminal/environmentActivationProviders/nushell.ts diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index b68f56042c1f..1e8f4e73b0b4 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -69,6 +69,7 @@ import { IProcessLogger } from './process/types'; import { TerminalActivator } from './terminal/activator'; import { PowershellTerminalActivationFailedHandler } from './terminal/activator/powershellFailedHandler'; import { Bash } from './terminal/environmentActivationProviders/bash'; +import { Nushell } from './terminal/environmentActivationProviders/nushell'; import { CommandPromptAndPowerShell } from './terminal/environmentActivationProviders/commandPrompt'; import { CondaActivationCommandProvider } from './terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from './terminal/environmentActivationProviders/pipEnvActivationProvider'; @@ -149,6 +150,11 @@ export function registerTypes(serviceManager: IServiceManager): void { CommandPromptAndPowerShell, TerminalActivationProviders.commandPromptAndPowerShell, ); + serviceManager.addSingleton( + ITerminalActivationCommandProvider, + Nushell, + TerminalActivationProviders.nushell, + ); serviceManager.addSingleton( ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, diff --git a/src/client/common/terminal/environmentActivationProviders/nushell.ts b/src/client/common/terminal/environmentActivationProviders/nushell.ts new file mode 100644 index 000000000000..b38b58543c2e --- /dev/null +++ b/src/client/common/terminal/environmentActivationProviders/nushell.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable } from 'inversify'; +import '../../extensions'; +import { TerminalShellType } from '../types'; +import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; + +// For a given shell the scripts are in order of precedence. +const SCRIPTS: ActivationScripts = { + [TerminalShellType.nushell]: ['activate.nu'], +} as ActivationScripts; + +export function getAllScripts(): string[] { + const scripts: string[] = []; + for (const key of Object.keys(SCRIPTS)) { + const shell = key as TerminalShellType; + for (const name of SCRIPTS[shell]) { + if (!scripts.includes(name)) { + scripts.push(name); + } + } + } + return scripts; +} + +@injectable() +export class Nushell extends VenvBaseActivationCommandProvider { + protected readonly scripts = SCRIPTS; + + public async getActivationCommandsForInterpreter( + pythonPath: string, + targetShell: TerminalShellType, + ): Promise { + const scriptFile = await this.findScriptFile(pythonPath, targetShell); + if (!scriptFile) { + return; + } + return [`overlay activate ${scriptFile.fileToCommandArgumentForPythonExt()}`]; + } +} diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index 304c98b4cd81..f1a89df10786 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -42,6 +42,9 @@ export class TerminalHelper implements ITerminalHelper { @named(TerminalActivationProviders.commandPromptAndPowerShell) private readonly commandPromptAndPowerShell: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) + @named(TerminalActivationProviders.nushell) + private readonly nushell: ITerminalActivationCommandProvider, + @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pyenv) private readonly pyenv: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) @@ -72,7 +75,7 @@ export class TerminalHelper implements ITerminalHelper { resource?: Uri, interpreter?: PythonEnvironment, ): Promise { - const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell]; + const providers = [this.pipenv, this.pyenv, this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; const promise = this.getActivationCommands(resource || undefined, interpreter, terminalShellType, providers); this.sendTelemetry( terminalShellType, @@ -90,7 +93,7 @@ export class TerminalHelper implements ITerminalHelper { if (this.platform.osType === OSType.Unknown) { return; } - const providers = [this.bashCShellFish, this.commandPromptAndPowerShell]; + const providers = [this.bashCShellFish, this.commandPromptAndPowerShell, this.nushell]; const promise = this.getActivationCommands(resource, interpreter, shell, providers); this.sendTelemetry( shell, diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index e02fa1c03fd7..880bf0dd72fb 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -11,6 +11,7 @@ import { IDisposable, Resource } from '../types'; export enum TerminalActivationProviders { bashCShellFish = 'bashCShellFish', commandPromptAndPowerShell = 'commandPromptAndPowerShell', + nushell = 'nushell', pyenv = 'pyenv', conda = 'conda', pipenv = 'pipenv', @@ -26,6 +27,7 @@ export enum TerminalShellType { fish = 'fish', cshell = 'cshell', tcshell = 'tshell', + nushell = 'nushell', wsl = 'wsl', xonsh = 'xonsh', other = 'other', From 1115b70a9f61aa17d3d9a7b721d8bc39ed32ed12 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Tue, 14 Mar 2023 14:50:42 +0100 Subject: [PATCH 02/10] add nu shell detector --- src/client/common/terminal/shellDetectors/baseShellDetector.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client/common/terminal/shellDetectors/baseShellDetector.ts b/src/client/common/terminal/shellDetectors/baseShellDetector.ts index 657c51e82af6..d3c3967d3e3a 100644 --- a/src/client/common/terminal/shellDetectors/baseShellDetector.ts +++ b/src/client/common/terminal/shellDetectors/baseShellDetector.ts @@ -30,6 +30,7 @@ const IS_POWERSHELL_CORE = /(pwsh$)/i; const IS_FISH = /(fish$)/i; const IS_CSHELL = /(csh$)/i; const IS_TCSHELL = /(tcsh$)/i; +const IS_NUSHELL = /(nu$)/i; const IS_XONSH = /(xonsh$)/i; const detectableShells = new Map(); @@ -43,6 +44,7 @@ detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND); detectableShells.set(TerminalShellType.fish, IS_FISH); detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL); detectableShells.set(TerminalShellType.cshell, IS_CSHELL); +detectableShells.set(TerminalShellType.nushell, IS_NUSHELL); detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE); detectableShells.set(TerminalShellType.xonsh, IS_XONSH); From 8f3aab0c84fca88f42f88d85f13b6b3ce7b3e086 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Tue, 14 Mar 2023 14:52:05 +0100 Subject: [PATCH 03/10] Simplify ActivationScripts type --- .../baseActivationProvider.ts | 2 +- .../environmentActivationProviders/bash.ts | 9 ++++----- .../commandPrompt.ts | 15 +++++++-------- .../environmentActivationProviders/nushell.ts | 7 +++---- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts index ca87b172f0a5..fbd4814530ec 100644 --- a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts @@ -66,7 +66,7 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma ): Promise; } -export type ActivationScripts = Record; +export type ActivationScripts = Partial>; export abstract class VenvBaseActivationCommandProvider extends BaseActivationCommandProvider { public isShellSupported(targetShell: TerminalShellType): boolean { diff --git a/src/client/common/terminal/environmentActivationProviders/bash.ts b/src/client/common/terminal/environmentActivationProviders/bash.ts index 827201037570..6c97d0f7013c 100644 --- a/src/client/common/terminal/environmentActivationProviders/bash.ts +++ b/src/client/common/terminal/environmentActivationProviders/bash.ts @@ -7,7 +7,7 @@ import { TerminalShellType } from '../types'; import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; // For a given shell the scripts are in order of precedence. -const SCRIPTS: ActivationScripts = ({ +const SCRIPTS: ActivationScripts = { // Group 1 [TerminalShellType.wsl]: ['activate.sh', 'activate'], [TerminalShellType.ksh]: ['activate.sh', 'activate'], @@ -19,13 +19,12 @@ const SCRIPTS: ActivationScripts = ({ [TerminalShellType.cshell]: ['activate.csh'], // Group 3 [TerminalShellType.fish]: ['activate.fish'], -} as unknown) as ActivationScripts; +}; export function getAllScripts(): string[] { const scripts: string[] = []; - for (const key of Object.keys(SCRIPTS)) { - const shell = key as TerminalShellType; - for (const name of SCRIPTS[shell]) { + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { if (!scripts.includes(name)) { scripts.push(name); } diff --git a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts index 25ab46ca1fb4..548db6e0bfc7 100644 --- a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts +++ b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts @@ -9,19 +9,18 @@ import { TerminalShellType } from '../types'; import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActivationProvider'; // For a given shell the scripts are in order of precedence. -const SCRIPTS: ActivationScripts = ({ +const SCRIPTS: ActivationScripts = { // Group 1 [TerminalShellType.commandPrompt]: ['activate.bat', 'Activate.ps1'], // Group 2 [TerminalShellType.powershell]: ['Activate.ps1', 'activate.bat'], [TerminalShellType.powershellCore]: ['Activate.ps1', 'activate.bat'], -} as unknown) as ActivationScripts; +}; export function getAllScripts(pathJoin: (...p: string[]) => string): string[] { const scripts: string[] = []; - for (const key of Object.keys(SCRIPTS)) { - const shell = key as TerminalShellType; - for (const name of SCRIPTS[shell]) { + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { if (!scripts.includes(name)) { scripts.push( name, @@ -40,11 +39,11 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide protected readonly scripts: ActivationScripts; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); - this.scripts = ({} as unknown) as ActivationScripts; - for (const key of Object.keys(SCRIPTS)) { + this.scripts = {}; + for (const [key, names] of Object.entries(SCRIPTS)) { const shell = key as TerminalShellType; const scripts: string[] = []; - for (const name of SCRIPTS[shell]) { + for (const name of names) { scripts.push( name, // We also add scripts in subdirs. diff --git a/src/client/common/terminal/environmentActivationProviders/nushell.ts b/src/client/common/terminal/environmentActivationProviders/nushell.ts index b38b58543c2e..fbac4c480515 100644 --- a/src/client/common/terminal/environmentActivationProviders/nushell.ts +++ b/src/client/common/terminal/environmentActivationProviders/nushell.ts @@ -9,13 +9,12 @@ import { ActivationScripts, VenvBaseActivationCommandProvider } from './baseActi // For a given shell the scripts are in order of precedence. const SCRIPTS: ActivationScripts = { [TerminalShellType.nushell]: ['activate.nu'], -} as ActivationScripts; +}; export function getAllScripts(): string[] { const scripts: string[] = []; - for (const key of Object.keys(SCRIPTS)) { - const shell = key as TerminalShellType; - for (const name of SCRIPTS[shell]) { + for (const names of Object.values(SCRIPTS)) { + for (const name of names) { if (!scripts.includes(name)) { scripts.push(name); } From 71117dfe19391a30df6ea98c8ed4fbc5ba12f827 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Tue, 14 Mar 2023 15:13:41 +0100 Subject: [PATCH 04/10] Add mock nushell activation providers to tests --- src/test/common/terminals/activation.conda.unit.test.ts | 2 ++ src/test/common/terminals/helper.unit.test.ts | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index 904752d698c9..84e4bffacfc1 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -12,6 +12,7 @@ import { IFileSystem, IPlatformService } from '../../../client/common/platform/t import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../../client/common/terminal/environmentActivationProviders/commandPrompt'; +import { Nushell } from '../../../client/common/terminal/environmentActivationProviders/nushell'; import { CondaActivationCommandProvider, _getPowershellCommands, @@ -110,6 +111,7 @@ suite('Terminal Environment Activation conda', () => { ), instance(bash), mock(CommandPromptAndPowerShell), + mock(Nushell), mock(PyEnvActivationCommandProvider), mock(PipEnvActivationCommandProvider), [], diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index 59ac56ebf82e..63668239b260 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -13,6 +13,7 @@ import { PlatformService } from '../../../client/common/platform/platformService import { IPlatformService } from '../../../client/common/platform/types'; import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../../client/common/terminal/environmentActivationProviders/commandPrompt'; +import { Nushell } from '../../../client/common/terminal/environmentActivationProviders/nushell'; import { CondaActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider'; import { PyEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; @@ -42,6 +43,7 @@ suite('Terminal Service helpers', () => { let condaActivationProvider: ITerminalActivationCommandProvider; let bashActivationProvider: ITerminalActivationCommandProvider; let cmdActivationProvider: ITerminalActivationCommandProvider; + let nushellActivationProvider: ITerminalActivationCommandProvider; let pyenvActivationProvider: ITerminalActivationCommandProvider; let pipenvActivationProvider: ITerminalActivationCommandProvider; let pythonSettings: PythonSettings; @@ -67,6 +69,7 @@ suite('Terminal Service helpers', () => { condaActivationProvider = mock(CondaActivationCommandProvider); bashActivationProvider = mock(Bash); cmdActivationProvider = mock(CommandPromptAndPowerShell); + nushellActivationProvider = mock(Nushell); pyenvActivationProvider = mock(PyEnvActivationCommandProvider); pipenvActivationProvider = mock(PipEnvActivationCommandProvider); pythonSettings = mock(PythonSettings); @@ -80,6 +83,7 @@ suite('Terminal Service helpers', () => { instance(condaActivationProvider), instance(bashActivationProvider), instance(cmdActivationProvider), + instance(nushellActivationProvider), instance(pyenvActivationProvider), instance(pipenvActivationProvider), [instance(mockDetector)], From d00309d4d2e926ee484fad504d9a2214339a9240 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Tue, 14 Mar 2023 15:23:47 +0100 Subject: [PATCH 05/10] add nushell to tests --- src/test/common/terminals/helper.unit.test.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index 63668239b260..b247cd2fcba9 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -217,6 +217,7 @@ suite('Terminal Service helpers', () => { when(bashActivationProvider.isShellSupported(anything())).thenReturn(false); when(cmdActivationProvider.isShellSupported(anything())).thenReturn(false); + when(nushellActivationProvider.isShellSupported(anything())).thenReturn(false); when(pyenvActivationProvider.isShellSupported(anything())).thenReturn(false); when(pipenvActivationProvider.isShellSupported(anything())).thenReturn(false); @@ -229,6 +230,7 @@ suite('Terminal Service helpers', () => { verify(pythonSettings.pythonPath).once(); verify(condaService.isCondaEnvironment(pythonPath)).once(); verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); + verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); @@ -242,6 +244,7 @@ suite('Terminal Service helpers', () => { when(bashActivationProvider.isShellSupported(anything())).thenReturn(true); when(cmdActivationProvider.isShellSupported(anything())).thenReturn(false); + when(nushellActivationProvider.isShellSupported(anything())).thenReturn(false); when(pyenvActivationProvider.isShellSupported(anything())).thenReturn(false); when(pipenvActivationProvider.isShellSupported(anything())).thenReturn(false); @@ -252,6 +255,7 @@ suite('Terminal Service helpers', () => { verify(condaService.isCondaEnvironment(pythonPath)).once(); verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); verify(bashActivationProvider.getActivationCommands(resource, anything())).once(); + verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); @@ -266,7 +270,12 @@ suite('Terminal Service helpers', () => { ); when(pipenvActivationProvider.isShellSupported(anything())).thenReturn(true); - [bashActivationProvider, cmdActivationProvider, pyenvActivationProvider].forEach((provider) => { + [ + bashActivationProvider, + cmdActivationProvider, + nushellActivationProvider, + pyenvActivationProvider, + ].forEach((provider) => { when(provider.getActivationCommands(resource, anything())).thenResolve(['Something']); when(provider.isShellSupported(anything())).thenReturn(true); }); @@ -282,6 +291,7 @@ suite('Terminal Service helpers', () => { verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.getActivationCommands(resource, anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); + verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); }); test('Activation command must return command from Command Prompt if that is supported and others are not', async () => { const pythonPath = 'some python Path value'; @@ -292,6 +302,7 @@ suite('Terminal Service helpers', () => { when(bashActivationProvider.isShellSupported(anything())).thenReturn(false); when(cmdActivationProvider.isShellSupported(anything())).thenReturn(true); + when(nushellActivationProvider.isShellSupported(anything())).thenReturn(false); when(pyenvActivationProvider.isShellSupported(anything())).thenReturn(false); when(pipenvActivationProvider.isShellSupported(anything())).thenReturn(false); @@ -301,21 +312,24 @@ suite('Terminal Service helpers', () => { verify(pythonSettings.pythonPath).once(); verify(condaService.isCondaEnvironment(pythonPath)).once(); verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); + verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.getActivationCommands(resource, anything())).once(); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); }); - test('Activation command must return command from Command Prompt if that is supported, and so is bash but no commands are returned', async () => { + test('Activation command must return command from Command Prompt if that is supported, and so is bash and nushell but no commands are returned', async () => { const pythonPath = 'some python Path value'; const expectCommand = ['one', 'two']; ensureCondaIsSupported(false, pythonPath, []); when(cmdActivationProvider.getActivationCommands(resource, anything())).thenResolve(expectCommand); when(bashActivationProvider.getActivationCommands(resource, anything())).thenResolve([]); + when(nushellActivationProvider.getActivationCommands(resource, anything())).thenResolve([]); when(bashActivationProvider.isShellSupported(anything())).thenReturn(true); when(cmdActivationProvider.isShellSupported(anything())).thenReturn(true); + when(nushellActivationProvider.isShellSupported(anything())).thenReturn(true); when(pyenvActivationProvider.isShellSupported(anything())).thenReturn(false); when(pipenvActivationProvider.isShellSupported(anything())).thenReturn(false); @@ -327,9 +341,11 @@ suite('Terminal Service helpers', () => { verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); verify(bashActivationProvider.getActivationCommands(resource, anything())).once(); verify(cmdActivationProvider.getActivationCommands(resource, anything())).once(); + verify(nushellActivationProvider.getActivationCommands(resource, anything())).once(); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); + verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); }); [undefined, pythonInterpreter].forEach((interpreter) => { test('Activation command for Shell must be empty for unknown os', async () => { @@ -357,6 +373,7 @@ suite('Terminal Service helpers', () => { when(platformService.osType).thenReturn(osType); when(bashActivationProvider.isShellSupported(shellToExpect)).thenReturn(false); when(cmdActivationProvider.isShellSupported(shellToExpect)).thenReturn(false); + when(nushellActivationProvider.isShellSupported(shellToExpect)).thenReturn(false); const cmd = await helper.getEnvironmentActivationShellCommands( resource, @@ -371,6 +388,7 @@ suite('Terminal Service helpers', () => { verify(pyenvActivationProvider.isShellSupported(anything())).never(); verify(pipenvActivationProvider.isShellSupported(anything())).never(); verify(cmdActivationProvider.isShellSupported(shellToExpect)).atLeast(1); + verify(nushellActivationProvider.isShellSupported(shellToExpect)).atLeast(1); }); }); }); From 9f1bc4ab4dc5c89013f6ca2976df826461d14258 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Wed, 15 Mar 2023 14:59:50 +0100 Subject: [PATCH 06/10] Fix lints --- .eslintignore | 4 ---- .../baseActivationProvider.ts | 3 +++ .../terminal/environmentActivationProviders/bash.ts | 2 +- .../environmentActivationProviders/commandPrompt.ts | 13 ++++++++----- .../environmentActivationProviders/nushell.ts | 2 +- .../pyenvActivationProvider.ts | 5 +++-- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.eslintignore b/.eslintignore index bce20d67c2c9..1c2e5c9d4fe4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -208,10 +208,6 @@ src/client/common/terminal/shellDetectors/vscEnvironmentShellDetector.ts src/client/common/terminal/shellDetectors/terminalNameShellDetector.ts src/client/common/terminal/shellDetectors/settingsShellDetector.ts src/client/common/terminal/shellDetectors/baseShellDetector.ts -src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts -src/client/common/terminal/environmentActivationProviders/commandPrompt.ts -src/client/common/terminal/environmentActivationProviders/bash.ts -src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts src/client/common/utils/decorators.ts src/client/common/utils/enum.ts src/client/common/utils/platform.ts diff --git a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts index fbd4814530ec..abc2ff89df63 100644 --- a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -48,6 +49,7 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma constructor(@inject(IServiceContainer) protected readonly serviceContainer: IServiceContainer) {} public abstract isShellSupported(targetShell: TerminalShellType): boolean; + public async getActivationCommands( resource: Uri | undefined, targetShell: TerminalShellType, @@ -60,6 +62,7 @@ abstract class BaseActivationCommandProvider implements ITerminalActivationComma } return this.getActivationCommandsForInterpreter(interpreter.path, targetShell); } + public abstract getActivationCommandsForInterpreter( pythonPath: string, targetShell: TerminalShellType, diff --git a/src/client/common/terminal/environmentActivationProviders/bash.ts b/src/client/common/terminal/environmentActivationProviders/bash.ts index 6c97d0f7013c..00c4d3da114c 100644 --- a/src/client/common/terminal/environmentActivationProviders/bash.ts +++ b/src/client/common/terminal/environmentActivationProviders/bash.ts @@ -43,7 +43,7 @@ export class Bash extends VenvBaseActivationCommandProvider { ): Promise { const scriptFile = await this.findScriptFile(pythonPath, targetShell); if (!scriptFile) { - return; + return undefined; } return [`source ${scriptFile.fileToCommandArgumentForPythonExt()}`]; } diff --git a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts index 548db6e0bfc7..6d40e2c390a0 100644 --- a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts +++ b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts @@ -37,6 +37,7 @@ export function getAllScripts(pathJoin: (...p: string[]) => string): string[] { @injectable() export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvider { protected readonly scripts: ActivationScripts; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); this.scripts = {}; @@ -61,21 +62,23 @@ export class CommandPromptAndPowerShell extends VenvBaseActivationCommandProvide ): Promise { const scriptFile = await this.findScriptFile(pythonPath, targetShell); if (!scriptFile) { - return; + return undefined; } if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('activate.bat')) { return [scriptFile.fileToCommandArgumentForPythonExt()]; - } else if ( + } + if ( (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) && scriptFile.endsWith('Activate.ps1') ) { return [`& ${scriptFile.fileToCommandArgumentForPythonExt()}`]; - } else if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) { + } + if (targetShell === TerminalShellType.commandPrompt && scriptFile.endsWith('Activate.ps1')) { // lets not try to run the powershell file from command prompt (user may not have powershell) return []; - } else { - return; } + + return undefined; } } diff --git a/src/client/common/terminal/environmentActivationProviders/nushell.ts b/src/client/common/terminal/environmentActivationProviders/nushell.ts index fbac4c480515..ef40fb8af9ef 100644 --- a/src/client/common/terminal/environmentActivationProviders/nushell.ts +++ b/src/client/common/terminal/environmentActivationProviders/nushell.ts @@ -33,7 +33,7 @@ export class Nushell extends VenvBaseActivationCommandProvider { ): Promise { const scriptFile = await this.findScriptFile(pythonPath, targetShell); if (!scriptFile) { - return; + return undefined; } return [`overlay activate ${scriptFile.fileToCommandArgumentForPythonExt()}`]; } diff --git a/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts index 91347f35ae95..6b5ced048672 100644 --- a/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/pyenvActivationProvider.ts @@ -14,6 +14,7 @@ import { ITerminalActivationCommandProvider, TerminalShellType } from '../types' export class PyEnvActivationCommandProvider implements ITerminalActivationCommandProvider { constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) {} + // eslint-disable-next-line class-methods-use-this public isShellSupported(_targetShell: TerminalShellType): boolean { return true; } @@ -23,7 +24,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman .get(IInterpreterService) .getActiveInterpreter(resource); if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) { - return; + return undefined; } return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`]; @@ -37,7 +38,7 @@ export class PyEnvActivationCommandProvider implements ITerminalActivationComman .get(IInterpreterService) .getInterpreterDetails(pythonPath); if (!interpreter || interpreter.envType !== EnvironmentType.Pyenv || !interpreter.envName) { - return; + return undefined; } return [`pyenv shell ${interpreter.envName.toCommandArgumentForPythonExt()}`]; From 0e0b72bf63759831c2dfe3f82f3eb279080eb155 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Sat, 18 Mar 2023 14:07:23 +0100 Subject: [PATCH 07/10] Install nushell in all test helpers --- src/test/common/installer.test.ts | 6 ++++++ src/test/common/moduleInstaller.test.ts | 6 ++++++ src/test/common/serviceRegistry.unit.test.ts | 2 ++ 3 files changed, 14 insertions(+) diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index 6c1a6383c2b6..5ad5a95f362b 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -53,6 +53,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator'; import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler'; import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt'; +import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell'; import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider'; import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; @@ -220,6 +221,11 @@ suite('Installer', () => { CommandPromptAndPowerShell, TerminalActivationProviders.commandPromptAndPowerShell, ); + ioc.serviceManager.addSingleton( + ITerminalActivationCommandProvider, + Nushell, + TerminalActivationProviders.nushell, + ); ioc.serviceManager.addSingleton( ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 302587902c16..43327f5c2483 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -52,6 +52,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator'; import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler'; import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt'; +import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell'; import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider'; import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; @@ -231,6 +232,11 @@ suite('Module Installer', () => { CommandPromptAndPowerShell, TerminalActivationProviders.commandPromptAndPowerShell, ); + ioc.serviceManager.addSingleton( + ITerminalActivationCommandProvider, + Nushell, + TerminalActivationProviders.nushell, + ); ioc.serviceManager.addSingleton( ITerminalActivationCommandProvider, PyEnvActivationCommandProvider, diff --git a/src/test/common/serviceRegistry.unit.test.ts b/src/test/common/serviceRegistry.unit.test.ts index 1bb6f30705fd..2b7963c2e369 100644 --- a/src/test/common/serviceRegistry.unit.test.ts +++ b/src/test/common/serviceRegistry.unit.test.ts @@ -41,6 +41,7 @@ import { TerminalActivator } from '../../client/common/terminal/activator'; import { PowershellTerminalActivationFailedHandler } from '../../client/common/terminal/activator/powershellFailedHandler'; import { Bash } from '../../client/common/terminal/environmentActivationProviders/bash'; import { CommandPromptAndPowerShell } from '../../client/common/terminal/environmentActivationProviders/commandPrompt'; +import { Nushell } from '../../client/common/terminal/environmentActivationProviders/nushell'; import { CondaActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { PipEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pipEnvActivationProvider'; import { PyEnvActivationCommandProvider } from '../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; @@ -116,6 +117,7 @@ suite('Common - Service Registry', () => { CommandPromptAndPowerShell, TerminalActivationProviders.commandPromptAndPowerShell, ], + [ITerminalActivationCommandProvider, Nushell, TerminalActivationProviders.nushell], [IToolExecutionPath, PipEnvExecutionPath, ToolExecutionPath.pipenv], [ITerminalActivationCommandProvider, CondaActivationCommandProvider, TerminalActivationProviders.conda], [ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv], From 9e614df7edbe53f095e15c56133a0c98b3c5579d Mon Sep 17 00:00:00 2001 From: Philipp A Date: Wed, 22 Mar 2023 17:40:13 +0100 Subject: [PATCH 08/10] fix tests with TODO --- src/test/common/terminals/helper.unit.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index b247cd2fcba9..9080e94f6abc 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -338,12 +338,13 @@ suite('Terminal Service helpers', () => { expect(cmd).to.deep.equal(expectCommand); verify(pythonSettings.pythonPath).once(); verify(condaService.isCondaEnvironment(pythonPath)).once(); - verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); verify(bashActivationProvider.getActivationCommands(resource, anything())).once(); verify(cmdActivationProvider.getActivationCommands(resource, anything())).once(); - verify(nushellActivationProvider.getActivationCommands(resource, anything())).once(); + // TODO: figure out why getActivationCommands is run for bash but not for nushell + //verify(nushellActivationProvider.getActivationCommands(resource, anything())).once(); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); + verify(bashActivationProvider.isShellSupported(anything())).atLeast(1); verify(cmdActivationProvider.isShellSupported(anything())).atLeast(1); verify(nushellActivationProvider.isShellSupported(anything())).atLeast(1); }); From 90f122e23f97adb4adeb5d09c1358b76b1fdf614 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Fri, 24 Mar 2023 13:44:07 +0100 Subject: [PATCH 09/10] Add test and fix command --- build/existingFiles.json | 1 + .../environmentActivationProviders/nushell.ts | 2 +- .../terminals/activation.bash.unit.test.ts | 174 +++++++++--------- .../terminals/activation.nushell.unit.test.ts | 72 ++++++++ 4 files changed, 164 insertions(+), 85 deletions(-) create mode 100644 src/test/common/terminals/activation.nushell.unit.test.ts diff --git a/build/existingFiles.json b/build/existingFiles.json index 0d7e0c3c41cc..1f5acc727d8e 100644 --- a/build/existingFiles.json +++ b/build/existingFiles.json @@ -379,6 +379,7 @@ "src/test/common/socketStream.test.ts", "src/test/common/terminals/activation.bash.unit.test.ts", "src/test/common/terminals/activation.commandPrompt.unit.test.ts", + "src/test/common/terminals/activation.nushell.unit.test.ts", "src/test/common/terminals/activation.conda.unit.test.ts", "src/test/common/terminals/activation.unit.test.ts", "src/test/common/terminals/activator/base.unit.test.ts", diff --git a/src/client/common/terminal/environmentActivationProviders/nushell.ts b/src/client/common/terminal/environmentActivationProviders/nushell.ts index ef40fb8af9ef..333fd5167770 100644 --- a/src/client/common/terminal/environmentActivationProviders/nushell.ts +++ b/src/client/common/terminal/environmentActivationProviders/nushell.ts @@ -35,6 +35,6 @@ export class Nushell extends VenvBaseActivationCommandProvider { if (!scriptFile) { return undefined; } - return [`overlay activate ${scriptFile.fileToCommandArgumentForPythonExt()}`]; + return [`overlay use ${scriptFile.fileToCommandArgumentForPythonExt()}`]; } } diff --git a/src/test/common/terminals/activation.bash.unit.test.ts b/src/test/common/terminals/activation.bash.unit.test.ts index 28bab49fd55a..cd057e7be3e5 100644 --- a/src/test/common/terminals/activation.bash.unit.test.ts +++ b/src/test/common/terminals/activation.bash.unit.test.ts @@ -24,107 +24,113 @@ suite('Terminal Environment Activation (bash)', () => { ? 'and there are spaces in the script file (pythonpath),' : 'and there are no spaces in the script file (pythonpath),'; suite(suiteTitle, () => { - ['activate', 'activate.sh', 'activate.csh', 'activate.fish', 'activate.bat', 'Activate.ps1'].forEach( - (scriptFileName) => { - suite(`and script file is ${scriptFileName}`, () => { - let serviceContainer: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - let fileSystem: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - fileSystem = TypeMoq.Mock.ofType(); - serviceContainer.setup((c) => c.get(IFileSystem)).returns(() => fileSystem.object); + [ + 'activate', + 'activate.sh', + 'activate.csh', + 'activate.fish', + 'activate.bat', + 'activate.nu', + 'Activate.ps1', + ].forEach((scriptFileName) => { + suite(`and script file is ${scriptFileName}`, () => { + let serviceContainer: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); + serviceContainer.setup((c) => c.get(IFileSystem)).returns(() => fileSystem.object); - interpreterService = TypeMoq.Mock.ofType(); - interpreterService - .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) - .returns(() => Promise.resolve(({ path: pythonPath } as unknown) as PythonEnvironment)); - serviceContainer - .setup((c) => c.get(IInterpreterService)) - .returns(() => interpreterService.object); - }); + interpreterService = TypeMoq.Mock.ofType(); + interpreterService + .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ path: pythonPath } as unknown) as PythonEnvironment)); + serviceContainer + .setup((c) => c.get(IInterpreterService)) + .returns(() => interpreterService.object); + }); + + getNamesAndValues(TerminalShellType).forEach((shellType) => { + let isScriptFileSupported = false; + switch (shellType.value) { + case TerminalShellType.zsh: + case TerminalShellType.ksh: + case TerminalShellType.wsl: + case TerminalShellType.gitbash: + case TerminalShellType.bash: { + isScriptFileSupported = ['activate', 'activate.sh'].indexOf(scriptFileName) >= 0; + break; + } + case TerminalShellType.fish: { + isScriptFileSupported = ['activate.fish'].indexOf(scriptFileName) >= 0; + break; + } + case TerminalShellType.tcshell: + case TerminalShellType.cshell: { + isScriptFileSupported = ['activate.csh'].indexOf(scriptFileName) >= 0; + break; + } + default: { + isScriptFileSupported = false; + } + } + const titleTitle = isScriptFileSupported + ? `Ensure bash Activation command returns activation command (Shell: ${shellType.name})` + : `Ensure bash Activation command returns undefined (Shell: ${shellType.name})`; + + test(titleTitle, async () => { + const bash = new Bash(serviceContainer.object); - getNamesAndValues(TerminalShellType).forEach((shellType) => { - let isScriptFileSupported = false; + const supported = bash.isShellSupported(shellType.value); switch (shellType.value) { + case TerminalShellType.wsl: case TerminalShellType.zsh: case TerminalShellType.ksh: - case TerminalShellType.wsl: + case TerminalShellType.bash: case TerminalShellType.gitbash: - case TerminalShellType.bash: { - isScriptFileSupported = ['activate', 'activate.sh'].indexOf(scriptFileName) >= 0; - break; - } - case TerminalShellType.fish: { - isScriptFileSupported = ['activate.fish'].indexOf(scriptFileName) >= 0; - break; - } case TerminalShellType.tcshell: - case TerminalShellType.cshell: { - isScriptFileSupported = ['activate.csh'].indexOf(scriptFileName) >= 0; + case TerminalShellType.cshell: + case TerminalShellType.fish: { + expect(supported).to.be.equal( + true, + `${shellType.name} shell not supported (it should be)`, + ); break; } default: { - isScriptFileSupported = false; + expect(supported).to.be.equal( + false, + `${shellType.name} incorrectly supported (should not be)`, + ); + // No point proceeding with other tests. + return; } } - const titleTitle = isScriptFileSupported - ? `Ensure bash Activation command returns activation command (Shell: ${shellType.name})` - : `Ensure bash Activation command returns undefined (Shell: ${shellType.name})`; - test(titleTitle, async () => { - const bash = new Bash(serviceContainer.object); + const pathToScriptFile = path.join(path.dirname(pythonPath), scriptFileName); + fileSystem + .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))) + .returns(() => Promise.resolve(true)); + const command = await bash.getActivationCommands(undefined, shellType.value); - const supported = bash.isShellSupported(shellType.value); - switch (shellType.value) { - case TerminalShellType.wsl: - case TerminalShellType.zsh: - case TerminalShellType.ksh: - case TerminalShellType.bash: - case TerminalShellType.gitbash: - case TerminalShellType.tcshell: - case TerminalShellType.cshell: - case TerminalShellType.fish: { - expect(supported).to.be.equal( - true, - `${shellType.name} shell not supported (it should be)`, - ); - break; - } - default: { - expect(supported).to.be.equal( - false, - `${shellType.name} incorrectly supported (should not be)`, - ); - // No point proceeding with other tests. - return; - } - } - - const pathToScriptFile = path.join(path.dirname(pythonPath), scriptFileName); - fileSystem - .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))) - .returns(() => Promise.resolve(true)); - const command = await bash.getActivationCommands(undefined, shellType.value); - - if (isScriptFileSupported) { - // Ensure the script file is of the following form: - // source "" - // Ensure the path is quoted if it contains any spaces. - // Ensure it contains the name of the environment as an argument to the script file. + if (isScriptFileSupported) { + // Ensure the script file is of the following form: + // source "" + // Ensure the path is quoted if it contains any spaces. + // Ensure it contains the name of the environment as an argument to the script file. - expect(command).to.be.deep.equal( - [`source ${pathToScriptFile.fileToCommandArgumentForPythonExt()}`.trim()], - 'Invalid command', - ); - } else { - expect(command).to.be.equal(undefined, 'Command should be undefined'); - } - }); + expect(command).to.be.deep.equal( + [`source ${pathToScriptFile.fileToCommandArgumentForPythonExt()}`.trim()], + 'Invalid command', + ); + } else { + expect(command).to.be.equal(undefined, 'Command should be undefined'); + } }); }); - }, - ); + }); + }); }); }); }); diff --git a/src/test/common/terminals/activation.nushell.unit.test.ts b/src/test/common/terminals/activation.nushell.unit.test.ts new file mode 100644 index 000000000000..bf748bc7c053 --- /dev/null +++ b/src/test/common/terminals/activation.nushell.unit.test.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { expect } from 'chai'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import '../../../client/common/extensions'; +import { IFileSystem } from '../../../client/common/platform/types'; +import { Nushell } from '../../../client/common/terminal/environmentActivationProviders/nushell'; +import { TerminalShellType } from '../../../client/common/terminal/types'; +import { getNamesAndValues } from '../../../client/common/utils/enum'; +import { IInterpreterService } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { PythonEnvironment } from '../../../client/pythonEnvironments/info'; + +const pythonPath = 'usr/bin/python'; + +suite('Terminal Environment Activation (nushell)', () => { + for (const scriptFileName of ['activate', 'activate.sh', 'activate.nu']) { + suite(`and script file is ${scriptFileName}`, () => { + let serviceContainer: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); + serviceContainer.setup((c) => c.get(IFileSystem)).returns(() => fileSystem.object); + + interpreterService = TypeMoq.Mock.ofType(); + interpreterService + .setup((i) => i.getActiveInterpreter(TypeMoq.It.isAny())) + .returns(() => Promise.resolve(({ path: pythonPath } as unknown) as PythonEnvironment)); + serviceContainer.setup((c) => c.get(IInterpreterService)).returns(() => interpreterService.object); + }); + + for (const { name, value } of getNamesAndValues(TerminalShellType)) { + const isNushell = value === TerminalShellType.nushell; + const isScriptFileSupported = isNushell && ['activate.nu'].includes(scriptFileName); + const expectedReturn = isScriptFileSupported ? 'activation command' : 'undefined'; + + // eslint-disable-next-line no-loop-func -- setup() takes care of shellType and fileSystem reinitialization + test(`Ensure nushell Activation command returns ${expectedReturn} (Shell: ${name})`, async () => { + const nu = new Nushell(serviceContainer.object); + + const supported = nu.isShellSupported(value); + if (isNushell) { + expect(supported).to.be.equal(true, `${name} shell not supported (it should be)`); + } else { + expect(supported).to.be.equal(false, `${name} incorrectly supported (should not be)`); + // No point proceeding with other tests. + return; + } + + const pathToScriptFile = path.join(path.dirname(pythonPath), scriptFileName); + fileSystem + .setup((fs) => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))) + .returns(() => Promise.resolve(true)); + const command = await nu.getActivationCommands(undefined, value); + + if (isScriptFileSupported) { + expect(command).to.be.deep.equal( + [`overlay use ${pathToScriptFile.fileToCommandArgumentForPythonExt()}`.trim()], + 'Invalid command', + ); + } else { + expect(command).to.be.equal(undefined, 'Command should be undefined'); + } + }); + } + }); + } +}); From 2b371b37811a6379c952c7ec217929c6c2f3ba20 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Thu, 30 Mar 2023 11:13:32 +0200 Subject: [PATCH 10/10] Fix helper test Co-authored-by: Kartik Raj --- src/test/common/terminals/helper.unit.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index 9080e94f6abc..b6a8d44ac030 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -340,8 +340,8 @@ suite('Terminal Service helpers', () => { verify(condaService.isCondaEnvironment(pythonPath)).once(); verify(bashActivationProvider.getActivationCommands(resource, anything())).once(); verify(cmdActivationProvider.getActivationCommands(resource, anything())).once(); - // TODO: figure out why getActivationCommands is run for bash but not for nushell - //verify(nushellActivationProvider.getActivationCommands(resource, anything())).once(); + // It should not be called as command prompt already returns the activation commands and is higher priority. + verify(nushellActivationProvider.getActivationCommands(resource, anything())).never(); verify(pyenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(pipenvActivationProvider.isShellSupported(anything())).atLeast(1); verify(bashActivationProvider.isShellSupported(anything())).atLeast(1);