diff --git a/package.json b/package.json index 38069022..995847d5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,10 @@ "pester.pesterModulePath": { "type": "string", "markdownDescription": "This allows to specify a custom path to the Pester module. A relative path under the current project is allowed. If this is not set then Pester module is expected to be found in one of the paths specified by `$env:PSModulePath`." + }, + "pester.workingDirectory": { + "type": "string", + "markdownDescription": "Specifies the working directory of the dedicated Pester Instance. While $PSScriptRoot is recommended when using relative paths in Pester Tests, the runner will also default to the path of the current first workspace folder. Specify this if you want a different working directory." } } }, diff --git a/src/pesterTestController.ts b/src/pesterTestController.ts index f70b8cd3..e1ab10d0 100644 --- a/src/pesterTestController.ts +++ b/src/pesterTestController.ts @@ -1,5 +1,4 @@ -import { join, isAbsolute } from 'path' -import { existsSync } from 'fs' +import { join, isAbsolute, dirname } from 'path' import { debug as vscodeDebug, DebugSession, @@ -494,15 +493,28 @@ export class PesterTestController implements Disposable { ? (await this.powerShellExtensionClient!.GetVersionDetails()).exePath : undefined + const cwd = this.getPesterWorkingDirectory() + // Restart PS to use the requested version if it is different from the current one - if (this.ps === undefined || this.ps.exePath !== exePath) { - log.info(`Starting PowerShell testing instance: ${exePath}`) + if ( + this.ps === undefined || + this.ps.exePath !== exePath || + this.ps.cwd !== cwd + ) { if (this.ps !== undefined) { log.warn( `Detected PowerShell Session change from ${this.ps.exePath} to ${exePath}. Restarting Pester Runner.` ) } - this.ps = new PowerShell(exePath) + const exePathDir = exePath + ? dirname(exePath) + : '*DEFAULT POWERSHELL PATH*' + log.debug( + `Starting PowerShell Pester testing instance ${exePath} with working directory ${ + cwd ? cwd : exePathDir + }` + ) + this.ps = new PowerShell(exePath, cwd) } // Objects from the run will return to the success stream, which we then send to the return handler @@ -543,6 +555,25 @@ export class PesterTestController implements Disposable { await this.ps.run(script, psOutput, undefined, true, useNewProcess) } } + // Fetches the current working directory that Pester should use. + getPesterWorkingDirectory() { + const customCwd = workspace + .getConfiguration('pester') + .get('workingDirectory') + if (customCwd) { + return customCwd + } + + // TODO: Multi-root workspace support, for now this just looks for the first defined workspace + if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) { + log.warn( + 'Multi-root workspace detected. Relative paths in Pester files will only work for the first workspace.' + ) + } + return workspace.workspaceFolders + ? workspace.workspaceFolders[0].uri.fsPath + : undefined + } /** Fetches the current pester module path if a custom path was defined, otherwise returns undefined */ getPesterCustomModulePath() { @@ -571,16 +602,6 @@ export class PesterTestController implements Disposable { return resolvedPath } - // Fetches the current working directory that Pester should use. - getPesterWorkingDirectory() { - const customCwd = workspace - .getConfiguration('pester') - .get('workingDirectory') - if (customCwd) { - return customCwd - } - } - /** Returns a list of relative patterns based on user configuration for matching Pester files in the workspace */ getPesterRelativePatterns() { const pesterFilePatterns = new Array() diff --git a/src/powershell.ts b/src/powershell.ts index 0c9ff0a0..1256f9e1 100644 --- a/src/powershell.ts +++ b/src/powershell.ts @@ -148,12 +148,14 @@ export function createSplitPSOutputStream(streams: IPSOutput) { /** Represents an instance of a PowerShell process. By default this will use pwsh if installed, and will fall back to PowerShell on Windows, * unless the exepath parameter is specified. Use the exePath parameter to specify specific powershell executables * such as pwsh-preview or a pwsh executable not located in the PATH + * @param exePath The path to the powershell executable to use. If not specified, the default will be used. + * @param cwd The current working directory of the process. All paths will be relative to this. Defaults to the folder where pwsh.exe resides. */ export class PowerShell { psProcess: ChildProcessWithoutNullStreams | undefined - private currentInvocation: Promise | undefined + private currentInvocation: Promise | undefined private resolvedExePath: string | undefined - constructor(public exePath?: string) {} + constructor(public exePath?: string, public cwd?: string) {} /** lazy-start a pwsh instance. If pwsh is not found but powershell is present, it will silently use that instead. */ private async initialize() { @@ -169,13 +171,11 @@ export class PowerShell { 'pwsh not found in your path and you are not on Windows so PowerShell 5.1 is not an option. Did you install PowerShell first?' ) } - this.psProcess = spawn(this.resolvedExePath, [ - '-NoProfile', - '-NonInteractive', - '-NoExit', - '-Command', - '-' - ]) + this.psProcess = spawn( + this.resolvedExePath, + ['-NoProfile', '-NonInteractive', '-NoExit', '-Command', '-'], + { cwd: this.cwd } + ) // Warn if we have more than one listener set on a process this.psProcess.stdout.setMaxListeners(1) this.psProcess.stderr.setMaxListeners(1)