diff --git a/module/PowerShellEditorServices/InvokePesterStub.ps1 b/module/PowerShellEditorServices/InvokePesterStub.ps1 new file mode 100755 index 000000000..6fc1e799f --- /dev/null +++ b/module/PowerShellEditorServices/InvokePesterStub.ps1 @@ -0,0 +1,192 @@ +#!/usr/bin/env pwsh + +<# +.SYNOPSIS + Stub around Invoke-Pester command used by VSCode PowerShell extension. +.DESCRIPTION + 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). 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. +.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 -All + 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, + + # If specified, executes all the tests in the specified test script. + [Parameter()] + [switch] + $All, + + [Parameter()] + [switch] $MinimumVersion5, + + [Parameter(Mandatory)] + [string] $Output, + + [Parameter()] + [string] $OutputPath +) + +$pesterModule = Microsoft.PowerShell.Core\Get-Module Pester +# add one line, so the subsequent output is not shifted to the side +Write-Output '' + +if (!$pesterModule) { + Write-Output "Importing Pester module..." + if ($MinimumVersion5) { + $pesterModule = Microsoft.PowerShell.Core\Import-Module Pester -ErrorAction Ignore -PassThru -MinimumVersion 5.0.0 + } + + if (!$pesterModule) { + $pesterModule = Microsoft.PowerShell.Core\Import-Module Pester -ErrorAction Ignore -PassThru + } + + if (!$pesterModule) { + Write-Warning "Failed to import Pester. You must install Pester module to run or debug Pester tests." + Write-Warning "$(if ($MinimumVersion5) {"Recommended version to install is Pester 5.0.0 or newer. "})You can install Pester by executing: Install-Module Pester$(if ($MinimumVersion5) {" -MinimumVersion 5.0.0" }) -Scope CurrentUser -Force" + return + } +} + +$pester4Output = switch ($Output) { + "None" { "None" } + "Minimal" { "Fails" } + default { "All" } +} + +if ($MinimumVersion5 -and $pesterModule.Version -lt "5.0.0") { + Write-Warning "Pester 5.0.0 or newer is required because setting PowerShell > Pester: Use Legacy Code Lens is disabled, but Pester $($pesterModule.Version) is loaded. Some of the code lens features might not work as expected." +} + + +function Get-InvokePesterParams { + $invokePesterParams = @{ + Script = $ScriptPath + } + + if ($pesterModule.Version -ge '3.4.0') { + # -PesterOption was introduced before 3.4.0, and VSCodeMarker in 4.0.3-rc, + # but because no-one checks the integrity of this hashtable we can call + # all of the versions down to 3.4.0 like this + $invokePesterParams.Add("PesterOption", @{ IncludeVSCodeMarker = $true }) + } + + if ($pesterModule.Version -ge '3.4.5') { + # -Show was introduced in 3.4.5 + $invokePesterParams.Add("Show", $pester4Output) + } + + return $invokePesterParams +} + +if ($All) { + if ($pesterModule.Version -ge '5.0.0') { + $configuration = @{ + Run = @{ + Path = $ScriptPath + } + } + # only override this if user asks us to do it, to allow Pester to pick up + # $PesterPreference from caller context and merge it with the configuration + # we provide below, this way user can specify his output (and other) settings + # using the standard [PesterConfiguration] object, and we can avoid providing + # settings for everything + if ("FromPreference" -ne $Output) { + $configuration.Add('Output', @{ Verbosity = $Output }) + } + + if ($OutputPath) { + $configuration.Add('TestResult', @{ + Enabled = $true + OutputPath = $OutputPath + }) + } + Pester\Invoke-Pester -Configuration $configuration | Out-Null + } + else { + $invokePesterParams = Get-InvokePesterParams + Pester\Invoke-Pester @invokePesterParams + } +} +elseif (($LineNumber -match '\d+') -and ($pesterModule.Version -ge '4.6.0')) { + if ($pesterModule.Version -ge '5.0.0') { + $configuration = @{ + Run = @{ + Path = $ScriptPath + } + Filter = @{ + Line = "${ScriptPath}:$LineNumber" + } + } + if ("FromPreference" -ne $Output) { + $configuration.Add('Output', @{ Verbosity = $Output }) + } + + if ($OutputPath) { + $configuration.Add('TestResult', @{ + Enabled = $true + OutputPath = $OutputPath + }) + } + + Pester\Invoke-Pester -Configuration $configuration | Out-Null + } + else { + Pester\Invoke-Pester -Script $ScriptPath -PesterOption (New-PesterOption -ScriptBlockFilter @{ + IncludeVSCodeMarker=$true; Line=$LineNumber; Path=$ScriptPath}) -Show $pester4Output + } +} +elseif ($TestName) { + if ($pesterModule.Version -ge '5.0.0') { + throw "Running tests by test name is unsafe. This should not trigger for Pester 5." + } + else { + $invokePesterParams = Get-InvokePesterParams + Pester\Invoke-Pester @invokePesterParams + } +} +else { + if ($pesterModule.Version -ge '5.0.0') { + throw "Running tests by expandable string is unsafe. This should not trigger for Pester 5." + } + + # 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, install Pester >= 4.6.0 or remove any expressions in the TestName." + + $invokePesterParams = Get-InvokePesterParams + Pester\Invoke-Pester @invokePesterParams +}