Skip to content

Commit

Permalink
Fix blank code coverage 1621 (pester#1807)
Browse files Browse the repository at this point in the history
* [-1621] Adding code coverage option to get all tests in a folder and it's sub folders.

* [-1621] Fixing coverage report to work with ReportGenerator.

* [-1621] Fix handling of test files in subfolders and add tests for file detection

* [-1621] Fixing coverage recursion for PS 5 and PS 3.

* Propagate recurse items

Co-authored-by: nohwnd <[email protected]>
  • Loading branch information
martinisaksenprogleasing and nohwnd authored Feb 21, 2021
1 parent 4e47548 commit 5234884
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 97 deletions.
1 change: 1 addition & 0 deletions src/Pester.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ function Invoke-Pester {
OutputEncoding = $PesterPreference.CodeCoverage.OutputEncoding.Value
ExcludeTests = $PesterPreference.CodeCoverage.ExcludeTests.Value
Path = @($paths)
RecursePaths = $PesterPreference.CodeCoverage.RecursePaths.Value
TestExtension = $PesterPreference.Run.TestExtension.Value
}

Expand Down
20 changes: 19 additions & 1 deletion src/csharp/Pester/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ public class CodeCoverageConfiguration : ConfigurationSection
private StringOption _outputEncoding;
private StringArrayOption _path;
private BoolOption _excludeTests;
private BoolOption _recursePaths;

public static CodeCoverageConfiguration Default { get { return new CodeCoverageConfiguration(); } }

Expand All @@ -587,7 +588,7 @@ public CodeCoverageConfiguration() : base("CodeCoverage configuration.")
OutputEncoding = new StringOption("Encoding of the output file.", "UTF8");
Path = new StringArrayOption("Directories or files to be used for codecoverage, by default the Path(s) from general settings are used, unless overridden here.", new string[0]);
ExcludeTests = new BoolOption("Exclude tests from code coverage. This uses the TestFilter from general configuration.", true);

RecursePaths = new BoolOption("Will recurse through directories in the Path option.", true);
}

public CodeCoverageConfiguration(IDictionary configuration) : this()
Expand All @@ -600,6 +601,7 @@ public CodeCoverageConfiguration(IDictionary configuration) : this()
OutputEncoding = configuration.GetObjectOrNull<string>("OutputEncoding") ?? OutputEncoding;
Path = configuration.GetArrayOrNull<string>("Path") ?? Path;
ExcludeTests = configuration.GetValueOrNull<bool>("ExcludeTests") ?? ExcludeTests;
RecursePaths = configuration.GetValueOrNull<bool>("RecursePaths") ?? RecursePaths;
}
}

Expand Down Expand Up @@ -698,6 +700,22 @@ public BoolOption ExcludeTests
}
}
}

public BoolOption RecursePaths
{
get { return _recursePaths; }
set
{
if (_recursePaths == null)
{
_recursePaths = value;
}
else
{
_recursePaths = new BoolOption(_recursePaths, value.Value);
}
}
}
}

public class TestResultConfiguration : ConfigurationSection
Expand Down
72 changes: 52 additions & 20 deletions src/functions/Coverage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ function Get-CoverageInfoFromUserInput {
# Auto-detect IncludeTests-value from path-input if user provides path that is a test
$IncludeTests = $Path -like "*$($PesterPreference.Run.TestExtension.Value)"

$unresolvedCoverageInfo = New-CoverageInfo -Path $Path -IncludeTests $IncludeTests
$unresolvedCoverageInfo = New-CoverageInfo -Path $Path -IncludeTests $IncludeTests -RecursePaths $PesterPreference.CodeCoverage.RecursePaths.Value
}

Resolve-CoverageInfo -UnresolvedCoverageInfo $unresolvedCoverageInfo
}

