From 99760df11d01b9549f620b7c855429279102751f Mon Sep 17 00:00:00 2001 From: Frode Flaten <3436158+fflaten@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:04:49 +0200 Subject: [PATCH] Exclude duplicate files for Run.Path and CodeCoverage.Path (#2535) * Return only unique files in Find-File * Remove redundant loop in New-PesterContainer * Refactor Get-CodeCoverageFilePaths and remove duplicates * Improve perf in New-PesterContainer --- src/Pester.RSpec.ps1 | 31 +++++++++++++------------------ src/functions/Coverage.ps1 | 37 +++++++++---------------------------- tst/Pester.Tests.ps1 | 5 +++++ 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/src/Pester.RSpec.ps1 b/src/Pester.RSpec.ps1 index 8e42aa229..633557cf5 100644 --- a/src/Pester.RSpec.ps1 +++ b/src/Pester.RSpec.ps1 @@ -8,9 +8,7 @@ [string] $Extension ) - - $files = - foreach ($p in $Path) { + $files = foreach ($p in $Path) { if ([String]::IsNullOrWhiteSpace($p)) { continue } @@ -66,11 +64,13 @@ } } - Filter-Excluded -Files $files -ExcludePath $ExcludePath | & $SafeCommands['Where-Object'] { $_ } + # Deduplicate files if overlapping -Path values + $uniquePaths = [System.Collections.Generic.HashSet[string]]::new(@($files).Count) + $uniqueFiles = foreach ($f in $files) { if ($uniquePaths.Add($f.FullName)) { $f } } + Filter-Excluded -Files $uniqueFiles -ExcludePath $ExcludePath | & $SafeCommands['Where-Object'] { $_ } } function Filter-Excluded ($Files, $ExcludePath) { - if ($null -eq $ExcludePath -or @($ExcludePath).Length -eq 0) { return @($Files) } @@ -81,12 +81,9 @@ function Filter-Excluded ($Files, $ExcludePath) { $excluded = $false foreach ($exclusion in (@($ExcludePath) -replace "/", "\")) { - if ($excluded) { - continue - } - if ($p -like $exclusion) { $excluded = $true + continue } } @@ -571,15 +568,13 @@ function New-PesterContainer { } if ("Path" -eq $kind) { - # the @() is significant here, it will make it iterate even if there are no data - # which allows files without data to run - foreach ($d in @($dt)) { - foreach ($p in $Path) { - # resolve the path we are given in the same way we would resolve -Path on Invoke-Pester - $files = @(Find-File -Path $p -ExcludePath $PesterPreference.Run.ExcludePath.Value -Extension $PesterPreference.Run.TestExtension.Value) - foreach ($file in $files) { - New-BlockContainerObject -File $file -Data $d - } + # resolve the path we are given in the same way we would resolve -Path on Invoke-Pester + $files = @(Find-File -Path $Path -ExcludePath $PesterPreference.Run.ExcludePath.Value -Extension $PesterPreference.Run.TestExtension.Value) + foreach ($file in $files) { + # the @() is significant here, it will make it iterate even if there are no data + # which allows files without data to run + foreach ($d in @($dt)) { + New-BlockContainerObject -File $file -Data $d } } } diff --git a/src/functions/Coverage.ps1 b/src/functions/Coverage.ps1 index 7b99688ed..2eb750894 100644 --- a/src/functions/Coverage.ps1 +++ b/src/functions/Coverage.ps1 @@ -211,7 +211,7 @@ function Resolve-CoverageInfo { $filePaths = Get-CodeCoverageFilePaths -Paths $resolvedPaths -IncludeTests $includeTests -RecursePaths $recursePaths - $params = @{ + $commonParams = @{ StartLine = $UnresolvedCoverageInfo.StartLine EndLine = $UnresolvedCoverageInfo.EndLine Class = $UnresolvedCoverageInfo.Class @@ -219,46 +219,27 @@ function Resolve-CoverageInfo { } foreach ($filePath in $filePaths) { - $params['Path'] = $filePath - New-CoverageInfo @params + New-CoverageInfo @commonParams -Path $filePath } } function Get-CodeCoverageFilePaths { param ( - [object]$Paths, + [string[]]$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 documentation 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." + [string[]] $filteredFiles = @(foreach ($file in (& $SafeCommands['Get-ChildItem'] -LiteralPath $Paths -File -Recurse:$RecursePaths)) { + if (('.ps1', '.psm1') -contains $file.Extension -and ($IncludeTests -or $file.Name -notlike $testsPattern)) { + $file.FullName } - } - - return $filePaths + }) + $uniqueFiles = [System.Collections.Generic.HashSet[string]]::new($filteredFiles) + return $uniqueFiles } function Get-CoverageBreakpoints { diff --git a/tst/Pester.Tests.ps1 b/tst/Pester.Tests.ps1 index 068748b94..3510cac91 100644 --- a/tst/Pester.Tests.ps1 +++ b/tst/Pester.Tests.ps1 @@ -244,6 +244,11 @@ InPesterModuleScope { ($paths -contains (Join-Path $testDrive "SomeOtherFile.Tests.ps1")) | Should -Be $true } + It 'Deduplicates filepaths when the provided paths overlaps' { + $result = @(Find-File 'TestDrive:\*.ps1','TestDrive:\*.ps1' -Extension '.Tests.ps1') + $result.Count | Should -Be 2 + } + # It 'Assigns empty array and hashtable to the Arguments and Parameters properties when none are specified by the caller' { # $result = @(Find-File 'TestDrive:\SomeFile.ps1' -Extension ".Tests.ps1")