tools/Run-GitEasyPester.ps1
|
<#
.SYNOPSIS Run the GitEasy Pester test suite under both PowerShell 7 and Windows PowerShell 5.1. .DESCRIPTION Loads Pester 5 (minimum 5.0) and runs the full Tests folder. After a passing run it re-invokes itself under the sibling PowerShell edition (powershell.exe if running in pwsh, or pwsh if running in powershell.exe) so that Desktop and Core compatibility are both verified on every run. Pester 5 must be installed in both editions. Install it once per edition with: Install-Module Pester -Force -SkipPublisherCheck .PARAMETER ProjectRoot Absolute path to the GitEasy source repository. Defaults to the repo root derived from this script's location. .EXAMPLE .\tools\Run-GitEasyPester.ps1 .NOTES The module manifest declares CompatiblePSEditions = @('Desktop','Core'). Both editions must pass before a release is cut. #> [CmdletBinding()] param( [string]$ProjectRoot = (Split-Path -Parent $PSScriptRoot), # Emit code-coverage data. Renders a per-file summary to stdout and writes # a coverage.txt artifact. Report-only — no threshold gate. [switch]$Coverage, [string]$CoverageOutputPath, # Suppress per-Describe/It chatter; show only totals and failure detail. [switch]$Quiet, # Internal: prevents infinite recursion when this script re-invokes itself # under the sibling PowerShell edition. Not intended for direct use. [switch]$ThisEditionOnly ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if (-not (Test-Path -LiteralPath $ProjectRoot)) { throw "Missing project folder: $ProjectRoot" } $testRoot = Join-Path $ProjectRoot 'Tests' if (-not (Test-Path -LiteralPath $testRoot)) { throw "Missing test folder: $testRoot" } $pester = Get-Module -ListAvailable Pester | Where-Object { $_.Version.Major -ge 5 } | Sort-Object Version -Descending | Select-Object -First 1 if (-not $pester) { throw "Pester 5 is not installed. Install it with: Install-Module Pester -Force -SkipPublisherCheck" } Remove-Module Pester -Force -ErrorAction SilentlyContinue Import-Module $pester.Path -Force Write-Host "" Write-Host "Running GitEasy Pester tests..." -ForegroundColor Cyan Write-Host "Project: $ProjectRoot" Write-Host "Pester: $($pester.Version)" Write-Host "" $invokeParams = @{ Path = $testRoot PassThru = $true } if ($Quiet) { $invokeParams.Output = 'Minimal' } if ($Coverage) { $publicRoot = Join-Path $ProjectRoot 'Public' $privateRoot = Join-Path $ProjectRoot 'Private' $coveragePaths = @() foreach ($r in @($publicRoot, $privateRoot)) { if (Test-Path -LiteralPath $r) { $coveragePaths += (Get-ChildItem -Path $r -Filter '*.ps1' -Recurse -File).FullName } } if ($coveragePaths.Count -gt 0) { $invokeParams.CodeCoverage = $coveragePaths } else { Write-Warning "No Public/Private .ps1 files found under $ProjectRoot - coverage skipped." $Coverage = $false } } $result = Invoke-Pester @invokeParams $summary = [PSCustomObject]@{ Total = $result.TotalCount Passed = $result.PassedCount Failed = $result.FailedCount Skipped = $result.SkippedCount } Write-Host "" Write-Host "GitEasy Pester summary:" -ForegroundColor Cyan $summary | Format-List if ($Coverage -and $result.PSObject.Properties['CodeCoverage'] -and $result.CodeCoverage) { $cc = $result.CodeCoverage $analyzed = $cc.NumberOfCommandsAnalyzed $executed = $cc.NumberOfCommandsExecuted $missed = $cc.NumberOfCommandsMissed $pct = if ($analyzed -gt 0) { [math]::Round(($executed / $analyzed) * 100, 1) } else { 0 } $perFile = $cc.AnalyzedFiles | ForEach-Object { $file = $_ $fileMissed = @($cc.MissedCommands | Where-Object { $_.File -eq $file }).Count $fileHit = @($cc.HitCommands | Where-Object { $_.File -eq $file }).Count $fileTotal = $fileHit + $fileMissed $filePct = if ($fileTotal -gt 0) { [math]::Round(($fileHit / $fileTotal) * 100, 1) } else { 0 } [PSCustomObject]@{ File = (Split-Path -Leaf $file) Hit = $fileHit Missed = $fileMissed Total = $fileTotal Percent = $filePct } } | Sort-Object Percent Write-Host "" Write-Host "Code coverage: $executed / $analyzed commands ($pct%)" -ForegroundColor Cyan $perFile | Format-Table -AutoSize | Out-String | Write-Host if (-not $CoverageOutputPath) { $CoverageOutputPath = Join-Path $ProjectRoot 'coverage.txt' } $reportLines = @( "GitEasy code coverage", "Generated: $(Get-Date -Format 'o')", "Total: $executed / $analyzed commands ($pct%)", "Missed: $missed", "", "Per-file:" ) $reportLines += ($perFile | Format-Table -AutoSize | Out-String).TrimEnd() Set-Content -Path $CoverageOutputPath -Value $reportLines -Encoding UTF8 Write-Host "Coverage report written to $CoverageOutputPath" -ForegroundColor Cyan } if ($result.FailedCount -gt 0) { throw "GitEasy Pester tests failed." } Write-Host "GitEasy Pester tests passed." -ForegroundColor Green if (-not $ThisEditionOnly) { if ($PSVersionTable.PSEdition -eq 'Core') { $altExe = Get-Command powershell.exe -ErrorAction SilentlyContinue $altName = 'Windows PowerShell 5.1' } else { $altExe = Get-Command pwsh -ErrorAction SilentlyContinue $altName = 'PowerShell 7' } if ($altExe) { Write-Host "" Write-Host "==> Re-running under $altName..." -ForegroundColor Cyan & $altExe.Source -NonInteractive -NoProfile -File $PSCommandPath -ProjectRoot $ProjectRoot -Quiet -ThisEditionOnly if ($LASTEXITCODE -ne 0) { throw "Suite failed under $altName (exit $LASTEXITCODE). Run '$($altExe.Source) -NoProfile -File tools\Run-GitEasyPester.ps1' to see failures." } Write-Host "GitEasy Pester tests passed under $altName." -ForegroundColor Green } else { Write-Warning "$altName not found - cross-edition pass skipped." } } |