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>
64 lines
2.6 KiB
PowerShell
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."
|