diff --git a/CHANGELOG.md b/CHANGELOG.md index dd54893..0e1994b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). two Pester versions were installed side by side. When no Pester is loaded, the newest installed version (5.0.0 minimum) is imported as before, and a loaded Pester older than 5.0.0 now produces a clear error. +- [**#138**](https://github.com/psake/PowerShellBuild/issues/138) + `Test-PSBuildPester` now reports real code coverage percentages and compares + them against `CodeCoverageThreshold` correctly. Each percentage was passed + through `[Math]::Truncate`, which collapses any fraction to zero, so the + coverage report always printed `0.00%` (or `100.00%`) and every threshold + above zero failed the build unless coverage was exactly 100%. Consumers who + set `$PSBPreference.Test.CodeCoverage.Threshold` could not use coverage + gating at all. The comparison is now strictly more permissive than before, + so a build that passed with a coverage threshold set still passes. ## [0.8.2] 2026-07-08 diff --git a/PowerShellBuild/Public/Test-PSBuildPester.ps1 b/PowerShellBuild/Public/Test-PSBuildPester.ps1 index 156d837..5a3b293 100644 --- a/PowerShellBuild/Public/Test-PSBuildPester.ps1 +++ b/PowerShellBuild/Public/Test-PSBuildPester.ps1 @@ -124,7 +124,12 @@ function Test-PSBuildPester { [xml]$testCoverage = Get-Content $CodeCoverageOutputFile $ccReport = $testCoverage.report.counter.ForEach({ $total = [int]$_.missed + [int]$_.covered - $percent = [Math]::Truncate([int]$_.covered / $total) + # Keep the ratio as a fraction between 0 and 1. [Math]::Truncate collapsed + # every partial result to 0, which both printed 0.00% and failed the + # CodeCoverageThreshold comparison below for any coverage under 100%. + # The value is deliberately not rounded: rounding 0.7996 up to 0.80 would + # pass a 0.80 threshold that the real coverage does not meet. + $percent = [int]$_.covered / $total [PSCustomObject]@{ name = $textInfo.ToTitleCase($_.Type.ToLower()) percent = $percent diff --git a/tests/Test-PSBuildPester.tests.ps1 b/tests/Test-PSBuildPester.tests.ps1 index e3cc9f7..43a3c8a 100644 --- a/tests/Test-PSBuildPester.tests.ps1 +++ b/tests/Test-PSBuildPester.tests.ps1 @@ -61,8 +61,12 @@ Describe 'Test-PSBuildPester' { $threw = $false $errorMessage = $null + # Capture the command's output rather than letting it fall through to the job's + # output stream, where the coverage report lines would be interleaved with the + # result object below. + $commandOutput = @() try { - Test-PSBuildPester @testPSBuildPesterParameters + $commandOutput = @(Test-PSBuildPester @testPSBuildPesterParameters) } catch { $threw = $true $errorMessage = $_.Exception.Message @@ -71,6 +75,7 @@ Describe 'Test-PSBuildPester' { [PSCustomObject]@{ Threw = $threw ErrorMessage = $errorMessage + Output = $commandOutput LoadedPesterVersions = @((Get-Module -Name 'Pester').Version.ToString()) } } -ArgumentList $InnerPesterVersion, $script:builtModulePath, $Path, $AdditionalParameters @@ -220,6 +225,39 @@ Describe 'Coverage target' { [xml]$coverageReport = Get-Content -Path $coverageOutputPath -Raw $coverageReport.report | Should -Not -BeNullOrEmpty } + + # The coverage scenario exercises only Get-Widget while measuring both public fixture + # functions, so measured coverage is always partial: comfortably above 1% and well + # below 99%. Asserting from both sides pins the reported value to a fraction between + # 0 and 1 -- a truncated 0 fails the first test, and a 0-to-100 scale fails the second. + It 'passes when measured coverage is above the code coverage threshold' { + # Regression: #138 + $coverageOutputPath = Join-Path -Path $script:outputPath -ChildPath "coverage-above-threshold-$script:innerVersion.xml" + $additionalParameters = @{ + CodeCoverage = $true + CodeCoverageFiles = @(Join-Path -Path $script:fixturePath -ChildPath 'Public/*.ps1') + CodeCoverageOutputFile = $coverageOutputPath + CodeCoverageThreshold = 0.01 + } + $result = Invoke-TestPSBuildPesterJob -InnerPesterVersion $script:innerVersion -Path $script:coveragePath -AdditionalParameters $additionalParameters + + $result.Threw | Should -BeFalse + } + + It 'fails when measured coverage is below the code coverage threshold' { + # Regression: #138 + $coverageOutputPath = Join-Path -Path $script:outputPath -ChildPath "coverage-below-threshold-$script:innerVersion.xml" + $additionalParameters = @{ + CodeCoverage = $true + CodeCoverageFiles = @(Join-Path -Path $script:fixturePath -ChildPath 'Public/*.ps1') + CodeCoverageOutputFile = $coverageOutputPath + CodeCoverageThreshold = 0.99 + } + $result = Invoke-TestPSBuildPesterJob -InnerPesterVersion $script:innerVersion -Path $script:coveragePath -AdditionalParameters $additionalParameters + + $result.Threw | Should -BeTrue + $result.ErrorMessage | Should -Match 'less than the threshold' + } } # BeforeDiscovery variables are not visible during the run phase, so the discovered version