function New-CoverageInfo {
param ($Path, [string] $Class = $null, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0, [bool] $IncludeTests = $false)
param ($Path, [string] $Class = $null, [string] $Function = $null, [int] $StartLine = 0, [int] $EndLine = 0, [bool] $IncludeTests = $false, $RecursePaths = $true)

return [pscustomobject]@{
Path = $Path
Expand All @@ -69,6 +69,7 @@ function New-CoverageInfo {
StartLine = $StartLine
EndLine = $EndLine
IncludeTests = $IncludeTests
RecursePaths = $RecursePaths
}
}

Expand All @@ -85,12 +86,14 @@ function Get-CoverageInfoFromDictionary {
[string] $class = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Class', 'c'
[string] $function = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'Function', 'f'
$includeTests = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'IncludeTests'
$recursePaths = Get-DictionaryValueFromFirstKeyFound -Dictionary $Dictionary -Key 'RecursePaths'

$startLine = Convert-UnknownValueToInt -Value $startLine -DefaultValue 0
$endLine = Convert-UnknownValueToInt -Value $endLine -DefaultValue 0
[bool] $includeTests = Convert-UnknownValueToInt -Value $includeTests -DefaultValue 0
[bool] $recursePaths = Convert-UnknownValueToInt -Value $recursePaths -DefaultValue 1

return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Class $class -Function $function -IncludeTests $includeTests
return New-CoverageInfo -Path $path -StartLine $startLine -EndLine $endLine -Class $class -Function $function -IncludeTests $includeTests -RecursePaths $recursePaths
}

function Convert-UnknownValueToInt {
Expand All @@ -107,30 +110,22 @@ function Convert-UnknownValueToInt {
function Resolve-CoverageInfo {
param ([psobject] $UnresolvedCoverageInfo)

$path = $UnresolvedCoverageInfo.Path

$testsPattern = "*$($PesterPreference.Run.TestExtension.Value)"
$paths = $UnresolvedCoverageInfo.Path
$includeTests = $UnresolvedCoverageInfo.IncludeTests
$recursePaths = $UnresolvedCoverageInfo.RecursePaths
$resolvedPaths = @()

try {
$resolvedPaths = & $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop |
& $SafeCommands['Where-Object'] { $includeTests -or $_.Path -notlike $testsPattern }
$resolvedPaths = foreach ($path in $paths) {
& $SafeCommands['Resolve-Path'] -Path $path -ErrorAction Stop
}
}
catch {
& $SafeCommands['Write-Error'] "Could not resolve coverage path '$path': $($_.Exception.Message)"
return
}

$filePaths = foreach ($resolvedPath in $resolvedPaths) {
$item = & $SafeCommands['Get-Item'] -LiteralPath $resolvedPath
if ($item -is [System.IO.FileInfo] -and ('.ps1', '.psm1') -contains $item.Extension) {
$item.FullName
}
elseif (-not $item.PsIsContainer) {
# todo: enable this warning for non wildcarded paths? otherwise it prints a ton of warnings for documenatation and so on when using "folder/*" wildcard
# & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report."
}
}
$filePaths = Get-CodeCoverageFilePaths -Paths $resolvedPaths -IncludeTests $includeTests -RecursePaths $recursePaths

$params = @{
StartLine = $UnresolvedCoverageInfo.StartLine
Expand All @@ -145,6 +140,42 @@ function Resolve-CoverageInfo {
}
}

function Get-CodeCoverageFilePaths {
param (
[object]$Paths,
[bool]$IncludeTests,
[bool]$RecursePaths
)

$testsPattern = "*$($PesterPreference.Run.TestExtension.Value)"

$filePaths = foreach ($path in $Paths) {
$item = & $SafeCommands['Get-Item'] -LiteralPath $path
if ($item -is [System.IO.FileInfo] -and ('.ps1', '.psm1') -contains $item.Extension -and ($IncludeTests -or $item.Name -notlike $testsPattern)) {
$item.FullName
}
elseif ($item -is [System.IO.DirectoryInfo]) {
$children = foreach ($i in & $SafeCommands['Get-ChildItem'] -LiteralPath $item) {
# if we recurse paths return both directories and files so they can be resolved in the
# recursive call to Get-CodeCoverageFilePaths, otherwise return just files
if ($RecursePaths) {
$i.PSPath
}
elseif (-not $i.PSIsContainer) {
$i.PSPath
}}
Get-CodeCoverageFilePaths -Paths $children -IncludeTests $IncludeTests -RecursePaths $RecursePaths
}
elseif (-not $item.PsIsContainer) {
# todo: enable this warning for non wildcarded paths? otherwise it prints a ton of warnings for documenatation and so on when using "folder/*" wildcard
# & $SafeCommands['Write-Warning'] "CodeCoverage path '$path' resolved to a non-PowerShell file '$($item.FullName)'; this path will not be part of the coverage report."
}
}

return $filePaths

}

function Get-CoverageBreakpoints {
[CmdletBinding()]
param (
Expand Down Expand Up @@ -794,7 +825,7 @@ function Get-JaCoCoReportXml {
$classElementName = $classElementName.Substring(0, $($classElementName.LastIndexOf(".")))
$classElement = Add-XmlElement $packageElement 'class' -Attributes ([ordered] @{
name = $classElementName
sourcefilename = (& $SafeCommands["Split-Path"] -Path $classElementRelativePath -Leaf)
sourcefilename = $classElementRelativePath
})

foreach ($function in $class.Methods.Keys) {
Expand All @@ -817,8 +848,9 @@ function Get-JaCoCoReportXml {

foreach ($file in $package.Classes.Keys) {
$class = $package.Classes.$file
$classElementRelativePath = (Get-RelativePath -Path $file -RelativeTo $commonParent).Replace("\", "/")
$sourceFileElement = Add-XmlElement $packageElement 'sourcefile' -Attributes ([ordered] @{
name = (& $SafeCommands["Split-Path"] -Path $file -Leaf)
name = $classElementRelativePath
})

foreach ($line in $class.Lines.Keys) {
Expand Down
155 changes: 79 additions & 76 deletions tst/functions/Coverage.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ InPesterModuleScope {
<counter type="CLASS" missed="0" covered="2" />
</package>
<package name="CommonRoot/TestSubFolder">
<class name="CommonRoot/TestSubFolder/TestScript3" sourcefilename="TestScript3.ps1">
<class name="CommonRoot/TestSubFolder/TestScript3" sourcefilename="TestSubFolder/TestScript3.ps1">
<method name="&lt;script&gt;" desc="()" line="1">
<counter type="INSTRUCTION" missed="0" covered="1" />
<counter type="LINE" missed="0" covered="1" />
Expand All @@ -295,7 +295,7 @@ InPesterModuleScope {
<counter type="METHOD" missed="0" covered="1" />
<counter type="CLASS" missed="0" covered="1" />
</class>
<sourcefile name="TestScript3.ps1">
<sourcefile name="TestSubFolder/TestScript3.ps1">
<line nr="1" mi="0" ci="1" mb="0" cb="0" />
<counter type="INSTRUCTION" missed="0" covered="1" />
<counter type="LINE" missed="0" covered="1" />
Expand Down Expand Up @@ -614,80 +614,83 @@ InPesterModuleScope {
}
}

# Describe 'Path resolution for test files' {
# BeforeAll {
# $root = (Get-PSDrive TestDrive).Root

# $null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.ps1) -ItemType File -ErrorAction SilentlyContinue

# $null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.tests.ps1) -ItemType File -ErrorAction SilentlyContinue

# $null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript2.tests.ps1) -ItemType File -ErrorAction SilentlyContinue
# }

# Context 'Using Path-input (auto-detect)' {
# It 'Excludes test files by default when using wildcard path' {
# $coverageInfo = Get-CoverageInfoFromUserInput "$(Join-Path -Path $root -ChildPath *)"

# $PesterTests = @($coverageInfo |
# Select-Object -ExpandProperty Path |
# Where-Object { $_ -match '\.tests.ps1$' })

# $PesterTests | Should -BeNullOrEmpty
# }

# It 'Includes test files when specified in wildcard path' {
# $coverageInfo = Get-CoverageInfoFromUserInput "$(Join-Path -Path $root -ChildPath *.tests.ps1)"

# $PesterTests = @($coverageInfo |
# Select-Object -ExpandProperty Path |
# Where-Object { $_ -match '\.tests.ps1$' })

# $PesterTests.Count | Should -Be 2
# $PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.tests.ps1)
# $PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript2.tests.ps1)
# }

# It 'Includes test file when targeted directly using filepath' {
# $path = Join-Path -Path $root -ChildPath TestScript.tests.ps1

# $coverageInfo = Get-CoverageInfoFromUserInput $path

# $PesterTests = $coverageInfo | Select-Object -ExpandProperty Path

# $PesterTests | Should -Be $path
# }

# }

# Context 'Using object-input' {
# It 'Excludes test files when IncludeTests is not specified' {
# $coverageInfo = Get-CoverageInfoFromUserInput @{ Path = "$(Join-Path -Path $root -ChildPath TestScript.tests.ps1)" }

# $PesterTests = $coverageInfo | Select-Object -ExpandProperty Path

# $PesterTests | Should -BeNullOrEmpty
# }

# It 'Excludes test files when IncludeTests is false' {
# $coverageInfo = Get-CoverageInfoFromUserInput @{ Path = "$(Join-Path -Path $root -ChildPath TestScript.tests.ps1)"; IncludeTests = $false }

# $PesterTests = $coverageInfo | Select-Object -ExpandProperty Path

# $PesterTests | Should -BeNullOrEmpty
# }

# It 'Includes test files when IncludeTests is true' {
# $path = Join-Path -Path $root -ChildPath TestScript.tests.ps1

# $coverageInfo = Get-CoverageInfoFromUserInput @{ Path = $path; IncludeTests = $true }

# $PesterTests = $coverageInfo | Select-Object -ExpandProperty Path

# $PesterTests | Should -Be $path
# }
# }
# }
Describe 'Path resolution for test files' {
BeforeAll {
$root = (Get-PSDrive TestDrive).Root
$rootSubFolder = Join-Path -Path $root -ChildPath TestSubFolder

$null = New-Item -Path $rootSubFolder -ItemType Directory -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.psm1) -ItemType File -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.ps1) -ItemType File -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.tests.ps1) -ItemType File -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript2.tests.ps1) -ItemType File -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $rootSubFolder -ChildPath TestScript3.ps1) -Force -ItemType File -ErrorAction SilentlyContinue
$null = New-Item -Path $(Join-Path -Path $rootSubFolder -ChildPath TestScript3.tests.ps1) -Force -ItemType File -ErrorAction SilentlyContinue
}
Context 'Using Path-input (auto-detect)' {
It 'Includes script files by default when using wildcard path' {
$coverageInfo = Get-CoverageInfoFromUserInput "$(Join-Path -Path $root -ChildPath *)"
$PesterTests = @($coverageInfo |
Select-Object -ExpandProperty Path |
Where-Object { $_ -notmatch '\.tests.ps1$' })
$PesterTests.Count | Should -Be 3
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.psm1)
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $rootSubFolder -ChildPath TestScript3.ps1)
}
It 'Excludes test files by default when using wildcard path' {
$coverageInfo = Get-CoverageInfoFromUserInput "$(Join-Path -Path $root -ChildPath *)"
$PesterTests = @($coverageInfo |
Select-Object -ExpandProperty Path |
Where-Object { $_ -match '\.tests.ps1$' })
$PesterTests | Should -BeNullOrEmpty
}
It 'Includes test files when specified in wildcard path' {
$coverageInfo = Get-CoverageInfoFromUserInput "$(Join-Path -Path $root -ChildPath *.tests.ps1)"
$PesterTests = @($coverageInfo |
Select-Object -ExpandProperty Path |
Where-Object { $_ -match '\.tests.ps1$' })
$PesterTests.Count | Should -Be 2
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.tests.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript2.tests.ps1)
}
It 'Includes test file when targeted directly using filepath' {
$path = Join-Path -Path $root -ChildPath TestScript.tests.ps1
$coverageInfo = Get-CoverageInfoFromUserInput $path
$PesterTests = $coverageInfo | Select-Object -ExpandProperty Path
$PesterTests | Should -Be $path
}
}
Context 'Using object-input' {
It 'Excludes test files when IncludeTests is not specified' {
$coverageInfo = Get-CoverageInfoFromUserInput @{ Path = "$(Join-Path -Path $root -ChildPath TestScript.tests.ps1)" }
$PesterTests = $coverageInfo | Select-Object -ExpandProperty Path
$PesterTests | Should -BeNullOrEmpty
}
It 'Excludes test files when IncludeTests is false' {
$coverageInfo = Get-CoverageInfoFromUserInput @{ Path = "$(Join-Path -Path $root -ChildPath TestScript.tests.ps1)"; IncludeTests = $false }
$PesterTests = $coverageInfo | Select-Object -ExpandProperty Path
$PesterTests | Should -BeNullOrEmpty
}
It 'Includes test files when IncludeTests is true' {
$path = Join-Path -Path $root -ChildPath TestScript.tests.ps1
$coverageInfo = Get-CoverageInfoFromUserInput @{ Path = $path; IncludeTests = $true }
$PesterTests = $coverageInfo | Select-Object -ExpandProperty Path
$PesterTests | Should -Be $path
}
It 'Includes test files when IncludeTests is true and using wildcard path' {
$coverageInfo = Get-CoverageInfoFromUserInput @{ Path = "$(Join-Path -Path $root -ChildPath *)"; IncludeTests = $true }
$PesterTests = $coverageInfo | Select-Object -ExpandProperty Path
$PesterTests.Count | Should -Be 6
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.psm1)
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript.tests.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $root -ChildPath TestScript2.tests.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $rootSubFolder -ChildPath TestScript3.ps1)
$PesterTests | Should -Contain $(Join-Path -Path $rootSubFolder -ChildPath TestScript3.tests.ps1)
}
}
}

# Describe 'Stripping common parent paths' {

Expand Down

0 comments on commit 5234884

Please sign in to comment.