Files
winutil/pester/runspace.Tests.ps1
Chris Titus 93dc23dd66 Speed up runspace and tab initialization (#4793)
* Add startup performance tracing

* Lazy initialize non-default tabs

* Render install app entries incrementally

* Defer and cache toggle status checks

* Clean up runspace invocation ownership

* Defer GUI runspace pool startup

* Defer status taskbar asset rendering

* Record final speed verification

* Fix deferred install render timer callback

* Add dispatcher smoke coverage for install rendering

* Replace install render timer with dispatcher callbacks

* Gate performance tracing behind compile switch

* Clean up analyzer warnings in speed changes

* Speed up runspace and tab initialization
2026-07-01 23:34:59 -05:00

209 lines
7.4 KiB
PowerShell

#===========================================================================
# Tests - Runspace Behavior
#===========================================================================
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
. (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1")
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFFeatureInstall.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFAppxRemoval.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFundoall.ps1")
function script:New-WinUtilRunspaceTestContext {
param([hashtable]$InitialSync = @{})
$script:sync = [Hashtable]::Synchronized($InitialSync)
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$syncVariable = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList "sync", $script:sync, $null
$initialSessionState.Variables.Add($syncVariable)
$script:sync.runspace = [runspacefactory]::CreateRunspacePool(1, 2, $initialSessionState, $Host)
$script:sync.runspace.Open()
function Write-WinUtilPerformanceCheckpoint { param($Name) }
}
function script:Clear-WinUtilRunspaceTestContext {
if ($script:sync -and $script:sync.runspace) {
$script:sync.runspace.Close()
$script:sync.runspace.Dispose()
}
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
}
function script:Assert-WinUtilAsyncHandle {
param($Handle)
($Handle -is [System.IAsyncResult]) | Should -BeTrue
($Handle -is [array]) | Should -BeFalse
$Handle.AsyncWaitHandle.WaitOne(5000) | Should -BeTrue
}
}
Describe "Invoke-WPFRunspace behavior" {
BeforeEach {
New-WinUtilRunspaceTestContext -InitialSync @{ Marker = "shared" }
}
AfterEach {
Clear-WinUtilRunspaceTestContext
}
It "returns a single async handle with no argument list" {
$script:sync.Result = $null
$handle = Invoke-WPFRunspace -ScriptBlock {
Start-Sleep -Milliseconds 100
$sync.Result = "no-args|$($sync.Marker)"
}
Assert-WinUtilAsyncHandle -Handle $handle
$script:sync.Result | Should -Be "no-args|shared"
}
It "passes one named parameter" {
$script:sync.Result = $null
$handle = Invoke-WPFRunspace -ParameterList @(,("Name", "value")) -ScriptBlock {
param([string]$Name)
Start-Sleep -Milliseconds 100
$sync.Result = "Name=$Name"
}
Assert-WinUtilAsyncHandle -Handle $handle
$script:sync.Result | Should -Be "Name=value"
}
It "passes multiple named parameters" {
$script:sync.Result = $null
$handle = Invoke-WPFRunspace -ParameterList @(
("First", "alpha"),
("Second", "beta")
) -ScriptBlock {
param(
[string]$First,
[string]$Second
)
Start-Sleep -Milliseconds 100
$sync.Result = "$First|$Second|$($sync.Marker)"
}
Assert-WinUtilAsyncHandle -Handle $handle
$script:sync.Result | Should -Be "alpha|beta|shared"
}
It "keeps the shared runspace pool usable after scriptblock failures" {
$handle = Invoke-WPFRunspace -ScriptBlock {
Start-Sleep -Milliseconds 100
throw "runspace failure"
}
Assert-WinUtilAsyncHandle -Handle $handle
$script:sync.Result = $null
$secondHandle = Invoke-WPFRunspace -ScriptBlock {
$sync.Result = "after-failure"
}
Assert-WinUtilAsyncHandle -Handle $secondHandle
$script:sync.Result | Should -Be "after-failure"
}
It "runs multiple queued invocations without shared PowerShell state" {
$script:sync.FirstResult = $null
$script:sync.SecondResult = $null
$firstHandle = Invoke-WPFRunspace -ParameterList @(,("Value", "first")) -ScriptBlock {
param([string]$Value)
Start-Sleep -Milliseconds 150
$sync.FirstResult = $Value
}
$secondHandle = Invoke-WPFRunspace -ParameterList @(,("Value", "second")) -ScriptBlock {
param([string]$Value)
$sync.SecondResult = $Value
}
Assert-WinUtilAsyncHandle -Handle $firstHandle
Assert-WinUtilAsyncHandle -Handle $secondHandle
$script:sync.FirstResult | Should -Be "first"
$script:sync.SecondResult | Should -Be "second"
}
It "does not use script-scoped PowerShell or handle state" {
$runspaceScript = Get-Content -Path (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1") -Raw
$runspaceScript | Should -Not -Match '\$script:powershell'
$runspaceScript | Should -Not -Match '\$script:handle'
}
}
Describe "Public runspace callers" {
BeforeEach {
$script:sync = [Hashtable]::Synchronized(@{
ProcessRunning = $false
selectedFeatures = [System.Collections.Generic.List[string]]::new()
selectedTweaks = [System.Collections.Generic.List[string]]::new()
selectedAppx = [System.Collections.Generic.List[string]]::new()
configs = @{
appxHashtable = @{}
}
})
Mock Invoke-WPFRunspace { [pscustomobject]@{ MockHandle = $true } }
}
AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
}
It "queues selected feature installation without executing the runspace body" {
$script:sync.selectedFeatures.Add("WPFFeaturesSandbox")
Invoke-WPFFeatureInstall
Should -Invoke -CommandName Invoke-WPFRunspace -Times 1 -Exactly -ParameterFilter {
$ScriptBlock -is [scriptblock] -and $null -eq $ArgumentList -and $null -eq $ParameterList
}
}
It "passes selected tweaks as the runspace argument list for undo all" {
$script:sync.selectedTweaks.Add("WPFTweaksTelemetry")
$script:sync.selectedTweaks.Add("WPFTweaksServices")
Invoke-WPFundoall
Should -Invoke -CommandName Invoke-WPFRunspace -Times 1 -Exactly -ParameterFilter {
$ScriptBlock -is [scriptblock] -and
$ArgumentList.Count -eq 2 -and
$ArgumentList[0] -eq "WPFTweaksTelemetry" -and
$ArgumentList[1] -eq "WPFTweaksServices"
}
}
It "passes selected AppX items and app metadata to the removal runspace" {
$script:sync.selectedAppx.Add("WPFAppxExample")
$script:sync.configs.appxHashtable["WPFAppxExample"] = [pscustomobject]@{
Content = "Example"
PackageId = "Example.Package"
}
Invoke-WPFAppxRemoval
Should -Invoke -CommandName Invoke-WPFRunspace -Times 1 -Exactly -ParameterFilter {
$ScriptBlock -is [scriptblock] -and
$ParameterList.Count -eq 2 -and
$ParameterList[0][0] -eq "selected" -and
$ParameterList[0][1][0] -eq "WPFAppxExample" -and
$ParameterList[1][0] -eq "apps" -and
$ParameterList[1][1].ContainsKey("WPFAppxExample")
}
}
}