tests/cases/70-coverage.ps1
|
# 其余可测代码:模块导出、Golf/Sandbox/PreprocessOnly、predicate/PSObjectToString、语法错误数据、WebServer 冒烟。 $deps = $script:CoreCompileDeps Add-Test @{ Name = 'modules.exports' Group = 'coverage' Deps = @('ps12exe.psm1', 'ps12exe.psd1', 'src/GUI/', 'src/WebServer/', 'src/Interact/') Run = { param($ctx) $mod = Import-Module (Join-Path $ctx.RepoRoot 'ps12exe.psd1') -Force -PassThru $expected = @('ps12exe', 'ps12exeGUI', 'Set-ps12exeContextMenu', 'Start-ps12exeWebServer', 'Enter-ps12exeInteract', 'exe21sp') foreach ($fn in $expected) { Assert-True ($null -ne (Get-Command $fn -ErrorAction Ignore)) "模块未导出命令 $fn" } $exported = @($mod.ExportedFunctions.Keys) foreach ($fn in $expected) { Assert-True ($exported -contains $fn) "声明导出的函数缺少 $fn" } } } Add-Test @{ Name = 'ps12exe.preprocess-only' Group = 'coverage' Deps = $deps Run = { param($ctx) $src = Join-Path $ctx.WorkDir 'pre.ps1' [System.IO.File]::WriteAllText($src, @' #_if PSScript 'kept-script' #_else 'kept-exe' #_endif #_pragma Resources.Title 'PreTitle' '@, [System.Text.UTF8Encoding]::new($true)) $content = ps12exe -inputFile $src -PreprocessOnly Assert-Match "$content" 'kept-exe' 'PreprocessOnly 未保留 PSEXE 分支' Assert-NotMatch "$content" 'kept-script' 'PreprocessOnly 未剔除 PSScript 分支' Assert-Match "$content" 'PreTitle' 'PreprocessOnly 未保留 pragma' } } Add-Test @{ Name = 'golf.unit' Group = 'coverage' Deps = @('src/GolfModeHeader.ps1') Run = { param($ctx) . (Join-Path $ctx.RepoRoot 'src/GolfModeHeader.ps1') Assert-Equal 5 (gcd 10 15) 'gcd 错误' Assert-Equal 120 (fac 5) 'fac 错误' Assert-Equal 55 (fib 10) 'fib 错误' Assert-Equal $true (isprime 7) 'isprime 7 应为真' Assert-Equal $false (isprime 8) 'isprime 8 应为假' Assert-Equal 'ABC' ($('abc' | up)) 'up 错误' Assert-Equal 'aGk=' (b64e 'hi') 'b64e 错误' Assert-Equal 'hi' (b64d 'aGk=') 'b64d 错误' Assert-Equal 6 ($(@(1, 2, 3) | sum)) 'sum 错误' Assert-Equal '1,2,3' ((jo @(1, 2, 3) ',') ) 'jo 错误' } } Add-Test @{ Name = 'golf.compile' Group = 'coverage' Deps = $deps Build = @{ Name = 'golf'; InputText = "gcd 10 15"; Params = @{ Golf = $true }; Output = 'golf.exe' } Run = { param($ctx) $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['golf'] Assert-Match $r.Output '5' "Golf 模式输出不符:$($r.Output)" } } Add-Test @{ Name = 'sandbox.compile' Group = 'coverage' Deps = $deps Build = @{ Name = 'sandbox'; InputText = "Get-Date | Out-Null; Write-Output 'sandbox-ok'"; Params = @{ Sandbox = $true }; Output = 'sandbox.exe' } Run = { param($ctx) $r = Invoke-ExeCaptureMergedOutput -ExePath $ctx.Builds['sandbox'] Assert-Match $r.Output 'sandbox-ok' "Sandbox 模式输出不符:$($r.Output)" } } Add-Test @{ Name = 'units.predicate-psobject' Group = 'coverage' Deps = @('src/predicate.ps1', 'src/PSObjectToString.ps1') Run = { param($ctx) . (Join-Path $ctx.RepoRoot 'src/predicate.ps1') foreach ($v in @('on', 'true', 'Y', 'Yes', '1', 'enable', '$true')) { Assert-True (IsEnable $v) "IsEnable 应接受 $v" } foreach ($v in @('off', 'false', 'N', 'No', '0', 'disable', '$false')) { Assert-True (IsDisable $v) "IsDisable 应接受 $v" } Assert-False (IsEnable 'maybe') "IsEnable 不应接受 maybe" . (Join-Path $ctx.RepoRoot 'src/PSObjectToString.ps1') Assert-Equal "'a''b'" (PSObjectToString "a'b") '字符串转义错误' Assert-Equal 42 (PSObjectToString 42) '整数转换错误' Assert-Equal '$true' (PSObjectToString $true) '布尔转换错误' $oneLine = PSObjectToString @{ a = 1 } -OneLine Assert-Match $oneLine 'a = 1' '哈希表转换错误' Assert-Equal '-a:1' (Get-ArgsString @{ a = 1 }) 'Get-ArgsString 错误' } } Add-Test @{ Name = 'units.syntax-error-data' Group = 'coverage' Deps = @('src/SyntaxErrorDataBuilder.ps1', 'src/SyntaxErrorI18nDataGetter.ps1') Run = { param($ctx) $data = & (Join-Path $ctx.RepoRoot 'src/SyntaxErrorI18nDataGetter.ps1') -Content 'function {' -Locale 'en-US' Assert-True (@($data).Count -ge 1) '无效脚本应产生语法错误数据' $first = @($data)[0] Assert-True ([bool]$first.Message) '语法错误缺少 Message' Assert-True ($first.Spoce.Line -ge 1) '语法错误缺少行号' Assert-True ([bool]$first.ErrorId) '语法错误缺少 ErrorId' } } Add-Test @{ Name = 'coverage.build-component-fingerprint' Group = 'coverage' Deps = @('tests/lib/common.ps1', 'tests/lib/framework.ps1') Run = { param($ctx) $root = $ctx.RepoRoot $all = @(Get-CompilerInputFiles -RepoRoot $root) Assert-True ($all.Count -gt 0) '编译输入为空' # 只影响编译期消息/测试运行、不进入产物的文件不得出现在构建指纹里(否则无关改动会打掉全部构建缓存)。 foreach ($unrelated in @('exe21sp.ps1', 'src/GUI/', 'src/WebServer/', 'src/locale/', 'src/TaskbarProgress.ps1')) { $hit = @($all | Where-Object { (Get-NormalizedRelPath -Path $_ -Base $root) -like "$unrelated*" }) Assert-Equal 0 $hit.Count "构建指纹不应包含无关输入:$unrelated" } # 各编译器只落在自己的组件里。 $expect = @{ 'src/CoreCompiler.ps1' = 'core' 'src/CodeDomCompiler.ps1' = 'codeDom' 'src/TinySharpCompiler.ps1' = 'tinySharp' } foreach ($rel in $expect.Keys) { $owners = @() foreach ($c in @('common', 'codeDom', 'tinySharp', 'core', 'ps2exe')) { if (@(Get-ComponentInputFiles -RepoRoot $root -Component $c | Where-Object { (Get-NormalizedRelPath -Path $_ -Base $root) -eq $rel }).Count) { $owners += $c } } Assert-Equal $expect[$rel] ($owners -join ',') "$rel 的组件归属错误" } # 组件指纹相互独立。 $coreFp = Get-SourceFingerprint -RepoRoot $root -Components @('common', 'core') $winFp = Get-SourceFingerprint -RepoRoot $root -Components @('common', 'codeDom', 'tinySharp') Assert-True ($coreFp -ne $winFp) 'Core 与 Windows 组件指纹不应相同' # 构建 → 组件映射。 Assert-True ((Get-BuildFingerprintComponents -Spec @{ Compiler = 'ps12exe'; Params = @{ Build = @{ Target = 'Core' } } }) -contains 'core') 'Core 目标应依赖 core 组件' $win = @(Get-BuildFingerprintComponents -Spec @{ Compiler = 'ps12exe' }) Assert-True ($win -contains 'codeDom' -and $win -contains 'tinySharp' -and $win -notcontains 'core') '非 Core 目标组件映射错误' Assert-True ((Get-BuildFingerprintComponents -Spec @{ Compiler = 'ps2exe' }) -contains 'ps2exe') 'ps2exe 兼容层应依赖 ps2exe 组件' } } Add-Test @{ Name = 'coverage.shard-partition' Group = 'coverage' Deps = @('tests/lib/framework.ps1') Run = { param($ctx) $cases = @(Get-AllTestCases) $count = 3 $map = Get-ShardAssignment -Cases $cases -ShardCount $count foreach ($c in $cases) { $s = $map[$c.Name] Assert-True ($s -ge 0 -and $s -lt $count) "用例 $($c.Name) 的分片越界:$s" } # 三片并集 == 全部用例,且不重不漏。 $seen = @{} $loads = @() for ($i = 0; $i -lt $count; $i++) { $sel = @(Select-ShardCases -Cases $cases -ShardCount $count -ShardIndex $i -Assignment $map) $builds = 0 foreach ($c in $sel) { Assert-False $seen.ContainsKey($c.Name) "用例 $($c.Name) 落在多个分片" $seen[$c.Name] = $true $builds += @(Get-CaseBuildSpecs -Case $c).Count } $loads += $builds } Assert-Equal $cases.Count $seen.Count '分片并集未覆盖全部用例' # 贪心均衡:各片构建数差异不应过大。 $spread = ($loads | Measure-Object -Maximum).Maximum - ($loads | Measure-Object -Minimum).Minimum Assert-True ($spread -le 2) "分片构建数不均衡:$($loads -join '/')" } } Add-Test @{ Name = 'webserver.smoke' Group = 'coverage' Deps = @('src/WebServer/', 'src/WebServer/main.ps1') Timeout = 180 Run = { param($ctx) $port = Get-Random -Minimum 41000 -Maximum 49000 $url = "http://localhost:$port/" $serverPs1 = Join-Path $ctx.WorkDir 'serve.ps1' [System.IO.File]::WriteAllText($serverPs1, "Import-Module '$($ctx.RepoRoot)' -Force`nStart-ps12exeWebServer -HostUrl '$url'", [System.Text.UTF8Encoding]::new($true)) $pwsh = (Get-Process -Id $PID).Path $log = Join-Path $ctx.WorkDir 'server.log' $err = Join-Path $ctx.WorkDir 'server.err' $proc = Start-Process -FilePath $pwsh -ArgumentList @('-NoProfile', '-NonInteractive', '-File', $serverPs1) -PassThru -NoNewWindow -RedirectStandardOutput $log -RedirectStandardError $err try { $ok = $false for ($i = 0; $i -lt 40; $i++) { Start-Sleep -Milliseconds 500 if ($proc.HasExited) { break } try { $resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 3 if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) { $ok = $true; break } } catch {} } $diag = (Get-Content -LiteralPath $log -Raw -ErrorAction Ignore) + "`n" + (Get-Content -LiteralPath $err -Raw -ErrorAction Ignore) Assert-True $ok "WebServer 未在预期时间内响应 $url;日志:$diag" } finally { if (-not $proc.HasExited) { Stop-ProcessTree -ProcessId $proc.Id } } } } Add-Test @{ Name = 'contextmenu.toggle' Group = 'coverage' Serial = $true Deps = @('src/GUI/ContextMenuAdder.ps1', 'ps12exe.psm1') Run = { param($ctx) $key = 'Registry::HKEY_CURRENT_USER\Software\Classes\*\shell\ps12exeCompile' $wasEnabled = [bool](Test-Path -LiteralPath $key) try { Set-ps12exeContextMenu -action disable -SkipEditorExtension Set-ps12exeContextMenu -action disable -SkipEditorExtension Assert-False (Test-Path -LiteralPath $key) '未启用时禁用右键菜单应不报错且保持不存在' Set-ps12exeContextMenu -action enable -SkipEditorExtension Assert-True (Test-Path -LiteralPath $key) '启用后右键菜单应存在' Set-ps12exeContextMenu -action disable -SkipEditorExtension Assert-False (Test-Path -LiteralPath $key) '禁用后右键菜单应不存在' } finally { if ($wasEnabled) { Set-ps12exeContextMenu -action enable -SkipEditorExtension } else { Set-ps12exeContextMenu -action disable -SkipEditorExtension } } } } |