From 7ec908b2f1df62d833fd46576bc8952394754b84 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Thu, 28 Feb 2019 23:53:25 -0700 Subject: [PATCH 1/6] Migrate Pester version detection into an InovkePester stub script --- InvokePesterStub.ps1 | 62 +++++++++++++++++++++++++++++++++++++ src/features/PesterTests.ts | 42 +++++++++++++------------ 2 files changed, 85 insertions(+), 19 deletions(-) create mode 100755 InvokePesterStub.ps1 diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 new file mode 100755 index 0000000000..c8918ecc0b --- /dev/null +++ b/InvokePesterStub.ps1 @@ -0,0 +1,62 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Stub around Invoke-Pester command used by VSCode PowerShell extension. +.DESCRIPTION + Stub around Invoke-Pester command used by VSCode PowerShell extension. + The stub checks the version of Pester and if >= 4.6.0, invokes Pester + using the LineNumber parameter (if specified). Otherwise, it invokes + using the TestName parameter (if specified). +.EXAMPLE + PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 -LineNumber 14 + Invokes a specific test by line number in the specified file. +.EXAMPLE + PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 -TestName 'Foo Tests' + Invokes a specific test by test name in the specified file. +.EXAMPLE + PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 + Invokes all tests in the specified file. +.INPUTS + None +.OUTPUTS + None +#> +param( + # Specifies the path to the test script. + [Parameter(Position=0, Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $ScriptPath, + + # Specifies the name of the test taken from the Describe block's name. + [Parameter()] + [string] + $TestName, + + # Specifies the starting line number of the DescribeBlock. This feature requires + # Pester 4.6.0 or higher. + [Parameter()] + [ValidatePattern('\d*')] + [string] + $LineNumber +) + +try { + $pesterVersion = (Microsoft.PowerShell.Core\Get-Command Invoke-Pester -ErrorAction Stop).Version + if (($pesterVersion -ge '4.6.0') -and ($LineNumber -match '\d+')) { + $pesterOption = New-PesterOption -ScriptBlockFilter @( + @{ IncludeVSCodeMarker=$true; Line=$LineNumber; Path=$ScriptPath } ) + Invoke-Pester -Script $ScriptPath -PesterOption $pesterOption + } + elseif ($TestName) { + Invoke-Pester -Script $ScriptPath -PesterOption @{ IncludeVSCodeMarker=$true } -TestName $TestName + } + else { + Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} + } +} +catch [System.Management.Automation.CommandNotFoundException] { + Write-Warning "You must install Pester to run or debug Pester Tests. You can install Pester by executing:" + Write-Warning "Install-Module Pester -Scope CurrentUser -Force" +} diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index ca950523cf..210bba0b90 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -18,8 +18,11 @@ export class PesterTestsFeature implements IFeature { private command: vscode.Disposable; private languageClient: LanguageClient; + private invokePesterStubScriptPath: string; constructor(private sessionManager: SessionManager) { + this.invokePesterStubScriptPath = path.resolve(__dirname, "../../../InvokePesterStub.ps1"); + // File context-menu command - Run Pester Tests this.command = vscode.commands.registerCommand( "PowerShell.RunPesterTestsFromFile", @@ -35,8 +38,8 @@ export class PesterTestsFeature implements IFeature { // This command is provided for usage by PowerShellEditorServices (PSES) only this.command = vscode.commands.registerCommand( "PowerShell.RunPesterTests", - (uriString, runInDebugger, describeBlockName?) => { - this.launchTests(uriString, runInDebugger, describeBlockName); + (uriString, runInDebugger, describeBlockName?, describeBlockLineNumber?) => { + this.launchTests(uriString, runInDebugger, describeBlockName, describeBlockLineNumber); }); } @@ -54,7 +57,7 @@ export class PesterTestsFeature implements IFeature { this.launch(launchConfig); } - private async launchTests(uriString: string, runInDebugger: boolean, describeBlockName?: string) { + private async launchTests(uriString: string, runInDebugger: boolean, describeBlockName?: string, lineNum?: number) { // PSES passes null for the describeBlockName to signal that it can't evaluate the TestName. if (!describeBlockName) { const answer = await vscode.window.showErrorMessage( @@ -68,21 +71,11 @@ export class PesterTestsFeature implements IFeature { } const launchType = runInDebugger ? LaunchType.Debug : LaunchType.Run; - const launchConfig = this.createLaunchConfig(uriString, launchType); - - if (describeBlockName) { - launchConfig.args.push("-TestName"); - // Escape single quotes inside double quotes by doubling them up - if (describeBlockName.includes("'")) { - describeBlockName = describeBlockName.replace(/'/g, "''"); - } - launchConfig.args.push(`'${describeBlockName}'`); - } - + const launchConfig = this.createLaunchConfig(uriString, launchType, describeBlockName, lineNum); this.launch(launchConfig); } - private createLaunchConfig(uriString: string, launchType: LaunchType) { + private createLaunchConfig(uriString: string, launchType: LaunchType, testName?: string, lineNum?: number) { const uri = vscode.Uri.parse(uriString); const currentDocument = vscode.window.activeTextEditor.document; const settings = Settings.load(); @@ -95,12 +88,10 @@ export class PesterTestsFeature implements IFeature { request: "launch", type: "PowerShell", name: "PowerShell Launch Pester Tests", - script: "Invoke-Pester", + script: this.invokePesterStubScriptPath, args: [ - "-Script", + "-ScriptPath", `'${scriptPath}'`, - "-PesterOption", - "@{IncludeVSCodeMarker=$true}", ], internalConsoleOptions: "neverOpen", noDebug: (launchType === LaunchType.Run), @@ -111,6 +102,19 @@ export class PesterTestsFeature implements IFeature { : path.dirname(currentDocument.fileName), }; + if (lineNum) { + launchConfig.args.push("-LineNumber", `${lineNum}`); + } + + if (testName) { + // Escape single quotes inside double quotes by doubling them up + if (testName.includes("'")) { + testName = testName.replace(/'/g, "''"); + } + + launchConfig.args.push("-TestName", `'${testName}'`); + } + return launchConfig; } From c916b5404afd46206459097d0690de41c85a9ed8 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Fri, 1 Mar 2019 00:42:47 -0700 Subject: [PATCH 2/6] Move TestName warning into script --- InvokePesterStub.ps1 | 17 ++++++++++++++++- src/features/PesterTests.ts | 20 +++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 index c8918ecc0b..b5e621f4f2 100755 --- a/InvokePesterStub.ps1 +++ b/InvokePesterStub.ps1 @@ -39,10 +39,20 @@ param( [Parameter()] [ValidatePattern('\d*')] [string] - $LineNumber + $LineNumber, + + # If specified, executes all the tests in the specified test script. + [Parameter()] + [switch] + $All ) try { + if ($All) { + Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} + return + } + $pesterVersion = (Microsoft.PowerShell.Core\Get-Command Invoke-Pester -ErrorAction Stop).Version if (($pesterVersion -ge '4.6.0') -and ($LineNumber -match '\d+')) { $pesterOption = New-PesterOption -ScriptBlockFilter @( @@ -53,6 +63,11 @@ try { Invoke-Pester -Script $ScriptPath -PesterOption @{ IncludeVSCodeMarker=$true } -TestName $TestName } else { + # We get here when PSES couldn't parse the TestName + Write-Warning "The Describe block's TestName cannot be evaluated. ALL TESTS will be executed." + Write-Warning "Either try again with Pester 4.6.0 or higher, or remove any variables or" + Write-Warning "sub-expressions in the Describe block's TestName." + Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} } } diff --git a/src/features/PesterTests.ts b/src/features/PesterTests.ts index 210bba0b90..ea2c5fea9f 100644 --- a/src/features/PesterTests.ts +++ b/src/features/PesterTests.ts @@ -54,24 +54,18 @@ export class PesterTestsFeature implements IFeature { private launchAllTestsInActiveEditor(launchType: LaunchType) { const uriString = vscode.window.activeTextEditor.document.uri.toString(); const launchConfig = this.createLaunchConfig(uriString, launchType); + launchConfig.args.push("-All"); this.launch(launchConfig); } - private async launchTests(uriString: string, runInDebugger: boolean, describeBlockName?: string, lineNum?: number) { - // PSES passes null for the describeBlockName to signal that it can't evaluate the TestName. - if (!describeBlockName) { - const answer = await vscode.window.showErrorMessage( - "This Describe block's TestName parameter cannot be evaluated. " + - `Would you like to ${runInDebugger ? "debug" : "run"} all the tests in this file?`, - "Yes", "No"); - - if (answer !== "Yes") { - return; - } - } + private async launchTests( + uriString: string, + runInDebugger: boolean, + describeBlockName?: string, + describeBlockLineNumber?: number) { const launchType = runInDebugger ? LaunchType.Debug : LaunchType.Run; - const launchConfig = this.createLaunchConfig(uriString, launchType, describeBlockName, lineNum); + const launchConfig = this.createLaunchConfig(uriString, launchType, describeBlockName, describeBlockLineNumber); this.launch(launchConfig); } From a257cfa9e4b4c8e5791ef2e56965d78b439058f8 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Sat, 2 Mar 2019 12:29:52 -0700 Subject: [PATCH 3/6] Improve stub script, move module loaded check to beginning --- InvokePesterStub.ps1 | 58 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 index b5e621f4f2..0a5e6b3fdc 100755 --- a/InvokePesterStub.ps1 +++ b/InvokePesterStub.ps1 @@ -4,10 +4,13 @@ .SYNOPSIS Stub around Invoke-Pester command used by VSCode PowerShell extension. .DESCRIPTION - Stub around Invoke-Pester command used by VSCode PowerShell extension. The stub checks the version of Pester and if >= 4.6.0, invokes Pester using the LineNumber parameter (if specified). Otherwise, it invokes - using the TestName parameter (if specified). + using the TestName parameter (if specified). If the All parameter + is specified, then all the tests are invoked in the specifed file. + Finally, if none of these three parameters are specified, all tests + are invoked and a warning is issued indicating what the user can do + to allow invocation of individual Describe blocks. .EXAMPLE PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 -LineNumber 14 Invokes a specific test by line number in the specified file. @@ -15,7 +18,7 @@ PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 -TestName 'Foo Tests' Invokes a specific test by test name in the specified file. .EXAMPLE - PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 + PS C:\> .\InvokePesterStub.ps1 ~\project\test\foo.tests.ps1 -All Invokes all tests in the specified file. .INPUTS None @@ -47,31 +50,34 @@ param( $All ) -try { - if ($All) { - Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} +$pesterModule = Microsoft.PowerShell.Core\Get-Module Pester +if (!$pesterModule) { + Write-Host "Importing Pester module..." + $pesterModule = Microsoft.PowerShell.Core\Import-Module Pester -ErrorAction Ignore -PassThru + if (!$pesterModule) { + # If we still don't have an imported Pester module, that is (most likely) because Pester is not installed. + Write-Warning "Failed to import the Pester module. You must install Pester to run or debug Pester tests." + Write-Warning "You can install Pester by executing: Install-Module Pester -Scope CurrentUser -Force" return } +} - $pesterVersion = (Microsoft.PowerShell.Core\Get-Command Invoke-Pester -ErrorAction Stop).Version - if (($pesterVersion -ge '4.6.0') -and ($LineNumber -match '\d+')) { - $pesterOption = New-PesterOption -ScriptBlockFilter @( - @{ IncludeVSCodeMarker=$true; Line=$LineNumber; Path=$ScriptPath } ) - Invoke-Pester -Script $ScriptPath -PesterOption $pesterOption - } - elseif ($TestName) { - Invoke-Pester -Script $ScriptPath -PesterOption @{ IncludeVSCodeMarker=$true } -TestName $TestName - } - else { - # We get here when PSES couldn't parse the TestName - Write-Warning "The Describe block's TestName cannot be evaluated. ALL TESTS will be executed." - Write-Warning "Either try again with Pester 4.6.0 or higher, or remove any variables or" - Write-Warning "sub-expressions in the Describe block's TestName." - - Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} - } +if ($All) { + Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} +} +elseif (($LineNumber -match '\d+') -and ($pesterModule.Version -ge '4.6.0')) { + Pester\Invoke-Pester -Script $ScriptPath -PesterOption (New-PesterOption -ScriptBlockFilter @{ + IncludeVSCodeMarker=$true; Line=$LineNumber; Path=$ScriptPath}) } -catch [System.Management.Automation.CommandNotFoundException] { - Write-Warning "You must install Pester to run or debug Pester Tests. You can install Pester by executing:" - Write-Warning "Install-Module Pester -Scope CurrentUser -Force" +elseif ($TestName) { + Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} -TestName $TestName +} +else { + # We get here when the TestName expression is of type ExpandableStringExpressionAst. + # PSES will not attempt to "evaluate" the expression so it returns null for the TestName. + Write-Warning "The Describe block's TestName cannot be evaluated. EXECUTING ALL TESTS instead." + Write-Warning "To avoid this, either install Pester 4.6.0 or higher, or remove any variables or" + Write-Warning "sub-expressions in the Describe block's TestName." + + Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} } From ca000755b5e5379345d61e01ed0ab3538f7eab4e Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Sat, 2 Mar 2019 19:12:06 -0700 Subject: [PATCH 4/6] Address Codacy issue --- InvokePesterStub.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 index 0a5e6b3fdc..1fa5f3522e 100755 --- a/InvokePesterStub.ps1 +++ b/InvokePesterStub.ps1 @@ -52,7 +52,7 @@ param( $pesterModule = Microsoft.PowerShell.Core\Get-Module Pester if (!$pesterModule) { - Write-Host "Importing Pester module..." + Write-Output "Importing Pester module..." $pesterModule = Microsoft.PowerShell.Core\Import-Module Pester -ErrorAction Ignore -PassThru if (!$pesterModule) { # If we still don't have an imported Pester module, that is (most likely) because Pester is not installed. From 127062f5eec247acd81a2be8d4baf8bc502cfaa2 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Mon, 4 Mar 2019 22:28:40 -0700 Subject: [PATCH 5/6] Address PR feedback --- InvokePesterStub.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 index 1fa5f3522e..689cf3ce6e 100755 --- a/InvokePesterStub.ps1 +++ b/InvokePesterStub.ps1 @@ -76,8 +76,7 @@ else { # We get here when the TestName expression is of type ExpandableStringExpressionAst. # PSES will not attempt to "evaluate" the expression so it returns null for the TestName. Write-Warning "The Describe block's TestName cannot be evaluated. EXECUTING ALL TESTS instead." - Write-Warning "To avoid this, either install Pester 4.6.0 or higher, or remove any variables or" - Write-Warning "sub-expressions in the Describe block's TestName." + Write-Warning "To avoid this, install Pester >= 4.6.0 or remove any expressions in the TestName." Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} } From dda63b9522721f70c17cf2701e3fcb2129a2ff26 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Tue, 5 Mar 2019 10:26:20 -0700 Subject: [PATCH 6/6] Move TestName check before LineNumber in case of multiple blocks/line --- InvokePesterStub.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvokePesterStub.ps1 b/InvokePesterStub.ps1 index 689cf3ce6e..8b6f204cce 100755 --- a/InvokePesterStub.ps1 +++ b/InvokePesterStub.ps1 @@ -65,13 +65,13 @@ if (!$pesterModule) { if ($All) { Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} } +elseif ($TestName) { + Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} -TestName $TestName +} elseif (($LineNumber -match '\d+') -and ($pesterModule.Version -ge '4.6.0')) { Pester\Invoke-Pester -Script $ScriptPath -PesterOption (New-PesterOption -ScriptBlockFilter @{ IncludeVSCodeMarker=$true; Line=$LineNumber; Path=$ScriptPath}) } -elseif ($TestName) { - Pester\Invoke-Pester -Script $ScriptPath -PesterOption @{IncludeVSCodeMarker=$true} -TestName $TestName -} else { # We get here when the TestName expression is of type ExpandableStringExpressionAst. # PSES will not attempt to "evaluate" the expression so it returns null for the TestName.