mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-07-16 00:40:39 +00:00
* 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
246 lines
8.9 KiB
PowerShell
246 lines
8.9 KiB
PowerShell
#===========================================================================
|
|
# Tests - General Sanity
|
|
#===========================================================================
|
|
|
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
|
|
BeforeAll {
|
|
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
|
|
function script:Test-WinUtilParser {
|
|
param([string]$Path)
|
|
|
|
$tokens = $null
|
|
$syntaxErrors = $null
|
|
[System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$syntaxErrors) | Out-Null
|
|
|
|
if ($syntaxErrors.Count -ne 0) {
|
|
throw ($syntaxErrors | Out-String)
|
|
}
|
|
}
|
|
|
|
function script:Invoke-WindowsPowerShellParser {
|
|
param([string[]]$Path)
|
|
|
|
$windowsPowerShell = Get-Command powershell.exe -ErrorAction SilentlyContinue
|
|
if (-not $windowsPowerShell) {
|
|
Set-ItResult -Skipped -Because "powershell.exe is not available on this platform."
|
|
return
|
|
}
|
|
|
|
$previousPaths = $env:WINUTIL_TEST_PARSE_PATHS
|
|
try {
|
|
$env:WINUTIL_TEST_PARSE_PATHS = @($Path) -join [Environment]::NewLine
|
|
$parseScript = @'
|
|
$ErrorActionPreference = 'Stop'
|
|
$paths = $env:WINUTIL_TEST_PARSE_PATHS -split "`r?`n" | Where-Object { $_ }
|
|
$failed = @()
|
|
|
|
foreach ($path in $paths) {
|
|
$tokens = $null
|
|
$syntaxErrors = $null
|
|
[System.Management.Automation.Language.Parser]::ParseFile($path, [ref]$tokens, [ref]$syntaxErrors) | Out-Null
|
|
|
|
if ($syntaxErrors.Count -ne 0) {
|
|
$messages = $syntaxErrors | ForEach-Object { $_.Message }
|
|
$failed += "[$path] $($messages -join '; ')"
|
|
}
|
|
}
|
|
|
|
if ($failed.Count -gt 0) {
|
|
$failed -join [Environment]::NewLine
|
|
exit 1
|
|
}
|
|
'@
|
|
|
|
$output = & $windowsPowerShell.Source -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command $parseScript 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Windows PowerShell parser failed:`n$($output | Out-String)"
|
|
}
|
|
} finally {
|
|
if ($null -eq $previousPaths) {
|
|
Remove-Item Env:\WINUTIL_TEST_PARSE_PATHS -ErrorAction SilentlyContinue
|
|
} else {
|
|
$env:WINUTIL_TEST_PARSE_PATHS = $previousPaths
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe "PowerShell source sanity" {
|
|
$scriptCases = @(
|
|
"Compile.ps1",
|
|
"windev.ps1",
|
|
"scripts\start.ps1",
|
|
"scripts\main.ps1"
|
|
) | ForEach-Object {
|
|
@{
|
|
Name = $_
|
|
Path = Join-Path $repoRoot $_
|
|
}
|
|
}
|
|
|
|
foreach ($scriptCase in $scriptCases) {
|
|
It "parses $($scriptCase.Name) with the current PowerShell parser" -TestCases $scriptCase {
|
|
param([string]$Path)
|
|
|
|
Test-WinUtilParser -Path $Path
|
|
}
|
|
}
|
|
|
|
It "parses WinUtil source files with Windows PowerShell when available" {
|
|
$sourcePaths = @(
|
|
Join-Path $script:repoRoot "Compile.ps1"
|
|
Join-Path $script:repoRoot "windev.ps1"
|
|
Join-Path $script:repoRoot "scripts\start.ps1"
|
|
Join-Path $script:repoRoot "scripts\main.ps1"
|
|
Get-ChildItem -Path (Join-Path $script:repoRoot "functions") -Filter *.ps1 -Recurse | Select-Object -ExpandProperty FullName
|
|
)
|
|
|
|
Invoke-WindowsPowerShellParser -Path $sourcePaths
|
|
}
|
|
}
|
|
|
|
Describe "Compiled WinUtil sanity" {
|
|
BeforeAll {
|
|
$script:compiledPath = Join-Path $script:repoRoot "winutil.ps1"
|
|
|
|
Push-Location $script:repoRoot
|
|
try {
|
|
& (Join-Path $script:repoRoot "Compile.ps1")
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
It "generates winutil.ps1" {
|
|
Test-Path -Path $script:compiledPath | Should -BeTrue
|
|
}
|
|
|
|
It "parses compiled winutil.ps1 with the current PowerShell parser" {
|
|
Test-WinUtilParser -Path $script:compiledPath
|
|
}
|
|
|
|
It "parses compiled winutil.ps1 with Windows PowerShell when available" {
|
|
Invoke-WindowsPowerShellParser -Path $script:compiledPath
|
|
}
|
|
|
|
It "contains embedded configs, XAML, autounattend XML, and runspace bootstrap" {
|
|
$content = Get-Content -Path $script:compiledPath -Raw
|
|
$requiredSnippets = @(
|
|
('$sync.configs.applications = @' + "'"),
|
|
('$inputXML = @' + "'"),
|
|
('$WinUtilAutounattendXml = @' + "'"),
|
|
"SessionStateVariableEntry -ArgumentList 'sync'",
|
|
"SessionStateFunctionEntry",
|
|
"[runspacefactory]::CreateRunspacePool",
|
|
"function Invoke-WPFRunspace"
|
|
)
|
|
|
|
foreach ($snippet in $requiredSnippets) {
|
|
if (-not $content.Contains($snippet)) {
|
|
throw "Compiled script is missing expected content: $snippet"
|
|
}
|
|
}
|
|
}
|
|
|
|
It "transforms applications config keys with WPFInstall prefixes" {
|
|
$content = Get-Content -Path $script:compiledPath -Raw
|
|
$configMatch = [regex]::Match(
|
|
$content,
|
|
"(?s)\`$sync\.configs\.applications = @'\r?\n(?<json>.*?)\r?\n'@ \| ConvertFrom-Json"
|
|
)
|
|
|
|
if (-not $configMatch.Success) {
|
|
throw "Compiled script is missing embedded applications config."
|
|
}
|
|
|
|
$sourceApps = Get-Content -Path (Join-Path $script:repoRoot "config\applications.json") -Raw | ConvertFrom-Json
|
|
$compiledApps = $configMatch.Groups["json"].Value | ConvertFrom-Json
|
|
|
|
foreach ($sourceApp in $sourceApps.PSObject.Properties) {
|
|
$compiledKey = "WPFInstall$($sourceApp.Name)"
|
|
if ($compiledApps.PSObject.Properties.Name -notcontains $compiledKey) {
|
|
throw "Compiled applications config is missing transformed key: $compiledKey"
|
|
}
|
|
if ($compiledApps.PSObject.Properties.Name -contains $sourceApp.Name) {
|
|
throw "Compiled applications config contains untransformed source key: $($sourceApp.Name)"
|
|
}
|
|
}
|
|
}
|
|
|
|
It "preserves compile source ordering" {
|
|
$content = Get-Content -Path $script:compiledPath -Raw
|
|
$orderedSnippets = @(
|
|
'$sync.version =',
|
|
'function Add-SelectedAppsMenuItem',
|
|
('$sync.configs.applications = @' + "'"),
|
|
('$inputXML = @' + "'"),
|
|
('$WinUtilAutounattendXml = @' + "'"),
|
|
'$sync.SearchBarClearButton.Add_Click({'
|
|
)
|
|
|
|
$lastIndex = -1
|
|
foreach ($snippet in $orderedSnippets) {
|
|
$index = $content.IndexOf($snippet)
|
|
if ($index -lt 0) {
|
|
throw "Compiled script is missing expected ordered content: $snippet"
|
|
}
|
|
if ($index -le $lastIndex) {
|
|
throw "Compiled script content is out of order near: $snippet"
|
|
}
|
|
|
|
$lastIndex = $index
|
|
}
|
|
}
|
|
|
|
It "replaces the generated build date placeholder" {
|
|
$content = Get-Content -Path $script:compiledPath -Raw
|
|
$expectedBuildDate = Get-Date -Format "yy.MM.dd"
|
|
|
|
$content | Should -Not -Match ([regex]::Escape("#{replaceme}"))
|
|
$content | Should -Match ([regex]::Escape('$sync.version = "' + $expectedBuildDate + '"'))
|
|
}
|
|
}
|
|
|
|
Describe "Runspace sanity" {
|
|
BeforeAll {
|
|
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1")
|
|
. (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1")
|
|
. (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1")
|
|
|
|
function Write-WinUtilPerformanceCheckpoint { param($Name) }
|
|
}
|
|
|
|
It "returns a single async handle and runs a scriptblock with arguments in the shared runspace pool" {
|
|
$script:sync = [Hashtable]::Synchronized(@{ SmokeValue = "shared" })
|
|
$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()
|
|
|
|
try {
|
|
$script:sync.Result = $null
|
|
$handle = Invoke-WPFRunspace -ArgumentList "argument" -ParameterList @(,("NamedValue", "parameter")) -ScriptBlock {
|
|
param($ArgumentValue, [string]$NamedValue)
|
|
|
|
Start-Sleep -Milliseconds 200
|
|
$sync.Result = "$ArgumentValue|$NamedValue|$($sync.SmokeValue)"
|
|
}
|
|
|
|
($handle -is [System.IAsyncResult]) | Should -BeTrue
|
|
($handle -is [array]) | Should -BeFalse
|
|
$handle.AsyncWaitHandle.WaitOne(5000) | Should -BeTrue
|
|
$script:sync.Result | Should -Be "argument|parameter|shared"
|
|
} finally {
|
|
if ($script:sync -and $script:sync.runspace) {
|
|
$script:sync.runspace.Close()
|
|
$script:sync.runspace.Dispose()
|
|
}
|
|
|
|
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|