Files
AppKit/build/Assert-Coverage.ps1
James MaesandClaude Opus 5.5 83f028f188 AppKit: shared foundation packages for KofTwentyTwo Windows apps
Extracts the app-agnostic plumbing proven in gclo 1.0 into four packages that
every future KofTwentyTwo Windows app builds on:

- KofTwentyTwo.AppKit: AppInfo identity, AppPaths, BuildVersion, crash-safe
  FileActivityLog with retention, LogTail, atomic SettingsStore<T> with
  ShellSettings, Credential Manager secret vault, IUserPrompter seam
- KofTwentyTwo.AppKit.Updates: Velopack startup hook, never-throwing GitHub
  Releases update service with stable/dev channel selection, and the shared
  "check for updates" flow (UpdateCoordinator)
- KofTwentyTwo.AppKit.WinUI / .Wpf: code-built splash overlay, About, live log
  viewer, crash net, theme and window helpers, dialog adapters

The two framework-free packages are held to 100% line coverage (109 tests).
WinUI and WPF sample apps exercise every service and are driven end to end by
12 FlaUI tests. BrandTool generates the vector mark and multi-size .ico.

CI mirrors gclo's gates (zero warnings, coverage, UI tests, format, CodeQL,
dependency review); the release workflow packs, checksums, attests build
provenance, publishes a GitHub release, and pushes to nuget.org.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 07:10:42 -05:00

64 lines
2.6 KiB
PowerShell

<#
.SYNOPSIS
Fails when any named assembly is below the required line coverage.
.DESCRIPTION
Reads every coverage.cobertura.xml under -ResultsDirectory (one per test project
run) and, for each package in -Packages, takes the best line rate any report
measured. A package can be split across test projects, and the report that
exercises it most is authoritative. Exits 1 on any shortfall or missing package.
.EXAMPLE
./build/Assert-Coverage.ps1 -ResultsDirectory TestResults -Packages KofTwentyTwo.AppKit,KofTwentyTwo.AppKit.Updates
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $ResultsDirectory,
[Parameter(Mandatory)] [string[]] $Packages,
[ValidateRange(0, 100)] [double] $MinimumPercent = 100
)
$ErrorActionPreference = 'Stop'
# `pwsh -File` passes "A,B" as one string; accept that as well as a real array.
$Packages = @($Packages | ForEach-Object { $_ -split ',' } | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$files = @(Get-ChildItem -Path $ResultsDirectory -Recurse -Filter coverage.cobertura.xml -ErrorAction SilentlyContinue)
if ($files.Count -eq 0) {
Write-Error "No coverage.cobertura.xml found under '$ResultsDirectory'. Did the coverage collector run?" -ErrorAction Continue
exit 1
}
$reports = foreach ($file in $files) { [xml](Get-Content -LiteralPath $file.FullName -Raw) }
$required = $MinimumPercent / 100
$failed = $false
foreach ($name in $Packages) {
$rates = foreach ($doc in $reports) {
$package = @($doc.coverage.packages.package) | Where-Object { $_.name -eq $name }
if ($package) { [double]::Parse($package.'line-rate', [Globalization.CultureInfo]::InvariantCulture) }
}
if (-not $rates) {
Write-Error "Package '$name' not found in any coverage report." -ErrorAction Continue
$failed = $true
continue
}
$best = ($rates | Measure-Object -Maximum).Maximum
Write-Host ("{0} line coverage: {1}% (required {2}%)" -f $name, [math]::Round($best * 100, 2), $MinimumPercent)
if ($best -lt $required) {
foreach ($doc in $reports) {
$package = @($doc.coverage.packages.package) | Where-Object { $_.name -eq $name }
foreach ($class in @($package.classes.class)) {
$missed = @($class.lines.line) | Where-Object { $_.hits -eq '0' } | ForEach-Object { $_.number }
if ($missed) { Write-Host " uncovered: $($class.filename) lines $($missed -join ', ')" }
}
}
Write-Error "Coverage gate FAILED: $name is below $MinimumPercent%." -ErrorAction Continue
$failed = $true
}
}
if ($failed) { exit 1 }
Write-Host "Coverage gate passed."