diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 3ec789f2b4b94..91f40c9d80770 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as process from 'vs/base/common/process'; -import { exists } from 'vs/base/node/pfs'; +import * as pfs from 'vs/base/node/pfs'; import { isString } from 'vs/base/common/types'; import { getCaseInsensitive } from 'vs/base/common/objects'; import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; @@ -20,7 +20,7 @@ export function getWindowsBuildNumber(): number { return buildNumber; } -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise { +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment, exists: (path: string) => Promise = pfs.exists): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return await exists(command) ? command : undefined; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index a6c3ec08d68b2..6a966489014d0 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -139,7 +139,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public async $getAvailableProfiles(configuredProfilesOnly: boolean): Promise { const config = await (await this._extHostConfiguration.getConfigProvider()).getConfiguration().get('terminal.integrated'); - return detectAvailableProfiles(configuredProfilesOnly, this._logService, config as ITerminalConfiguration, await this._variableResolverPromise, this._lastActiveWorkspace); + return detectAvailableProfiles(configuredProfilesOnly, undefined, this._logService, config as ITerminalConfiguration, await this._variableResolverPromise, this._lastActiveWorkspace); } public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise { diff --git a/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts b/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts index a7635d635768d..f354e67329a3f 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProfiles.ts @@ -17,11 +17,18 @@ import * as pfs from 'vs/base/node/pfs'; let profileSources: Map | undefined; -export function detectAvailableProfiles(configuredProfilesOnly: boolean, logService?: ILogService, config?: ITerminalConfiguration, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder, statProvider?: IStatProvider, testPaths?: string[]): Promise { - return platform.isWindows ? detectAvailableWindowsProfiles(configuredProfilesOnly, statProvider, logService, config?.useWslProfiles, config?.profiles.windows, variableResolver, workspaceFolder) : detectAvailableUnixProfiles(statProvider, logService, configuredProfilesOnly, platform.isMacintosh ? config?.profiles.osx : config?.profiles.linux, testPaths, variableResolver, workspaceFolder); +export function detectAvailableProfiles(configuredProfilesOnly: boolean, fsProvider?: IFsProvider, logService?: ILogService, config?: ITerminalConfiguration, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder, testPaths?: string[]): Promise { + fsProvider = fsProvider || { + existsFile: pfs.SymlinkSupport.existsFile, + readFile: fs.promises.readFile + }; + if (platform.isWindows) { + return detectAvailableWindowsProfiles(configuredProfilesOnly, fsProvider, logService, config?.useWslProfiles, config?.profiles.windows, variableResolver, workspaceFolder); + } + return detectAvailableUnixProfiles(fsProvider, logService, configuredProfilesOnly, platform.isMacintosh ? config?.profiles.osx : config?.profiles.linux, testPaths, variableResolver, workspaceFolder); } -async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, statProvider?: IStatProvider, logService?: ILogService, useWslProfiles?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { +async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, fsProvider: IFsProvider, logService?: ILogService, useWslProfiles?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { // Determine the correct System32 path. We want to point to Sysnative // when the 32-bit version of VS Code is running on a 64-bit machine. // The reason for this is because PowerShell's important PSReadline @@ -61,7 +68,7 @@ async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, s applyConfigProfilesToMap(configProfiles, detectedProfiles); - const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), logService, statProvider, variableResolver, workspaceFolder); + const resultProfiles: ITerminalProfile[] = await transformToTerminalProfiles(detectedProfiles.entries(), fsProvider, logService, variableResolver, workspaceFolder); if (!configuredProfilesOnly || (configuredProfilesOnly && useWslProfiles)) { try { @@ -77,7 +84,7 @@ async function detectAvailableWindowsProfiles(configuredProfilesOnly: boolean, s return resultProfiles; } -async function transformToTerminalProfiles(entries: IterableIterator<[string, ITerminalProfileObject]>, logService?: ILogService, statProvider?: IStatProvider, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { +async function transformToTerminalProfiles(entries: IterableIterator<[string, ITerminalProfileObject]>, fsProvider: IFsProvider, logService?: ILogService, variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { const resultProfiles: ITerminalProfile[] = []; for (const [profileName, profile] of entries) { if (profile === null) { continue; } @@ -102,7 +109,8 @@ async function transformToTerminalProfiles(entries: IterableIterator<[string, IT for (let i = 0; i < paths.length; i++) { paths[i] = variableResolver?.resolve(workspaceFolder, paths[i]) || paths[i]; } - const validatedProfile = await validateProfilePaths(profileName, paths, statProvider, args, profile.overrideName, profile.isAutoDetected, logService); + const validatedProfile = await validateProfilePaths(profileName, paths, fsProvider, args, profile.overrideName, profile.isAutoDetected, logService); + console.log('validated', validatedProfile?.profileName + ' ' + validatedProfile?.path); if (validatedProfile) { validatedProfile.isAutoDetected = profile.isAutoDetected; resultProfiles.push(validatedProfile); @@ -201,12 +209,13 @@ async function getWslProfiles(wslPath: string, useWslProfiles?: boolean): Promis return []; } -async function detectAvailableUnixProfiles(statProvider?: IStatProvider, logService?: ILogService, configuredProfilesOnly?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, testPaths?: string[], variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { +async function detectAvailableUnixProfiles(fsProvider: IFsProvider, logService?: ILogService, configuredProfilesOnly?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, testPaths?: string[], variableResolver?: ExtHostVariableResolverService, workspaceFolder?: IWorkspaceFolder): Promise { const detectedProfiles: Map = new Map(); // Add non-quick launch profiles if (!configuredProfilesOnly) { - const contents = await fs.promises.readFile('/etc/shells', 'utf8'); + const contents = await fsProvider.readFile('/etc/shells', 'utf8'); + console.log('contents:' + contents); const profiles = testPaths || contents.split('\n').filter(e => e.trim().indexOf('#') !== 0 && e.trim().length > 0); const counts: Map = new Map(); for (const profile of profiles) { @@ -223,7 +232,7 @@ async function detectAvailableUnixProfiles(statProvider?: IStatProvider, logServ applyConfigProfilesToMap(configProfiles, detectedProfiles); - return await transformToTerminalProfiles(detectedProfiles.entries(), logService, statProvider, variableResolver, workspaceFolder); + return await transformToTerminalProfiles(detectedProfiles.entries(), fsProvider, logService, variableResolver, workspaceFolder); } function applyConfigProfilesToMap(configProfiles: { [key: string]: ITerminalProfileObject } | undefined, profilesMap: Map) { @@ -239,13 +248,13 @@ function applyConfigProfilesToMap(configProfiles: { [key: string]: ITerminalProf } } -async function validateProfilePaths(profileName: string, potentialPaths: string[], statProvider?: IStatProvider, args?: string[] | string, overrideName?: boolean, isAutoDetected?: boolean, logService?: ILogService): Promise { +async function validateProfilePaths(profileName: string, potentialPaths: string[], fsProvider: IFsProvider, args?: string[] | string, overrideName?: boolean, isAutoDetected?: boolean, logService?: ILogService): Promise { if (potentialPaths.length === 0) { return Promise.resolve(undefined); } const path = potentialPaths.shift()!; if (path === '') { - return validateProfilePaths(profileName, potentialPaths, statProvider, args, overrideName, isAutoDetected); + return validateProfilePaths(profileName, potentialPaths, fsProvider, args, overrideName, isAutoDetected); } const profile = { profileName, path, args, overrideName, isAutoDetected }; @@ -254,23 +263,24 @@ async function validateProfilePaths(profileName: string, potentialPaths: string[ if (basename(path) === path) { // The executable isn't an absolute path, try find it on the PATH const envPaths: string[] | undefined = process.env.PATH ? process.env.PATH.split(delimiter) : undefined; - const executable = await findExecutable(path, undefined, envPaths); + const executable = await findExecutable(path, undefined, envPaths, undefined, fsProvider.existsFile); if (!executable) { - return validateProfilePaths(profileName, potentialPaths, statProvider, args); + return validateProfilePaths(profileName, potentialPaths, fsProvider, args); } return profile; } - const result = statProvider ? await statProvider.existsFile(path) : await pfs.SymlinkSupport.existsFile(normalize(path)); + const result = await fsProvider.existsFile(normalize(path)); if (result) { return profile; } - return validateProfilePaths(profileName, potentialPaths, statProvider, args, overrideName, isAutoDetected); + return validateProfilePaths(profileName, potentialPaths, fsProvider, args, overrideName, isAutoDetected); } -export interface IStatProvider { +export interface IFsProvider { existsFile(path: string): Promise, + readFile(path: string, options: { encoding: BufferEncoding, flag?: string | number } | BufferEncoding): Promise; } interface IPotentialTerminalProfile { diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts index 8f986102dbac6..ec9a5820817f0 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts @@ -3,133 +3,220 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import * as assert from 'assert'; -import assert = require('assert'); -import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { ITerminalConfiguration, ITerminalProfiles, ProfileSource } from 'vs/workbench/contrib/terminal/common/terminal'; -import { detectAvailableProfiles, IStatProvider } from 'vs/workbench/contrib/terminal/node/terminalProfiles'; +import { deepStrictEqual, fail, ok, strictEqual } from 'assert'; +import { isWindows } from 'vs/base/common/platform'; +import { ITerminalConfiguration, ITerminalProfile, ITerminalProfiles, ProfileSource } from 'vs/workbench/contrib/terminal/common/terminal'; +import { detectAvailableProfiles, IFsProvider } from 'vs/workbench/contrib/terminal/node/terminalProfiles'; + +/** + * Assets that two profiles objects are equal, this will treat explicit undefined and unset + * properties the same. Order of the profiles is ignored. + */ +function profilesEqual(actualProfiles: ITerminalProfile[], expectedProfiles: ITerminalProfile[]) { + strictEqual(actualProfiles.length, expectedProfiles.length); + for (const expected of expectedProfiles) { + const actual = actualProfiles.find(e => e.profileName === expected.profileName); + ok(actual, `Expected profile ${expected.profileName} not found`); + strictEqual(actual.profileName, expected.profileName); + strictEqual(actual.path, expected.path); + deepStrictEqual(actual.args, expected.args); + strictEqual(actual.isAutoDetected, expected.isAutoDetected); + strictEqual(actual.isWorkspaceProfile, expected.isWorkspaceProfile); + strictEqual(actual.overrideName, expected.overrideName); + } +} suite('Workbench - TerminalProfiles', () => { suite('detectAvailableProfiles', () => { if (isWindows) { - suite.skip('detectAvailableWindowsProfiles', async () => { - test.skip('should detect Git Bash and provide login args', async () => { - const _paths = [`C:\\Program Files\\Git\\bin\\bash.exe`]; - const config: ITestTerminalConfig = { - profiles: { - windows: { - 'Git Bash': { source: ProfileSource.GitBash } - }, - linux: {}, - osx: {} + test('should detect Git Bash and provide login args', async () => { + const fsProvider = createFsProvider([ + 'C:\\Program Files\\Git\\bin\\bash.exe' + ]); + const config: ITestTerminalConfig = { + profiles: { + windows: { + 'Git Bash': { source: ProfileSource.GitBash } }, - useWslProfiles: false - }; - const profiles = await detectAvailableProfiles(true, undefined, config as ITerminalConfiguration, undefined, undefined, createStatProvider(_paths)); - const expected = [{ profileName: 'Git Bash', path: _paths[0], args: ['--login'], isAutoDetected: undefined, overrideName: undefined }]; - assert.deepStrictEqual(profiles, expected); - }); - test.skip('should allow source to have args', async () => { - const config: ITestTerminalConfig = { - profiles: { - windows: { - 'PowerShell NoProfile': { source: ProfileSource.Pwsh, args: ['-NoProfile'], overrideName: true } - }, - linux: {}, - osx: {}, + linux: {}, + osx: {} + }, + useWslProfiles: false + }; + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, config as ITerminalConfiguration, undefined, undefined); + const expected = [ + { profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: ['--login'] } + ]; + profilesEqual(profiles, expected); + }); + test('should allow source to have args', async () => { + const fsProvider = createFsProvider([ + 'C:\\Program Files\\PowerShell\\7\\pwsh.exe' + ]); + const config: ITestTerminalConfig = { + profiles: { + windows: { + 'PowerShell NoProfile': { source: ProfileSource.Pwsh, args: ['-NoProfile'], overrideName: true } }, - useWslProfiles: false - }; - const profiles = await detectAvailableProfiles(true, undefined, config as ITerminalConfiguration, undefined, undefined, undefined); - const expected = [{ profileName: 'PowerShell NoProfile', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, isAutoDetected: undefined, args: ['-NoProfile'] }]; - assert.deepStrictEqual(expected, profiles); - }); - test.skip('configured args should override default source ones', async () => { - const _paths = [`C:\\Program Files\\Git\\bin\\bash.exe`]; - const config: ITestTerminalConfig = { - profiles: { - windows: { - 'Git Bash': { source: ProfileSource.GitBash, args: [] } - }, - linux: {}, - osx: {} + linux: {}, + osx: {}, + }, + useWslProfiles: false + }; + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, config as ITerminalConfiguration, undefined, undefined); + const expected = [ + { profileName: 'PowerShell NoProfile', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, args: ['-NoProfile'] } + ]; + profilesEqual(profiles, expected); + }); + test('configured args should override default source ones', async () => { + const fsProvider = createFsProvider([ + 'C:\\Program Files\\Git\\bin\\bash.exe' + ]); + const config: ITestTerminalConfig = { + profiles: { + windows: { + 'Git Bash': { source: ProfileSource.GitBash, args: [] } }, - useWslProfiles: false - }; - const profiles = await detectAvailableProfiles(true, undefined, config as ITerminalConfiguration, undefined, undefined, createStatProvider(_paths)); - const expected = [{ profileName: 'Git Bash', path: _paths[0], args: [], isAutoDetected: undefined, overrideName: undefined }]; - assert.deepStrictEqual(profiles, expected); - }); + linux: {}, + osx: {} + }, + useWslProfiles: false + }; + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, config as ITerminalConfiguration, undefined, undefined); + const expected = [{ profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: [], isAutoDetected: undefined, overrideName: undefined }]; + profilesEqual(profiles, expected); }); - } else if (isMacintosh) { - suite.skip('detectAvailableOsxProfiles', async () => { - test('should detect bash, zsh, tmux, fish', async () => { - const _paths = ['bash', 'zsh', 'tmux', 'fish']; - const config: ITestTerminalConfig = { - profiles: { - windows: {}, - osx: { - 'bash': { - path: 'bash' - }, - 'zsh': { - path: 'zsh' - }, - 'fish': { - path: 'fish' - }, - 'tmux': { - path: 'tmux' - } - }, - linux: {} + suite('pwsh source detection/fallback', async () => { + const pwshSourceConfig = ({ + profiles: { + windows: { + 'PowerShell': { source: ProfileSource.Pwsh } }, - useWslProfiles: false - }; - const profiles = await detectAvailableProfiles(true, undefined, config as ITerminalConfiguration, undefined, undefined, createStatProvider(_paths)); - const expected = [{ profileName: 'bash', path: _paths[0] }, { profileName: 'bash', path: _paths[0] }, { profileName: 'zsh', path: _paths[1] }, { profileName: 'tmux', path: _paths[2] }, { profileName: 'fish', path: _paths[3] }]; - assert.deepStrictEqual(profiles, expected); + linux: {}, + osx: {}, + }, + useWslProfiles: false + } as ITestTerminalConfig) as ITerminalConfiguration; + + test('should prefer pwsh 7 to Windows PowerShell', async () => { + const fsProvider = createFsProvider([ + 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', + 'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', + 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + ]); + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, pwshSourceConfig, undefined, undefined); + const expected = [ + { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe' } + ]; + profilesEqual(profiles, expected); }); - } - ); - } else if (isLinux) { - suite.skip('detectAvailableLinuxProfiles', async () => { - test('should detect bash, zsh, tmux, fish', async () => { - const _paths = ['bash', 'zsh', 'tmux', 'fish']; - const config: ITestTerminalConfig = { - profiles: { - windows: {}, - linux: { - 'bash': { - path: 'bash' - }, - 'zsh': { - path: 'zsh' - }, - 'fish': { - path: 'fish' - }, - 'tmux': { - path: 'tmux' - } - }, - osx: {} - }, - useWslProfiles: false - }; - const profiles = await detectAvailableProfiles(true, undefined, config as ITerminalConfiguration, undefined, undefined, createStatProvider(_paths)); - const expected = [{ profileName: 'bash', path: _paths[0] }, { profileName: 'bash', path: _paths[0] }, { profileName: 'zsh', path: _paths[1] }, { profileName: 'tmux', path: _paths[2] }, { profileName: 'fish', path: _paths[3] }]; - assert.deepStrictEqual(profiles, expected); + test('should prefer pwsh 7 to pwsh 6', async () => { + const fsProvider = createFsProvider([ + 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', + 'C:\\Program Files\\PowerShell\\6\\pwsh.exe', + 'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', + 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + ]); + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, pwshSourceConfig, undefined, undefined); + const expected = [ + { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe' } + ]; + profilesEqual(profiles, expected); }); - } - ); + test('should fallback to Windows PowerShell', async () => { + const fsProvider = createFsProvider([ + 'C:\\Windows\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' + ]); + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, pwshSourceConfig, undefined, undefined); + strictEqual(profiles.length, 1); + strictEqual(profiles[0].profileName, 'PowerShell'); + }); + }); + } else { + const absoluteConfig = ({ + profiles: { + windows: {}, + osx: { + 'fakeshell1': { path: '/bin/fakeshell1' }, + 'fakeshell2': { path: '/bin/fakeshell2' }, + 'fakeshell3': { path: '/bin/fakeshell3' } + }, + linux: { + 'fakeshell1': { path: '/bin/fakeshell1' }, + 'fakeshell2': { path: '/bin/fakeshell2' }, + 'fakeshell3': { path: '/bin/fakeshell3' } + } + }, + useWslProfiles: false + } as ITestTerminalConfig) as ITerminalConfiguration; + const onPathConfig = ({ + profiles: { + windows: {}, + osx: { + 'fakeshell1': { path: 'fakeshell1' }, + 'fakeshell2': { path: 'fakeshell2' }, + 'fakeshell3': { path: 'fakeshell3' } + }, + linux: { + 'fakeshell1': { path: 'fakeshell1' }, + 'fakeshell2': { path: 'fakeshell2' }, + 'fakeshell3': { path: 'fakeshell3' } + } + }, + useWslProfiles: false + } as ITestTerminalConfig) as ITerminalConfiguration; + + test('should detect shells via absolute paths', async () => { + const fsProvider = createFsProvider([ + '/bin/fakeshell1', + '/bin/fakeshell3' + ]); + const profiles = await detectAvailableProfiles(true, fsProvider, undefined, absoluteConfig, undefined, undefined); + const expected: ITerminalProfile[] = [ + { profileName: 'fakeshell1', path: '/bin/fakeshell1' }, + { profileName: 'fakeshell3', path: '/bin/fakeshell3' } + ]; + profilesEqual(profiles, expected); + }); + test('should auto detect shells via /etc/shells', async () => { + const fsProvider = createFsProvider([ + '/bin/fakeshell1', + '/bin/fakeshell3' + ], '/bin/fakeshell1\n/bin/fakeshell3'); + const profiles = await detectAvailableProfiles(false, fsProvider, undefined, onPathConfig, undefined, undefined); + const expected: ITerminalProfile[] = [ + { profileName: 'fakeshell1', path: 'fakeshell1' }, + { profileName: 'fakeshell3', path: 'fakeshell3' } + ]; + profilesEqual(profiles, expected); + }); + test('should validate auto detected shells from /etc/shells exist', async () => { + // fakeshell3 exists in /etc/shells but not on FS + const fsProvider = createFsProvider([ + '/bin/fakeshell1' + ], '/bin/fakeshell1\n/bin/fakeshell3'); + const profiles = await detectAvailableProfiles(false, fsProvider, undefined, onPathConfig, undefined, undefined); + const expected: ITerminalProfile[] = [ + { profileName: 'fakeshell1', path: 'fakeshell1' } + ]; + profilesEqual(profiles, expected); + }); } }); - function createStatProvider(expectedPaths: string[]): IStatProvider { + function createFsProvider(expectedPaths: string[], etcShellsContent: string = ''): IFsProvider { const provider = { async existsFile(path: string): Promise { return expectedPaths.includes(path); + }, + async readFile(path: string, options: { encoding: BufferEncoding, flag?: string | number } | BufferEncoding): Promise { + if (path !== '/etc/shells') { + fail('Unexected path'); + } + console.log('readfile', etcShellsContent); + return etcShellsContent; } }; return provider;