diff --git a/src/Pester.RSpec.ps1 b/src/Pester.RSpec.ps1 index e53f56a9c..c556711b1 100644 --- a/src/Pester.RSpec.ps1 +++ b/src/Pester.RSpec.ps1 @@ -118,15 +118,41 @@ function Filter-Excluded ($Files, $ExcludePath) { return @($Files) } + # Directory exclusions are compared as path prefixes below. Match them + # case-insensitively on Windows, but case-sensitively on Linux and macOS + # where the filesystem is case-sensitive. + $pathComparison = if ('Windows' -eq (GetPesterOs)) { + [System.StringComparison]::OrdinalIgnoreCase + } + else { + [System.StringComparison]::Ordinal + } + + # normalize backslashes for cross-platform ease of use + $exclusions = @($ExcludePath) -replace "/", "\" + foreach ($file in @($Files)) { # normalize backslashes for cross-platform ease of use $p = $file.FullName -replace "/", "\" $excluded = $false - foreach ($exclusion in (@($ExcludePath) -replace "/", "\")) { + foreach ($exclusion in $exclusions) { + # Wildcard patterns and exact file paths keep the original -like behavior. if ($p -like $exclusion) { $excluded = $true - continue + break + } + + # A directory exclusion (e.g. C:\proj\excluded) should exclude every file + # underneath it, see https://github.com/pester/Pester/issues/1575. Trailing + # separators are trimmed so both 'C:\proj\excluded' and 'C:\proj\excluded\' + # behave the same. Wildcard patterns are already handled by -like above. + if (-not [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($exclusion)) { + $prefix = $exclusion.TrimEnd("\") + "\" + if ($p.StartsWith($prefix, $pathComparison)) { + $excluded = $true + break + } } } diff --git a/tst/Pester.RSpec.ts.ps1 b/tst/Pester.RSpec.ts.ps1 index 0b165a9d5..af6342b50 100644 --- a/tst/Pester.RSpec.ts.ps1 +++ b/tst/Pester.RSpec.ts.ps1 @@ -280,6 +280,42 @@ i -PassThru:$PassThru { } } + b "Excluding directories from a run" { + try { + $path = $pwd + $c = 'Describe "d1" { It "i1" { $true } }' + $root = Join-Path ([IO.Path]::GetTempPath()) "excludepath-dir-$([Guid]::NewGuid().Guid)" + $includedDir = Join-Path $root "included" + $excludedDir = Join-Path $root "excluded" + New-Item -ItemType Directory -Path $includedDir -Force | Out-Null + New-Item -ItemType Directory -Path $excludedDir -Force | Out-Null + + $includedFile = Join-Path $includedDir "included.Tests.ps1" + $excludedFile = Join-Path $excludedDir "excluded.Tests.ps1" + $c | Set-Content $includedFile + $c | Set-Content $excludedFile + + t "Excluding a directory excludes the tests underneath it but keeps sibling tests" { + $result = Invoke-Pester -Path $root -ExcludePath $excludedDir -PassThru + + $result.Containers.Count | Verify-Equal 1 + $result.Containers[0].Item.FullName | Verify-Equal $includedFile + } + + t "Excluding a directory with a trailing separator excludes the tests underneath it" { + $excludedWithSeparator = $excludedDir + [System.IO.Path]::DirectorySeparatorChar + $result = Invoke-Pester -Path $root -ExcludePath $excludedWithSeparator -PassThru + + $result.Containers.Count | Verify-Equal 1 + $result.Containers[0].Item.FullName | Verify-Equal $includedFile + } + } + finally { + cd $path + Remove-Item $root -Recurse -Force -Confirm:$false -ErrorAction Stop + } + } + b "Terminating and non-terminating Should" { t "Non-terminating assertion fails the test after running to completion" { $sb = {