Tests/ParamLevel.Verify.ps1
|
# ============================================================================== # ParamLevel.Verify.ps1 - Parameter-level completion verification script (run in a normal session) # Path: Tests\ # Purpose: Pester runspaces cannot verify the "parameter-level forwarding + engine direct # call" path (see the 5 skipped cases in the test file), so this script verifies the # same logic in a normal pwsh session (with a host) — it maps 1:1 to the Skipped cases # in LazyCompletions.Tests.ps1. # Run: pwsh -File .\Modules\LazyCompletions\Tests\ParamLevel.Verify.ps1 # ============================================================================== $moduleRoot = Split-Path $PSScriptRoot -Parent $passed = 0 $failed = 0 function Assert-Case([string]$Name, [scriptblock]$Body) { $err = $null try { & $Body } catch { $err = $_.Exception.Message } if ($err) { Write-Host " [FAIL] $Name`n $err" -ForegroundColor Red $script:failed++ } else { Write-Host " [PASS] $Name" -ForegroundColor Green $script:passed++ } } # Note: the parameter name cannot be $Input (PowerShell automatic variable; binding fails to an # empty value, measured); the first+second Tab must share the same $ps (the engine direct call # on the second Tab depends on the same completion session, measured) function Test-Tab([string]$Line, [int]$Column, $PS) { $r = [System.Management.Automation.CommandCompletion]::CompleteInput($Line, $Column, $null, $PS) return @($r.CompletionMatches | ForEach-Object { $_.CompletionText }) } function New-PS { return [powershell]::Create([System.Management.Automation.RunspaceMode]::CurrentRunspace) } # reset the module state per case (no reload: after completers are registered, Import-Module # -Force corrupts later completions, measured; leftover engine placeholders are harmless — # later registrations win) function Import-Case([string]$Dir) { $m = Get-Module LazyCompletions if (-not $m) { Import-Module (Join-Path $moduleRoot 'LazyCompletions.psd1') -Force $m = Get-Module LazyCompletions } & $m { $script:LazyCompletionsDirs = @($script:DefaultCompletionsDir) $script:LazyCompletionsDir = $script:DefaultCompletionsDir $script:LazyCompleterStates = @{} $script:CommandToScript = @{} $script:StatesByDir = @{} $script:CacheEnabled = $false $script:CacheUpdateMode = 'Sync' $script:CacheChangeMode = 'Full' $script:CacheLastError = $null $script:CacheLastUpdate = $null $script:FilterSystemFilesEnabled = $true $global:LC_FilterSystemFilesEnabled = $true $global:LC_TabWrapperDeferred = $false } Set-LazyCache -Disable | Out-Null Set-LazyCompletionsPath $Dir Import-LazyCompletion } Write-Host "=== Parameter-level completion verification (normal session) ===" -ForegroundColor Cyan # test functions must be defined at script scope: functions defined inside scriptblocks are # invisible to engine command discovery (measured: the engine treats local functions as unknown # commands and only calls the general completer) — unrelated to the Pester runspace function lc-multitest { param([string]$One, [string]$Two) } function lc-boundtest { param([string]$One, [string]$Mode) } function lc-abbrtest { param([string]$Mode) } function lc-hotparam { param([string]$Mode) } function lc-proxykey { param([string]$Old, [string]$New) } # ---- 1. multi-registration: general + parameter-level; second Tab calls the engine directly ---- Assert-Case 'multi-registration: second Tab calls parameter-level directly (one/two-result)' { $dir = Join-Path $env:TEMP 'lc-verify-multi' Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $dir | Out-Null @' Register-ArgumentCompleter -CommandName 'lc-multitest' -ParameterName 'One' -ScriptBlock { param($c,$p,$w,$a,$f) 'one-result' } Register-ArgumentCompleter -CommandName 'lc-multitest' -ParameterName 'Two' -ScriptBlock { param($c,$p,$w,$a,$f) 'two-result' } Register-ArgumentCompleter -CommandName 'lc-multitest' -ScriptBlock { param($w,$a,$c) 'gen-result' } '@ | Set-Content (Join-Path $dir 'multi1.ps1') -Encoding UTF8 Import-Case $dir $ps = New-PS try { $null = Test-Tab 'lc-multitest -One ' 18 $ps # first: trigger loading $m4 = Test-Tab 'lc-multitest -One ' 18 $ps $m5 = Test-Tab 'lc-multitest -Two ' 18 $ps } finally { $ps.Dispose() } if ($m4 -notcontains 'one-result') { throw "second -One expected one-result, got [$($m4 -join ',')]" } if ($m5 -notcontains 'two-result') { throw "second -Two expected two-result, got [$($m5 -join ',')]" } Remove-Item $dir -Recurse -Force } # ---- 2. fakeBoundParameters is the engine real value ---- Assert-Case 'fakeBoundParameters is the engine real binding (bound-result)' { $dir = Join-Path $env:TEMP 'lc-verify-bound' Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $dir | Out-Null @' Register-ArgumentCompleter -CommandName 'lc-boundtest' -ParameterName 'Mode' -ScriptBlock { param($c,$p,$w,$a,$f) if ($f -and $f.ContainsKey('One')) { 'bound-result' } else { 'nobound-result' } } '@ | Set-Content (Join-Path $dir 'prox1.ps1') -Encoding UTF8 Import-Case $dir Add-LazyCompletion (Join-Path $dir 'prox1.ps1') | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-boundtest -One x -Mode ' 26 $ps # first: trigger loading $m2 = Test-Tab 'lc-boundtest -One x -Mode ' 26 $ps } finally { $ps.Dispose() } if ($m2 -notcontains 'bound-result') { throw "expected bound-result (real fakeBoundParameters), got [$($m2 -join ',')]" } if ($m2 -contains 'nobound-result') { throw "nobound-result must not appear" } Remove-Item function:lc-boundtest -ErrorAction SilentlyContinue Remove-Item $dir -Recurse -Force } # ---- 3. parameter abbreviation resolved by the engine on the second Tab ---- Assert-Case 'parameter abbreviation -Mo -> Mode resolved by the engine (abbr-ok)' { $dir = Join-Path $env:TEMP 'lc-verify-abbr' Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $dir | Out-Null @' Register-ArgumentCompleter -CommandName 'lc-abbrtest' -ParameterName 'Mode' -ScriptBlock { param($c,$p,$w,$a,$f) 'abbr-ok' } '@ | Set-Content (Join-Path $dir 'abbr1.ps1') -Encoding UTF8 Import-Case $dir Add-LazyCompletion (Join-Path $dir 'abbr1.ps1') | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-abbrtest -Mo ' 16 $ps # first: trigger loading $m2 = Test-Tab 'lc-abbrtest -Mo ' 16 $ps } finally { $ps.Dispose() } if ($m2 -notcontains 'abbr-ok') { throw "expected abbr-ok (engine resolves -Mo to Mode), got [$($m2 -join ',')]" } Remove-Item function:lc-abbrtest -ErrorAction SilentlyContinue Remove-Item $dir -Recurse -Force } # ---- 4. -Force hot update: parameter-level placeholder overrides the old real ---- Assert-Case '-Force hot update (old-mode -> new-mode)' { $dir = Join-Path $env:TEMP 'lc-verify-hotparam' Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $dir | Out-Null $f = Join-Path $dir 'hot1.ps1' @' Register-ArgumentCompleter -CommandName 'lc-hotparam' -ParameterName 'Mode' -ScriptBlock { param($c,$p,$w,$a,$f) 'old-mode' } '@ | Set-Content $f -Encoding UTF8 Import-Case $dir Add-LazyCompletion $f | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-hotparam -Mode ' 18 $ps # first: trigger loading $m2 = Test-Tab 'lc-hotparam -Mode ' 18 $ps } finally { $ps.Dispose() } if ($m2 -notcontains 'old-mode') { throw "second expected old-mode, got [$($m2 -join ',')]" } @' Register-ArgumentCompleter -CommandName 'lc-hotparam' -ParameterName 'Mode' -ScriptBlock { param($c,$p,$w,$a,$f) 'new-mode' } '@ | Set-Content $f -Encoding UTF8 Add-LazyCompletion $f -Force | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-hotparam -Mode ' 18 $ps # trigger reload $m4 = Test-Tab 'lc-hotparam -Mode ' 18 $ps } finally { $ps.Dispose() } if ($m4 -notcontains 'new-mode') { throw "after -Force expected new-mode, got [$($m4 -join ',')]" } Remove-Item function:lc-hotparam -ErrorAction SilentlyContinue Remove-Item $dir -Recurse -Force } # ---- 5. -Force parameter rename: the old parameter no longer yields old results ---- Assert-Case '-Force parameter rename (Old -> New)' { $dir = Join-Path $env:TEMP 'lc-verify-proxykey' Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $dir | Out-Null $f = Join-Path $dir 'proxy.ps1' @' Register-ArgumentCompleter -CommandName 'lc-proxykey' -ParameterName 'Old' -ScriptBlock { 'old' } '@ | Set-Content $f -Encoding UTF8 Import-Case $dir Add-LazyCompletion $f | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-proxykey -Old ' 17 $ps # first: trigger loading $m2 = Test-Tab 'lc-proxykey -Old ' 17 $ps } finally { $ps.Dispose() } if ($m2 -notcontains 'old') { throw "second expected old, got [$($m2 -join ',')]" } @' Register-ArgumentCompleter -CommandName 'lc-proxykey' -ParameterName 'New' -ScriptBlock { 'new' } '@ | Set-Content $f -Encoding UTF8 Add-LazyCompletion $f -Force | Out-Null $ps = New-PS try { $null = Test-Tab 'lc-proxykey -New ' 17 $ps # trigger reload $m4 = Test-Tab 'lc-proxykey -New ' 17 $ps $m5 = Test-Tab 'lc-proxykey -Old ' 17 $ps } finally { $ps.Dispose() } if ($m4 -notcontains 'new') { throw "-New expected new, got [$($m4 -join ',')]" } if ($m5 -contains 'old') { throw "-Old must not yield old anymore, got [$($m5 -join ',')]" } Remove-Item function:lc-proxykey -ErrorAction SilentlyContinue Remove-Item $dir -Recurse -Force } Write-Host "" Write-Host "=== Result: $passed passed / $failed failed ===" -ForegroundColor Cyan exit $failed |