diff --git a/.gitattributes b/.gitattributes index 69bc95b2d..84f736dfc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,12 @@ # Auto detect text files and perform LF normalization -* text=auto -*.ps1 text \ No newline at end of file +* text=auto eol=crlf +*.ps1 text +*.psm1 text +*.psd1 text +*.ps1xml text +*.md text +*.feature text +*.xsd text +*.dtd text +*.dll binary +*.PNG binary diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..18e3c6647 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ + +language: generic + +matrix: + include: + - os: osx + osx_image: xcode9.1 + before_install: + - brew update + - brew tap caskroom/cask + - brew cask install powershell + - os: linux + dist: trusty + # VM-based builds turned out to be faster + sudo: required + addons: + apt: + sources: + - sourceline: deb [arch=amd64] https://packages.microsoft.com/ubuntu/14.04/prod trusty main + key_url: https://packages.microsoft.com/keys/microsoft.asc + packages: + - powershell + +script: + - pwsh -c 'Import-Module ./Pester.psd1; Invoke-Pester -ExcludeTag VersionChecks -EnableExit' diff --git a/CHANGELOG.md b/CHANGELOG.md index fb0a1e223..98f6f2fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased - Add-AssertionOperator can be called multiple times for identical parameters without errors. [GH-893] + - Support for PowerShell Core on Windows, Linux and macOS added [GH-639] ## 4.0.8 (September 15, 2017) - Add Assert-VerifiableMocks that throws [GH-881] diff --git a/Functions/Assertions/Be.Tests.ps1 b/Functions/Assertions/Be.Tests.ps1 index 57699129a..da836743b 100644 --- a/Functions/Assertions/Be.Tests.ps1 +++ b/Functions/Assertions/Be.Tests.ps1 @@ -91,13 +91,17 @@ InModuleScope Pester { $doc | Should be $doc } - It 'throws exception when self-imposed recursion limit is reached' { - $a1 = @(0,1) - $a2 = @($a1,2) - $a1[0] = $a2 + # The test excluded on macOS due to issue https://github.com/PowerShell/PowerShell/issues/4268 + If ((GetPesterOS) -ne 'macOS') { + It 'throws exception when self-imposed recursion limit is reached' { + $a1 = @(0,1) + $a2 = @($a1,2) + $a1[0] = $a2 - { $a1 | Should be $a2 } | Should throw 'recursion depth limit' + { $a1 | Should be $a2 } | Should throw 'recursion depth limit' + } } + } Describe "PesterBeFailureMessage" { diff --git a/Functions/Assertions/FileContentMatch.Tests.ps1 b/Functions/Assertions/FileContentMatch.Tests.ps1 index e3237aecc..f4f175646 100644 --- a/Functions/Assertions/FileContentMatch.Tests.ps1 +++ b/Functions/Assertions/FileContentMatch.Tests.ps1 @@ -3,7 +3,7 @@ InModuleScope Pester { Describe "PesterFileContentMatch" { Context "when testing file contents" { - Setup -File "test.txt" "this is line 1`nrush is awesome`nAnd this is Unicode: ☺" + Setup -File "test.txt" "this is line 1$([System.Environment]::NewLine)rush is awesome$([System.Environment]::NewLine)And this is Unicode: ☺" It "returns true if the file contains the specified content" { "$TestDrive\test.txt" | Should FileContentMatch rush diff --git a/Functions/Assertions/FileContentMatchExactly.Tests.ps1 b/Functions/Assertions/FileContentMatchExactly.Tests.ps1 index 6db422abf..1ebb9a851 100644 --- a/Functions/Assertions/FileContentMatchExactly.Tests.ps1 +++ b/Functions/Assertions/FileContentMatchExactly.Tests.ps1 @@ -3,7 +3,7 @@ InModuleScope Pester { Describe "PesterFileContentMatchExactly" { Context "when testing file contents" { - Setup -File "test.txt" "this is line 1`nPester is awesome`nAnd this is Unicode: ☺" + Setup -File "test.txt" "this is line 1$([System.Environment]::NewLine)Pester is awesome$([System.Environment]::NewLine)And this is Unicode: ☺" It "returns true if the file contains the specified content exactly" { "$TestDrive\test.txt" | Should FileContentMatchExactly Pester "$TestDrive\test.txt" | Should -FileContentMatchExactly Pester diff --git a/Functions/Assertions/FileContentMatchMultiline.Tests.ps1 b/Functions/Assertions/FileContentMatchMultiline.Tests.ps1 index 5467ee3c0..373ebcaec 100644 --- a/Functions/Assertions/FileContentMatchMultiline.Tests.ps1 +++ b/Functions/Assertions/FileContentMatchMultiline.Tests.ps1 @@ -3,13 +3,13 @@ Set-StrictMode -Version Latest InModuleScope Pester { Describe "PesterFileContentMatchMultiline" { Context "when testing file contents" { - Setup -File "test.txt" "this is line 1`nthis is line 2`nPester is awesome" + Setup -File "test.txt" "this is line 1$([System.Environment]::NewLine)this is line 2$([System.Environment]::NewLine)Pester is awesome" It "returns true if the file matches the specified content on one line" { "$TestDrive\test.txt" | Should FileContentMatchMultiline "Pester" } It "returns true if the file matches the specified content across multiple lines" { - "$TestDrive\test.txt" | Should FileContentMatchMultiline "line 2`nPester" + "$TestDrive\test.txt" | Should FileContentMatchMultiline "line 2$([System.Environment]::NewLine)Pester" } It "returns false if the file does not contain the specified content" { diff --git a/Functions/Assertions/PesterThrow.Tests.ps1 b/Functions/Assertions/PesterThrow.Tests.ps1 index ed1168acf..81416a6af 100644 --- a/Functions/Assertions/PesterThrow.Tests.ps1 +++ b/Functions/Assertions/PesterThrow.Tests.ps1 @@ -211,8 +211,8 @@ InModuleScope Pester { PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $unexpectedErrorMessage $expectedErrorMessage - $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { @@ -237,8 +237,8 @@ InModuleScope Pester { PesterThrow { & $testScriptPath } -ErrorId $expectedErrorId > $null $result = PesterThrowFailureMessage $null -ExpectedErrorId $expectedErrorId - $result | Should Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result | Should -Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should -Match "^Expected: the expression to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual error id is the same as the expected error id' { @@ -276,8 +276,8 @@ InModuleScope Pester { PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId - $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns false if the actual message is not the same as the expected message when the actual error id and expected error id match' { @@ -296,8 +296,8 @@ InModuleScope Pester { PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId - $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$unexpectedErrorMessage} and error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns false if the actual error id is not the same as the expected error id when the actual message and expected message match' { @@ -316,8 +316,8 @@ InModuleScope Pester { PesterThrow { & $testScriptPath } $expectedErrorMessage > $null $result = PesterThrowFailureMessage $null $expectedErrorMessage $expectedErrorId - $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$unexpectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } } @@ -331,15 +331,15 @@ InModuleScope Pester { Set-Content -Path $testScriptPath -Value "throw '$unexpectedErrorMessage'" $result = PesterThrow { & $testScriptPath } $expectedErrorMessage - $result.FailureMessage | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result.FailureMessage | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should -Match "^Expected: the expression to throw an exception with message {$expectedErrorMessage}, an exception was raised, message was {$unexpectedErrorMessage}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns true if the actual message is the same as the expected message' { Set-Content -Path $testScriptPath -Value "throw 'error message'" $result = PesterThrow { & $testScriptPath } -Negate - $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception. Message was {error message}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception. Message was {error message}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception. Message was {error message}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception. Message was {error message}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns false if the actual error id is the same as the expected error id' { @@ -355,8 +355,8 @@ InModuleScope Pester { " $result = PesterThrow { & $testScriptPath } -ErrorId $expectedErrorId -Negate - $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with error id {$expectedErrorId}, an exception was raised, error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } It 'returns false if the actual message or actual error id is the same as the expected message or expected error id' { @@ -373,8 +373,8 @@ InModuleScope Pester { " $result = PesterThrow { & $testScriptPath } $expectedErrorMessage -ErrorId $expectedErrorId -Negate - $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" - $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}`n from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" + $result.FailureMessage | Should -Match "^Expected: the expression not to throw an exception with message {$expectedErrorMessage} and error id {$expectedErrorId}, an exception was raised, message was {$expectedErrorMessage} and error id was {$expectedErrorId}$([System.Environment]::NewLine) from $([RegEx]::Escape($testScriptPath)):\d+ char:\d+" } } } diff --git a/Functions/Assertions/PesterThrow.ps1 b/Functions/Assertions/PesterThrow.ps1 index 0eef273f2..83a658937 100644 --- a/Functions/Assertions/PesterThrow.ps1 +++ b/Functions/Assertions/PesterThrow.ps1 @@ -79,11 +79,11 @@ function PesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedErro { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage } { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId } } - $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n {3}" -f + $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}$([System.Environment]::NewLine) {3}" -f ($Expected -join ' and '), @{$true="";$false="not "}[$ActualExceptionWasThrown], ($Actual -join ' and '), - ($ActualExceptionLine -replace "`n","`n ") + ($ActualExceptionLine -replace "`n","$([System.Environment]::NewLine) ") )) } @@ -107,16 +107,16 @@ function NotPesterThrowFailureMessage($ActualValue, $ExpectedMessage, $ExpectedE { $ExpectedMessage } { 'message was {{{0}}}' -f $ActualExceptionMessage } { $ExpectedErrorId } { 'error id was {{{0}}}' -f $ActualErrorId } } - $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}`n {3}" -f + $null = $StringBuilder.Append(("{0}, an exception was {1}raised, {2}$([System.Environment]::NewLine) {3}" -f ($Expected -join ' and '), (@{$true="";$false="not "}[$ActualExceptionWasThrown]), ($Actual -join ' and '), - ($ActualExceptionLine -replace "`n","`n ") + ($ActualExceptionLine -replace "$([System.Environment]::NewLine)","$([System.Environment]::NewLine) ") )) } else { - $null = $StringBuilder.Append((". Message was {{{0}}}`n {1}" -f $ActualExceptionMessage, ($ActualExceptionLine -replace "`n","`n "))) + $null = $StringBuilder.Append((". Message was {{{0}}}$([System.Environment]::NewLine) {1}" -f $ActualExceptionMessage, ($ActualExceptionLine -replace "$([System.Environment]::NewLine)","$([System.Environment]::NewLine) "))) } return $StringBuilder.ToString() diff --git a/Functions/Assertions/Set-TestInconclusive.ps1 b/Functions/Assertions/Set-TestInconclusive.ps1 index a6ab8cfa1..8f0c4d3df 100644 --- a/Functions/Assertions/Set-TestInconclusive.ps1 +++ b/Functions/Assertions/Set-TestInconclusive.ps1 @@ -57,7 +57,7 @@ function Set-TestInconclusive { ) Assert-DescribeInProgress -CommandName Set-TestInconclusive - $lineText = $MyInvocation.Line.TrimEnd("`n") + $lineText = $MyInvocation.Line.TrimEnd($([System.Environment]::NewLine)) $line = $MyInvocation.ScriptLineNumber $file = $MyInvocation.ScriptName diff --git a/Functions/Assertions/Should.ps1 b/Functions/Assertions/Should.ps1 index c3982c2ac..f2ecf3275 100644 --- a/Functions/Assertions/Should.ps1 +++ b/Functions/Assertions/Should.ps1 @@ -119,7 +119,7 @@ function Should { end { $lineNumber = $MyInvocation.ScriptLineNumber - $lineText = $MyInvocation.Line.TrimEnd("`n") + $lineText = $MyInvocation.Line.TrimEnd("$([System.Environment]::NewLine)") $file = $MyInvocation.ScriptName if ($PSCmdlet.ParameterSetName -eq 'Legacy') diff --git a/Functions/Coverage.Tests.ps1 b/Functions/Coverage.Tests.ps1 index a0a0d029b..4f9a665b2 100644 --- a/Functions/Coverage.Tests.ps1 +++ b/Functions/Coverage.Tests.ps1 @@ -1,12 +1,14 @@ +Set-StrictMode -Version Latest + if ($PSVersionTable.PSVersion.Major -le 2) { return } InModuleScope Pester { Describe 'Code Coverage Analysis' { $root = (Get-PSDrive TestDrive).Root - $null = New-Item -Path $root\TestScript.ps1 -ItemType File -ErrorAction SilentlyContinue + $null = New-Item -Path $(Join-Path -Path $root -ChildPath TestScript.ps1) -ItemType File -ErrorAction SilentlyContinue - Set-Content -Path $root\TestScript.ps1 -Value @' + Set-Content -Path $(Join-Path -Path $root -ChildPath TestScript.ps1) -Value @' function FunctionOne { function NestedFunction @@ -35,13 +37,13 @@ InModuleScope Pester { $testState = New-PesterState -Path $root # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands - Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState + Enter-CoverageAnalysis -CodeCoverage "$(Join-Path -Path $root -ChildPath TestScript.ps1)", "$(Join-Path -Path $root -ChildPath TestScript.ps1)" -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 7 } - $null = & "$root\TestScript.ps1" + $null = & "$(Join-Path -Path $root -ChildPath TestScript.ps1)" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { @@ -77,7 +79,7 @@ InModuleScope Pester { $jaCoCoReportXml = $jaCoCoReportXml -replace 'Pester \([^\)]*','Pester (date' $jaCoCoReportXml = $jaCoCoReportXml -replace 'start="[0-9]*"','start=""' $jaCoCoReportXml = $jaCoCoReportXml -replace 'dump="[0-9]*"','dump=""' - $jaCoCoReportXml = $jaCoCoReportXml -replace '\n','' + $jaCoCoReportXml = $jaCoCoReportXml -replace "$([System.Environment]::NewLine)",'' $jaCoCoReportXml | should be '' } Exit-CoverageAnalysis -PesterState $testState @@ -86,13 +88,13 @@ InModuleScope Pester { Context 'Single function with missed commands' { $testState = New-PesterState -Path $root - Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionTwo'} -PesterState $testState + Enter-CoverageAnalysis -CodeCoverage @{Path = "$(Join-Path -Path $root -ChildPath TestScript.ps1)"; Function = 'FunctionTwo'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 1 } - $null = & "$root\TestScript.ps1" + $null = & "$(Join-Path -Path $root -ChildPath TestScript.ps1)" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { @@ -121,13 +123,13 @@ InModuleScope Pester { Context 'Single function with no missed commands' { $testState = New-PesterState -Path $root - Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; Function = 'FunctionOne'} -PesterState $testState + Enter-CoverageAnalysis -CodeCoverage @{Path = "$(Join-Path -Path $root -ChildPath TestScript.ps1)"; Function = 'FunctionOne'} -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 5 } - $null = & "$root\TestScript.ps1" + $null = & "$(Join-Path -Path $root -ChildPath TestScript.ps1)" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { @@ -156,13 +158,13 @@ InModuleScope Pester { Context 'Range of lines' { $testState = New-PesterState -Path $root - Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\TestScript.ps1"; StartLine = 11; EndLine = 12 } -PesterState $testState + Enter-CoverageAnalysis -CodeCoverage @{Path = "$(Join-Path -Path $root -ChildPath TestScript.ps1)"; StartLine = 11; EndLine = 12 } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 2 } - $null = & "$root\TestScript.ps1" + $null = & "$(Join-Path -Path $root -ChildPath TestScript.ps1)" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { @@ -191,13 +193,13 @@ InModuleScope Pester { Context 'Wildcard resolution' { $testState = New-PesterState -Path $root - Enter-CoverageAnalysis -CodeCoverage @{Path = "$root\*.ps1"; Function = '*' } -PesterState $testState + Enter-CoverageAnalysis -CodeCoverage @{Path = "$(Join-Path -Path $root -ChildPath *.ps1)"; Function = '*' } -PesterState $testState It 'Has the proper number of breakpoints defined' { $testState.CommandCoverage.Count | Should Be 6 } - $null = & "$root\TestScript.ps1" + $null = & "$(Join-Path -Path $root -ChildPath TestScript.ps1)" $coverageReport = Get-CoverageReport -PesterState $testState It 'Reports the proper number of executed commands' { @@ -233,14 +235,35 @@ InModuleScope Pester { } Describe 'Stripping common parent paths' { - $paths = @( - Normalize-Path 'C:\Common\Folder\UniqueSubfolder1/File.ps1' - Normalize-Path 'C:\Common\Folder\UniqueSubfolder2/File2.ps1' - Normalize-Path 'C:\Common\Folder\UniqueSubfolder3/File3.ps1' - ) + + If ( (& $SafeCommands['Get-Variable'] -Name IsLinux -Scope Global -ErrorAction SilentlyContinue) -or + (& $SafeCommands['Get-Variable'] -Name IsMacOS -Scope Global -ErrorAction SilentlyContinue)) { + + $paths = @( + Normalize-Path '/usr/lib/Common\Folder\UniqueSubfolder1/File.ps1' + Normalize-Path '/usr/lib/Common\Folder\UniqueSubfolder2/File2.ps1' + Normalize-Path '/usr/lib/Common\Folder\UniqueSubfolder3/File3.ps1' + + $expectedCommonPath = Normalize-Path '/usr/lib/Common/Folder' + + ) + + } + Else { + + $paths = @( + Normalize-Path 'C:\Common\Folder\UniqueSubfolder1/File.ps1' + Normalize-Path 'C:\Common\Folder\UniqueSubfolder2/File2.ps1' + Normalize-Path 'C:\Common\Folder\UniqueSubfolder3/File3.ps1' + + $expectedCommonPath = Normalize-Path 'C:\Common/Folder' + + ) + + } $commonPath = Get-CommonParentPath -Path $paths - $expectedCommonPath = Normalize-Path 'C:\Common/Folder' + It 'Identifies the correct parent path' { $commonPath | Should Be $expectedCommonPath @@ -254,8 +277,9 @@ InModuleScope Pester { } } - if ((Get-Module -ListAvailable PSDesiredStateConfiguration) -and $PSVersionTable.PSVersion.Major -ge 4) - { + #Workaround for Linux and MacOS - they don't have DSC by default installed with PowerShell - disable tests on these platforms + if ((Get-Module -ListAvailable PSDesiredStateConfiguration) -and $PSVersionTable.PSVersion.Major -ge 4 -and ((GetPesterOS) -eq 'Windows')) { + Describe 'Analyzing coverage of a DSC configuration' { $root = (Get-PSDrive TestDrive).Root @@ -289,7 +313,6 @@ InModuleScope Pester { $doesnotexecute = $true # Triggers breakpoint '@ - $testState = New-PesterState -Path $root Enter-CoverageAnalysis -CodeCoverage "$root\TestScriptWithConfiguration.ps1" -PesterState $testState diff --git a/Functions/Coverage.ps1 b/Functions/Coverage.ps1 index ff3e8ada3..2dc7c2371 100644 --- a/Functions/Coverage.ps1 +++ b/Functions/Coverage.ps1 @@ -589,16 +589,17 @@ function Get-JaCoCoReportXml { } $allCommands = $CoverageReport.MissedCommands + $CoverageReport.HitCommands - [long]$totalFunctions = ($allCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -uniq ).Count - [long]$hitFunctions = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -uniq ).Count + [long]$totalFunctions = ($allCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -Unique ).Count + [long]$hitFunctions = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Function} | Select-Object -Unique ).Count [long]$missedFunctions = $totalFunctions - $hitFunctions - [long]$totalLines = ($allCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -uniq ).Count - [long]$hitLines = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -uniq ).Count + [long]$totalLines = ($allCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -Unique ).Count + [long]$hitLines = ($CoverageReport.HitCommands | ForEach-Object {$_.File+$_.Line} | Select-Object -Unique ).Count [long]$missedLines = $totalLines - $hitLines [long]$totalFiles = $CoverageReport.NumberOfFilesAnalyzed - [long]$hitFiles = ($CoverageReport.HitCommands | ForEach-Object {$_.File} | Select-Object -uniq ).Count + + [long]$hitFiles = $(Measure-Object -InputObject $($CoverageReport.HitCommands | ForEach-Object {$_.File} | Select-Object -Unique )).Count [long]$missedFiles = $totalFiles - $hitFiles $now = & $SafeCommands['Get-Date'] @@ -607,13 +608,13 @@ function Get-JaCoCoReportXml { [long]$startTime = [math]::Floor($endTime - $PesterState.Time.TotalSeconds*1000) # the JaCoCo xml format without the doctype, as the XML stuff does not like DTD's. - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" - $jaCoCoReport += "`n" + $jaCoCoReport = "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" + $jaCoCoReport += "$([System.Environment]::NewLine)" $jaCoCoReport += "" [xml] $jaCoCoReportXml = $jaCoCoReport @@ -629,6 +630,6 @@ function Get-JaCoCoReportXml { $jaCoCoReportXml.report.counter[3].missed = $missedFiles.ToString() $jaCoCoReportXml.report.counter[3].covered = $hitFiles.ToString() # There is no pretty way to insert the Doctype, as microsoft has deprecated the DTD stuff. - $jaCoCoReportDocType = "`n" + $jaCoCoReportDocType = "$([System.Environment]::NewLine)" return $jaCocoReportXml.OuterXml.Insert(54, $jaCoCoReportDocType) } diff --git a/Functions/Describe.ps1 b/Functions/Describe.ps1 index 629aca79f..92f34af7b 100644 --- a/Functions/Describe.ps1 +++ b/Functions/Describe.ps1 @@ -179,7 +179,7 @@ function DescribeImpl { } catch { - $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split '\r?\n' | & $SafeCommands['Select-Object'] -First 1 + $firstStackTraceLine = $_.InvocationInfo.PositionMessage.Trim() -split "$([System.Environment]::NewLine)" | & $SafeCommands['Select-Object'] -First 1 $Pester.AddTestResult("Error occurred in $CommandUsed block", "Failed", $null, $_.Exception.Message, $firstStackTraceLine, $null, $null, $_) if ($null -ne $TestOutputBlock) { diff --git a/Functions/Environment.Tests.ps1 b/Functions/Environment.Tests.ps1 index aa7bbb093..b6fa3629c 100644 --- a/Functions/Environment.Tests.ps1 +++ b/Functions/Environment.Tests.ps1 @@ -23,7 +23,7 @@ InModuleScope -ModuleName Pester { Context "Windows with PowerShell 6 and higher" { Mock Get-Variable -ParameterFilter { $Name -eq 'IsWindows' -and $ValueOnly } -MockWith { $true } Mock Get-Variable -ParameterFilter { $Name -eq 'IsLinux' -and $ValueOnly } -MockWith { $false } - Mock Get-Variable -ParameterFilter { $Name -eq 'IsOSX' -and $ValueOnly } -MockWith { $false } + Mock Get-Variable -ParameterFilter { $Name -eq 'IsMacOS' -and $ValueOnly } -MockWith { $false } Mock GetPesterPsVersion { 6 } $os = GetPesterOs @@ -43,7 +43,7 @@ InModuleScope -ModuleName Pester { Context "Linux with PowerShell 6 and higher" { Mock Get-Variable -ParameterFilter { $Name -eq 'IsWindows' -and $ValueOnly } -MockWith { $false } Mock Get-Variable -ParameterFilter { $Name -eq 'IsLinux' -and $ValueOnly } -MockWith { $true } - Mock Get-Variable -ParameterFilter { $Name -eq 'IsOSX' -and $ValueOnly } -MockWith { $false } + Mock Get-Variable -ParameterFilter { $Name -eq 'IsMacOS' -and $ValueOnly } -MockWith { $false } Mock GetPesterPsVersion { 6 } $os = GetPesterOs @@ -56,19 +56,19 @@ InModuleScope -ModuleName Pester { } } - Context "OSx with PowerShell 6 and higher" { + Context "macOS with PowerShell 6 and higher" { Mock Get-Variable -ParameterFilter { $Name -eq 'IsWindows' -and $ValueOnly } -MockWith { $false } Mock Get-Variable -ParameterFilter { $Name -eq 'IsLinux' -and $ValueOnly } -MockWith { $false } - Mock Get-Variable -ParameterFilter { $Name -eq 'IsOSX' -and $ValueOnly } -MockWith { $true } + Mock Get-Variable -ParameterFilter { $Name -eq 'IsMacOS' -and $ValueOnly } -MockWith { $true } Mock GetPesterPsVersion { 6 } $os = GetPesterOs - It "Returns 'OSX' when `$IsOSX is `$true and powershell version is 6 or higher" { - $os | Should -Be 'OSX' + It "Returns 'OSX' when `$IsMacOS is `$true and powershell version is 6 or higher" { + $os | Should -Be 'macOS' } - It "Uses Get-Variable to retreive IsOSX" { - Assert-MockCalled Get-Variable -ParameterFilter { $Name -eq 'IsOSX' -and $ValueOnly } -Exactly 1 + It "Uses Get-Variable to retreive IsMacOS" { + Assert-MockCalled Get-Variable -ParameterFilter { $Name -eq 'IsMacOS' -and $ValueOnly } -Exactly 1 } } } diff --git a/Functions/Environment.ps1 b/Functions/Environment.ps1 index 0f3754f41..786b996f8 100644 --- a/Functions/Environment.ps1 +++ b/Functions/Environment.ps1 @@ -15,9 +15,9 @@ function GetPesterOs { 'Windows' } - elseif (Get-Variable -Name 'IsOSX' -ErrorAction 'SilentlyContinue' -ValueOnly ) + elseif (Get-Variable -Name 'IsMacOS' -ErrorAction 'SilentlyContinue' -ValueOnly ) { - 'OSX' + 'macOS' } elseif (Get-Variable -Name 'IsLinux' -ErrorAction 'SilentlyContinue' -ValueOnly ) { diff --git a/Functions/Gherkin.Tests.ps1 b/Functions/Gherkin.Tests.ps1 index 918871433..0c68526b9 100644 --- a/Functions/Gherkin.Tests.ps1 +++ b/Functions/Gherkin.Tests.ps1 @@ -1,6 +1,7 @@ -if ($PSVersionTable.PSVersion.Major -le 2) { return } - Set-StrictMode -Version Latest + +If (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core') -or ($PSVersionTable.PSVersion.Major -le 2)) { return } + $scriptRoot = Split-Path (Split-Path $MyInvocation.MyCommand.Path) # Calling this in a job so we don't monkey with the active pester state that's already running diff --git a/Functions/Gherkin.ps1 b/Functions/Gherkin.ps1 index 81722d261..8b5c1b8a7 100644 --- a/Functions/Gherkin.ps1 +++ b/Functions/Gherkin.ps1 @@ -1,5 +1,10 @@ -# Work around bug in PowerShell 2 type loading... -Microsoft.PowerShell.Core\Import-Module -Name "${Script:PesterRoot}\lib\Gherkin.dll" +If (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core') -or ($PSVersionTable.PSVersion.Major -le 2)) { + return +} +Else { + # Work around bug in PowerShell 2 type loading... + Microsoft.PowerShell.Core\Import-Module -Name "${Script:PesterRoot}\lib\Gherkin.dll" +} $GherkinSteps = @{} $GherkinHooks = @{ diff --git a/Functions/GherkinHook.Tests.ps1 b/Functions/GherkinHook.Tests.ps1 index 123257782..d714e257a 100644 --- a/Functions/GherkinHook.Tests.ps1 +++ b/Functions/GherkinHook.Tests.ps1 @@ -1,7 +1,7 @@ -if ($PSVersionTable.PSVersion.Major -le 2) { return } - Set-StrictMode -Version Latest +If (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core') -or ($PSVersionTable.PSVersion.Major -le 2)) { return } + Describe 'Testing Gerkin Hook' { It 'Generates a function named "BeforeEachFeature" with mandatory Tags and Script parameters' { $command = &(Get-Module Pester) { Get-Command BeforeEachFeature -Module Pester } diff --git a/Functions/GherkinStep.Tests.ps1 b/Functions/GherkinStep.Tests.ps1 index 32a003378..09133d9ab 100644 --- a/Functions/GherkinStep.Tests.ps1 +++ b/Functions/GherkinStep.Tests.ps1 @@ -1,7 +1,7 @@ -if ($PSVersionTable.PSVersion.Major -le 2) { return } - Set-StrictMode -Version Latest +If (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core') -or ($PSVersionTable.PSVersion.Major -le 2)) { return } + Describe 'Testing Gerkin Step' { It 'Generates a function named "GherkinStep" with mandatory name and test parameters' { $command = &(Get-Module Pester) { Get-Command GherkinStep -Module Pester } diff --git a/Functions/It.ps1 b/Functions/It.ps1 index 9de7d51d4..aa501b375 100644 --- a/Functions/It.ps1 +++ b/Functions/It.ps1 @@ -280,7 +280,7 @@ function Invoke-Test $pester.LeaveTest() } - $result = ConvertTo-PesterResult -ErrorRecord $errorRecord + $result = ConvertTo-PesterResult -Name $Name -ErrorRecord $errorRecord $orderedParameters = Get-OrderedParameterDictionary -ScriptBlock $ScriptBlock -Dictionary $Parameters $Pester.AddTestResult( $result.name, $result.Result, $null, $result.FailureMessage, $result.StackTrace, $ParameterizedSuiteName, $orderedParameters, $result.ErrorRecord ) & $SafeCommands['Write-Progress'] -Activity "Running test '$Name'" -Completed -Status Processing diff --git a/Functions/Mock.Tests.ps1 b/Functions/Mock.Tests.ps1 index b5695118c..382385933 100644 --- a/Functions/Mock.Tests.ps1 +++ b/Functions/Mock.Tests.ps1 @@ -105,6 +105,9 @@ Describe "When calling Mock on existing cmdlet" { } Describe 'When calling Mock on an alias' { + + (Get-Item Env:PATH).Value + $originalPath = $env:path try @@ -227,13 +230,30 @@ Describe 'When calling Mock on an external script' { Remove-Item $ps1File -Force -ErrorAction SilentlyContinue } -Describe 'When calling Mock on an application command' { - Mock schtasks.exe {return 'I am not schtasks.exe'} +InModuleScope -ModuleName Pester { + Describe 'When calling Mock on an application command' { - $result = schtasks.exe + If ((GetPesterOs) -ne 'Windows') { - It 'Should Invoke the mocked script' { - $result | Should Be 'I am not schtasks.exe' + Mock visudo {return 'I am not visudo'} + + $result = visudo + + It 'Should Invoke the mocked script' { + $result | Should Be 'I am not visudo' + } + + } + Else { + + Mock schtasks.exe {return 'I am not schtasks.exe'} + + $result = schtasks.exe + + It 'Should Invoke the mocked script' { + $result | Should Be 'I am not schtasks.exe' + } + } } } @@ -581,7 +601,7 @@ Describe "When Creating a Verifiable Mock that is not called" { } It "Should throw" { - $result.Exception.Message | Should Be "`r`n Expected FunctionUnderTest to be called with `$param1 -eq `"one`"" + $result.Exception.Message | Should Be "$([System.Environment]::NewLine) Expected FunctionUnderTest to be called with `$param1 -eq `"one`"" } } @@ -600,7 +620,7 @@ Describe "When Creating a Verifiable Mock that is not called" { } It "Should throw" { - $result.Exception.Message | Should Be "`r`n Expected ModuleFunctionUnderTest in module TestModule to be called with `$param1 -eq `"one`"" + $result.Exception.Message | Should Be "$([System.Environment]::NewLine) Expected ModuleFunctionUnderTest in module TestModule to be called with `$param1 -eq `"one`"" } AfterAll { @@ -851,14 +871,34 @@ Describe 'Dot Source Test' { } } -Describe 'Mocking Cmdlets with dynamic parameters' { - $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } - Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$CodeSigningCert } +InModuleScope -ModuleName Pester { + + Describe 'Mocking Cmdlets with dynamic parameters' { + + If ((GetPesterOs) -ne 'Windows') { + + $mockWith = { if (-not $Hidden) { throw 'Hidden variable not found, or set to false!' } } + Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$Hidden } + + It 'Allows calls to be made with dynamic parameters (including parameter filters)' { + { Get-ChildItem -Path / -Hidden } | Should Not Throw + Assert-MockCalled Get-ChildItem + } + + } + Else { + + $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } + Mock Get-ChildItem -MockWith $mockWith -ParameterFilter { [bool]$CodeSigningCert } + + It 'Allows calls to be made with dynamic parameters (including parameter filters)' { + { Get-ChildItem -Path Cert:\ -CodeSigningCert } | Should Not Throw + Assert-MockCalled Get-ChildItem + } + } - It 'Allows calls to be made with dynamic parameters (including parameter filters)' { - { Get-ChildItem -Path Cert:\ -CodeSigningCert } | Should Not Throw - Assert-MockCalled Get-ChildItem } + } Describe 'Mocking functions with dynamic parameters' { @@ -1200,91 +1240,162 @@ Describe 'Mocking functions with dynamic parameters' { } } -Describe 'Mocking Cmdlets with dynamic parameters in a module' { - New-Module -Name TestModule { - function PublicFunction { Get-ChildItem -Path Cert:\ -CodeSigningCert } - } | Import-Module -Force +InModuleScope -ModuleName Pester { - $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } - Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$CodeSigningCert } + Describe 'Mocking Cmdlets with dynamic parameters in a module' { - It 'Allows calls to be made with dynamic parameters (including parameter filters)' { - { TestModule\PublicFunction } | Should Not Throw - Assert-MockCalled Get-ChildItem -ModuleName TestModule - } + If ((GetPesterOs) -ne 'Windows') { - AfterAll { - Remove-Module TestModule -Force + New-Module -Name TestModule { + function PublicFunction { Get-ChildItem -Path \ -Hidden } + } | Import-Module -Force + + $mockWith = { if (-not $Hidden) { throw 'Hidden variable not found, or set to false!' } } + Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$Hidden } + + } + Else { + + New-Module -Name TestModule { + function PublicFunction { Get-ChildItem -Path Cert:\ -CodeSigningCert } + } | Import-Module -Force + + $mockWith = { if (-not $CodeSigningCert) { throw 'CodeSigningCert variable not found, or set to false!' } } + Mock Get-ChildItem -MockWith $mockWith -ModuleName TestModule -ParameterFilter { [bool]$CodeSigningCert } + + } + + It 'Allows calls to be made with dynamic parameters (including parameter filters)' { + { TestModule\PublicFunction } | Should Not Throw + Assert-MockCalled Get-ChildItem -ModuleName TestModule + } + + AfterAll { + Remove-Module TestModule -Force + } } -} -Describe 'DynamicParam blocks in other scopes' { - New-Module -Name TestModule1 { - $script:DoDynamicParam = $true + Describe 'DynamicParam blocks in other scopes' { - function DynamicParamFunction { - [CmdletBinding()] - param ( ) + If ((GetPesterOs) -ne 'Windows') { - DynamicParam { - if ($script:DoDynamicParam) - { - if ($PSVersionTable.PSVersion.Major -ge 3) - { - # -Parameters needs to be a PSBoundParametersDictionary object to work properly, due to internal - # details of the PS engine in v5. Naturally, this is an internal type and we need to use reflection - # to make a new one. + New-Module -Name TestModule1 { + $script:DoDynamicParam = $true + + function DynamicParamFunction { + [CmdletBinding()] + param ( ) - $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' - $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@()) + DynamicParam { + if ($script:DoDynamicParam) + { + + $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' + $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@()) + + $params['Path'] = [string[]]'/' + Get-MockDynamicParameter -CmdletName Get-ChildItem -Parameters $params + } } - else + + end { - $params = @{} + 'I am the original function' } + } + } | Import-Module -Force - $params['Path'] = [string[]]'Cert:\' - Get-MockDynamicParameter -CmdletName Get-ChildItem -Parameters $params + New-Module -Name TestModule2 { + function CallingFunction + { + DynamicParamFunction -Hidden } - } - end - { - 'I am the original function' - } - } - } | Import-Module -Force + function CallingFunction2 { + [CmdletBinding()] + param ( + [ValidateScript({ [bool](DynamicParamFunction -Hidden) })] + [string] + $Whatever + ) + } + } | Import-Module -Force + + Mock DynamicParamFunction { if ($Hidden) { 'I am the mocked function' } } -ModuleName TestModule2 - New-Module -Name TestModule2 { - function CallingFunction - { - DynamicParamFunction -CodeSigningCert } + Else { + + New-Module -Name TestModule1 { + $script:DoDynamicParam = $true + + function DynamicParamFunction { + [CmdletBinding()] + param ( ) + + DynamicParam { + if ($script:DoDynamicParam) + { + if ($PSVersionTable.PSVersion.Major -ge 3) + { + # -Parameters needs to be a PSBoundParametersDictionary object to work properly, due to internal + # details of the PS engine in v5. Naturally, this is an internal type and we need to use reflection + # to make a new one. + + $flags = [System.Reflection.BindingFlags]'Instance,NonPublic' + $params = $PSBoundParameters.GetType().GetConstructor($flags, $null, @(), $null).Invoke(@()) + } + else + { + $params = @{} + } + + $params['Path'] = [string[]]'Cert:\' + Get-MockDynamicParameter -CmdletName Get-ChildItem -Parameters $params + } + } + + end + { + 'I am the original function' + } + } + } | Import-Module -Force + + New-Module -Name TestModule2 { + function CallingFunction + { + DynamicParamFunction -CodeSigningCert + } + + function CallingFunction2 { + [CmdletBinding()] + param ( + [ValidateScript({ [bool](DynamicParamFunction -CodeSigningCert) })] + [string] + $Whatever + ) + } + } | Import-Module -Force + + Mock DynamicParamFunction { if ($CodeSigningCert) { 'I am the mocked function' } } -ModuleName TestModule2 - function CallingFunction2 { - [CmdletBinding()] - param ( - [ValidateScript({ [bool](DynamicParamFunction -CodeSigningCert) })] - [string] - $Whatever - ) } - } | Import-Module -Force - Mock DynamicParamFunction { if ($CodeSigningCert) { 'I am the mocked function' } } -ModuleName TestModule2 + It 'Properly evaluates dynamic parameters when called from another scope' { + CallingFunction | Should Be 'I am the mocked function' + } - It 'Properly evaluates dynamic parameters when called from another scope' { - CallingFunction | Should Be 'I am the mocked function' - } + It 'Properly evaluates dynamic parameters when called from another scope when the call is from a ValidateScript block' { + CallingFunction2 -Whatever 'Whatever' + } - It 'Properly evaluates dynamic parameters when called from another scope when the call is from a ValidateScript block' { - CallingFunction2 -Whatever 'Whatever' + AfterAll { + Remove-Module TestModule1 -Force + Remove-Module TestModule2 -Force + } } - AfterAll { - Remove-Module TestModule1 -Force - Remove-Module TestModule2 -Force - } } Describe 'Parameter Filters and Common Parameters' { diff --git a/Functions/Mock.ps1 b/Functions/Mock.ps1 index 45a2cf665..3664402d7 100644 --- a/Functions/Mock.ps1 +++ b/Functions/Mock.ps1 @@ -422,7 +422,7 @@ This will not throw an exception because the mock was invoked. $function = $array[1] $module = $array[0] - $message = "`r`n Expected $function " + $message = "$([System.Environment]::NewLine) Expected $function " if ($module) { $message += "in module $module " } $message += "to be called with $($unVerified[$mock].Filter)" } @@ -660,7 +660,7 @@ param( } } - $lineText = $MyInvocation.Line.TrimEnd("`n") + $lineText = $MyInvocation.Line.TrimEnd("$([System.Environment]::NewLine)") $line = $MyInvocation.ScriptLineNumber if($matchingCalls.Count -ne $times -and ($Exactly -or ($times -eq 0))) @@ -1294,13 +1294,16 @@ function Get-DynamicParamBlock } else { - if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock) + If ( $ScriptBlock.AST.psobject.Properties.Name -match "Body") { - $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements | - & $SafeCommands['Select-Object'] -ExpandProperty Extent | - & $SafeCommands['Select-Object'] -ExpandProperty Text + if ($null -ne $ScriptBlock.Ast.Body.DynamicParamBlock) + { + $statements = $ScriptBlock.Ast.Body.DynamicParamBlock.Statements | + & $SafeCommands['Select-Object'] -ExpandProperty Extent | + & $SafeCommands['Select-Object'] -ExpandProperty Text - return $statements -join "`r`n" + return $statements -join "$([System.Environment]::NewLine)" + } } } } @@ -1381,7 +1384,7 @@ function Get-DynamicParametersForCmdlet return } - if ($PSVersionTable.PSVersion -ge '5.0.10586.122') + if ('5.0.10586.122' -lt $PSVersionTable.PSVersion) { # Older version of PS required Reflection to do this. It has run into problems on occasion with certain cmdlets, # such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible. diff --git a/Functions/New-Fixture.ps1 b/Functions/New-Fixture.ps1 index 067555b12..b646ab963 100644 --- a/Functions/New-Fixture.ps1 +++ b/Functions/New-Fixture.ps1 @@ -67,7 +67,7 @@ function New-Fixture { ) #region File contents #keep this formatted as is. the format is output to the file as is, including indentation - $scriptCode = "function $name {`r`n`r`n}" + $scriptCode = "function $name {$([System.Environment]::NewLine)$([System.Environment]::NewLine)}" $testCode = '$here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ''\.Tests\.'', ''.'' diff --git a/Functions/Output.Tests.ps1 b/Functions/Output.Tests.ps1 index 3e7b6fb1c..13f903ab5 100644 --- a/Functions/Output.Tests.ps1 +++ b/Functions/Output.Tests.ps1 @@ -66,7 +66,7 @@ Describe 'ConvertTo-PesterResult' { #on which line the test failed. $errorRecord = $null try{'something' | should be 'nothing'}catch{ $errorRecord=$_} ; $script={} - $result = & $getPesterResult 0 $errorRecord + $result = & $getPesterResult -Time 0 -ErrorRecord $errorRecord It 'records the correct stack line number' { $result.Stacktrace | should match "${thisScriptRegex}: line $($script.startPosition.StartLine)" } @@ -83,7 +83,7 @@ Describe 'ConvertTo-PesterResult' { $errorRecord = $null try { $object.ThrowSomething() } catch { $errorRecord = $_ } - $pesterResult = & $getPesterResult 0 $errorRecord + $pesterResult = & $getPesterResult -Time 0 -ErrorRecord $errorRecord $pesterResult.FailureMessage | Should Be $errorRecord.Exception.Message } @@ -93,7 +93,7 @@ Describe 'ConvertTo-PesterResult' { $testPath = Join-Path $TestDrive test.ps1 $escapedTestPath = [regex]::Escape($testPath) - Set-Content -Path $testPath -Value "`r`n'One' | Should Be 'Two'" + Set-Content -Path $testPath -Value "$([System.Environment]::NewLine)'One' | Should Be 'Two'" try { @@ -104,7 +104,7 @@ Describe 'ConvertTo-PesterResult' { $errorRecord = $_ } - $result = & $getPesterResult 0 $errorRecord + $result = & $getPesterResult -Time 0 -ErrorRecord $errorRecord It 'records the correct stack line number' { @@ -119,31 +119,67 @@ Describe 'ConvertTo-PesterResult' { InModuleScope -ModuleName Pester -ScriptBlock { Describe "Format-PesterPath" { + It "Writes path correctly when it is given `$null" { Format-PesterPath -Path $null | Should -Be $null } - It "Writes path correctly when it is provided as string" { - Format-PesterPath -Path "C:\path" | Should -Be "C:\path" - } + If ( (GetPesterOS) -ne 'Windows') { + + It "Writes path correctly when it is provided as string" { + Format-PesterPath -Path "/home/username/folder1" | Should -Be "/home/username/folder1" + } + + It "Writes path correctly when it is provided as string[]" { + Format-PesterPath -Path @("/home/username/folder1", "/home/username/folder2") -Delimiter ', ' | Should -Be "/home/username/folder1, /home/username/folder2" + } + + It "Writes path correctly when provided through hashtable" { + Format-PesterPath -Path @{ Path = "/home/username/folder1" } | Should -Be "/home/username/folder1" + } + + It "Writes path correctly when provided through array of hashtable" { + Format-PesterPath -Path @{ Path = "/home/username/folder1" }, @{ Path = "/home/username/folder2" } -Delimiter ', ' | Should -Be "/home/username/folder1, /home/username/folder2" + } - It "Writes path correctly when it is provided as string[]" { - Format-PesterPath -Path @("C:\path1", "C:\path2") -Delimiter ', ' | Should -Be "C:\path1, C:\path2" - } - It "Writes path correctly when provided through hashtable" { - Format-PesterPath -Path @{ Path = "C:\path" } | Should -Be "C:\path" } + Else { + + It "Writes path correctly when it is provided as string" { + Format-PesterPath -Path "C:\path" | Should -Be "C:\path" + } + + It "Writes path correctly when it is provided as string[]" { + Format-PesterPath -Path @("C:\path1", "C:\path2") -Delimiter ', ' | Should -Be "C:\path1, C:\path2" + } + + It "Writes path correctly when provided through hashtable" { + Format-PesterPath -Path @{ Path = "C:\path" } | Should -Be "C:\path" + } + + It "Writes path correctly when provided through array of hashtable" { + Format-PesterPath -Path @{ Path = "C:\path1" }, @{ Path = "C:\path2" } -Delimiter ', ' | Should -Be "C:\path1, C:\path2" + } - It "Writes path correctly when provided through array of hashtable" { - Format-PesterPath -Path @{ Path = "C:\path1" }, @{ Path = "C:\path2" } -Delimiter ', ' | Should -Be "C:\path1, C:\path2" } } Describe "Write-PesterStart" { It "uses Format-PesterPath with the provided path" { Mock Format-PesterPath - $expected = "C:\temp" + + If ((GetPesterOS) -ne 'Windows'){ + + $expected = "/tmp" + + } + Else { + + $expected = "C:\temp" + + } + Write-PesterStart -PesterState (New-PesterState) -Path $expected Assert-MockCalled Format-PesterPath -ParameterFilter {$Path -eq $expected} } @@ -162,6 +198,7 @@ InModuleScope -ModuleName Pester -ScriptBlock { } It 'failed should produces correct message lines.' { try { 'One' | Should be 'Two' } catch { $e = $_ } + $r = $e | ConvertTo-FailureLines $r.Message[0] | Should be 'String lengths are both 3. Strings differ at index 0.' @@ -228,13 +265,29 @@ InModuleScope -ModuleName Pester -ScriptBlock { } if ( $e | Get-Member -Name ScriptStackTrace ) { - It 'produces correct trace lines.' { - $r.Trace[0] | Should be "at f1, $testPath`: line 2" - $r.Trace[1] | Should be "at f2, $testPath`: line 5" - $r.Trace[2] | Should be "at , $testPath`: line 7" - $r.Trace[3] -match 'at , .*\\Functions\\Output.Tests.ps1: line [0-9]*$' | - Should be $true - $r.Trace.Count | Should be 5 + If ((GetPesterOS) -ne 'Windows') { + + It 'produces correct trace lines.' { + $r.Trace[0] | Should be "at f1, $testPath`: line 2" + $r.Trace[1] | Should be "at f2, $testPath`: line 5" + $r.Trace[2] | Should be "at , $testPath`: line 7" + $r.Trace[3] -match 'at , .*/Functions/Output.Tests.ps1: line [0-9]*$' | + Should be $true + $r.Trace.Count | Should be 5 + } + + } + Else { + + It 'produces correct trace lines.' { + $r.Trace[0] | Should be "at f1, $testPath`: line 2" + $r.Trace[1] | Should be "at f2, $testPath`: line 5" + $r.Trace[2] | Should be "at , $testPath`: line 7" + $r.Trace[3] -match 'at , .*\\Functions\\Output.Tests.ps1: line [0-9]*$' | + Should be $true + $r.Trace.Count | Should be 5 + } + } } else @@ -274,10 +327,19 @@ InModuleScope -ModuleName Pester -ScriptBlock { } if ( $e | Get-Member -Name ScriptStackTrace ) { - It 'produces correct trace line.' { - $r.Trace[0] | Should be "at , $testPath`: line 10" - $r.Trace[1] -match 'at , .*\\Functions\\Output.Tests.ps1: line [0-9]*$' - $r.Trace.Count | Should be 3 + If ((GetPesterOS) -ne 'Windows') { + It 'produces correct trace line.' { + $r.Trace[0] | Should be "at , $testPath`: line 10" + $r.Trace[1] -match 'at , .*/Functions/Output.Tests.ps1: line [0-9]*$' + $r.Trace.Count | Should be 3 + } + } + Else { + It 'produces correct trace line.' { + $r.Trace[0] | Should be "at , $testPath`: line 10" + $r.Trace[1] -match 'at , .*\\Functions\\Output.Tests.ps1: line [0-9]*$' + $r.Trace.Count | Should be 3 + } } } else diff --git a/Functions/Output.ps1 b/Functions/Output.ps1 index 518569097..f6effc147 100644 --- a/Functions/Output.ps1 +++ b/Functions/Output.ps1 @@ -126,7 +126,7 @@ function Write-Describe { & $SafeCommands['Write-Host'] "${margin}${Text}" -ForegroundColor $ReportTheme.Describe # If the feature has a longer description, write that too if($Describe.PSObject.Properties['Description'] -and $Describe.Description) { - $Describe.Description -split '\n' | ForEach { + $Describe.Description -split "$([System.Environment]::NewLine)" | ForEach { & $SafeCommands['Write-Host'] ($ReportStrings.Margin * ($pester.IndentLevel + 1)) $_ -ForegroundColor $ReportTheme.DescribeDetail } } @@ -150,7 +150,7 @@ function Write-Context { & $SafeCommands['Write-Host'] ($ReportStrings.Margin + $Text) -ForegroundColor $ReportTheme.Context # If the scenario has a longer description, write that too if($Context.PSObject.Properties['Description'] -and $Context.Description) { - $Context.Description -split '\n' | ForEach { + $Context.Description -split "$([System.Environment]::NewLine)" | ForEach { & $SafeCommands['Write-Host'] (" " * $ReportStrings.Context.Length) $_ -ForegroundColor $ReportTheme.ContextDetail } } @@ -159,19 +159,20 @@ function Write-Context { function ConvertTo-PesterResult { param( + [String] $Name, [Nullable[TimeSpan]] $Time, [System.Management.Automation.ErrorRecord] $ErrorRecord ) $testResult = @{ - name = $name + name = $Name time = $time failureMessage = "" stackTrace = "" ErrorRecord = $null success = $false result = "Failed" - }; + } if(-not $ErrorRecord) { @@ -211,7 +212,7 @@ function ConvertTo-PesterResult { } $testResult.failureMessage = $failureMessage - $testResult.stackTrace = "at , ${file}: line ${line}`n${line}: ${Text}" + $testResult.stackTrace = "at , ${file}: line ${line}$([System.Environment]::NewLine)${line}: ${Text}" $testResult.ErrorRecord = $ErrorRecord return $testResult @@ -322,9 +323,23 @@ function Write-PesterReport { $Pending = if($PesterState.PendingCount -gt 0) { $ReportTheme.Pending } else { $ReportTheme.Information } $Inconclusive = if($PesterState.InconclusiveCount -gt 0) { $ReportTheme.Inconclusive } else { $ReportTheme.Information } + Try { + $PesterStatePassedScenariosCount = $PesterState.PassedScenarios.Count + } + Catch { + $PesterStatePassedScenariosCount = 0 + } + + Try { + $PesterStateFailedScenariosCount = $PesterState.FailedScenarios.Count + } + Catch { + $PesterStateFailedScenariosCount = 0 + } + if($ReportStrings.ContextsPassed) { - & $SafeCommands['Write-Host'] ($ReportStrings.ContextsPassed -f $PesterState.PassedScenarios.Count) -Foreground $Success -NoNewLine - & $SafeCommands['Write-Host'] ($ReportStrings.ContextsFailed -f $PesterState.FailedScenarios.Count) -Foreground $Failure + & $SafeCommands['Write-Host'] ($ReportStrings.ContextsPassed -f $PesterStatePassedScenariosCount) -Foreground $Success -NoNewLine + & $SafeCommands['Write-Host'] ($ReportStrings.ContextsFailed -f $PesterStateFailedScenariosCount) -Foreground $Failure } if($ReportStrings.TestsPassed) { & $SafeCommands['Write-Host'] ($ReportStrings.TestsPassed -f $PesterState.PassedCount) -Foreground $Success -NoNewLine @@ -391,10 +406,11 @@ function ConvertTo-FailureLines ## convert the exception messages $exception = $ErrorRecord.Exception $exceptionLines = @() + while ($exception) { $exceptionName = $exception.GetType().Name - $thisLines = $exception.Message.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) + $thisLines = $exception.Message.Split([string[]]($([System.Environment]::NewLine),"\n","`n"), [System.StringSplitOptions]::RemoveEmptyEntries) if ($ErrorRecord.FullyQualifiedErrorId -ne 'PesterAssertionFailed') { $thisLines[0] = "$exceptionName`: $($thisLines[0])" @@ -407,7 +423,7 @@ function ConvertTo-FailureLines $lines.Message += $exceptionLines if ($ErrorRecord.FullyQualifiedErrorId -eq 'PesterAssertionFailed') { - $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) + $lines.Message += "$($ErrorRecord.TargetObject.Line)`: $($ErrorRecord.TargetObject.LineText)".Split([string[]]($([System.Environment]::NewLine),"\n","`n"), [System.StringSplitOptions]::RemoveEmptyEntries) } if ( -not ($ErrorRecord | & $SafeCommands['Get-Member'] -Name ScriptStackTrace) ) @@ -424,14 +440,29 @@ function ConvertTo-FailureLines } ## convert the stack trace - $traceLines = $ErrorRecord.ScriptStackTrace.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) + $traceLines = $ErrorRecord.ScriptStackTrace.Split([string[]]($([System.Environment]::NewLine),"\n","`n"), [System.StringSplitOptions]::RemoveEmptyEntries) $count = 0 # omit the lines internal to Pester + + If ((GetPesterOS) -ne 'Windows') { + + [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*/Functions/.*.ps1: line [0-9]*$' + [String]$pattern2 = '^at Should, .*/Functions/Assertions/Should.ps1: line [0-9]*$' + [String]$pattern3 = '^at Assert-MockCalled, .*/Functions/Mock.ps1: line [0-9]*$' + } + Else { + + [String]$pattern1 = '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' + [String]$pattern2 = '^at Should, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' + [String]$pattern3 = '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$' + + } + foreach ( $line in $traceLines ) { - if ( $line -match '^at (Invoke-Test|Context|Describe|InModuleScope|Invoke-Pester), .*\\Functions\\.*.ps1: line [0-9]*$' ) + if ( $line -match $pattern1 ) { break } @@ -440,8 +471,8 @@ function ConvertTo-FailureLines $lines.Trace += $traceLines | & $SafeCommands['Select-Object'] -First $count | & $SafeCommands['Where-Object'] { - $_ -notmatch '^at Should, .*\\Functions\\Assertions\\Should.ps1: line [0-9]*$' -and - $_ -notmatch '^at Assert-MockCalled, .*\\Functions\\Mock.ps1: line [0-9]*$' + $_ -notmatch $pattern2 -and + $_ -notmatch $pattern3 } return $lines diff --git a/Functions/TestResults.Tests.ps1 b/Functions/TestResults.Tests.ps1 index 235ada3d1..7eb2adb14 100644 --- a/Functions/TestResults.Tests.ps1 +++ b/Functions/TestResults.Tests.ps1 @@ -12,7 +12,7 @@ InModuleScope Pester { $TestResults.AddTestResult("Successful testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' @@ -29,7 +29,7 @@ InModuleScope Pester { $TestResults.AddTestResult("Failed testcase",'Failed',$time,'Assert failed: "Expected: Test. But was: Testing"','at line: 28 in C:\Pester\Result.Tests.ps1') #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestCase = $xmlResult.'test-results'.'test-suite'.'results'.'test-suite'.'results'.'test-case' @@ -47,7 +47,7 @@ InModuleScope Pester { $TestResults.AddTestResult("Testcase",'Passed',(New-TimeSpan -Seconds 1)) #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) $xmlTestResult = $xmlResult.'test-results' @@ -68,7 +68,7 @@ InModuleScope Pester { Set-PesterStatistics -Node $TestResults.TestActions #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) @@ -94,7 +94,7 @@ InModuleScope Pester { Set-PesterStatistics -Node $TestResults.TestActions #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) @@ -115,7 +115,7 @@ InModuleScope Pester { it "should write the environment information" { $state = New-PesterState "." - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $state $testFile $xmlResult = [xml] (Get-Content $testFile) @@ -139,7 +139,7 @@ InModuleScope Pester { $TestResults.AddTestResult("Failed testcase",'Failed',(New-TimeSpan -Seconds 2)) #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) @@ -156,7 +156,7 @@ InModuleScope Pester { $TestResults.LeaveTestGroup('Describe -!@#$%^&*()_+`1234567890[];'',./"- #1', 'Describe') #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xml = [xml] (Get-Content $testFile) @@ -196,7 +196,7 @@ InModuleScope Pester { ) #export and validate the file - $testFile = "$TestDrive\Results\Tests.xml" + [String]$testFile = "$TestDrive{0}Results{0}Tests.xml" -f [System.IO.Path]::DirectorySeparatorChar Export-NunitReport $testResults $testFile $xmlResult = [xml] (Get-Content $testFile) @@ -285,8 +285,15 @@ InModuleScope Pester { $p | Should Be (Join-Path $TestDrive existingfile.txt) } + If (($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -eq 'Core')) { + $CommandToTest = "pwsh" + } + Else { + $CommandToTest = "powershell" + } + It "Resolves full path correctly" { - $powershellPath = Get-Command 'powershell' | Select -ExpandProperty 'Definition' + $powershellPath = Get-Command -Name $CommandToTest | Select-Object -ExpandProperty 'Definition' $powershellPath | Should -Not -BeNullOrEmpty GetFullPath $powershellPath | Should Be $powershellPath diff --git a/Functions/TestResults.ps1 b/Functions/TestResults.ps1 index 3e3cdfa54..80a50796d 100644 --- a/Functions/TestResults.ps1 +++ b/Functions/TestResults.ps1 @@ -437,6 +437,18 @@ function Get-RunTimeEnvironment() { Version = "0.0.0.0" } } + + If ( ($PSVersionTable.ContainsKey('PSEdition')) -and ($PSVersionTable.PSEdition -EQ 'Core')) { + + $CLrVersion = "Unknown" + + } + Else { + + $CLrVersion = [string]$PSVersionTable.ClrVersion + + } + @{ 'nunit-version' = '2.5.8.0' 'os-version' = $osSystemInformation.Version @@ -445,7 +457,7 @@ function Get-RunTimeEnvironment() { 'machine-name' = $env:ComputerName user = $env:Username 'user-domain' = $env:userDomain - 'clr-version' = [string]$PSVersionTable.ClrVersion + 'clr-version' = $CLrVersion } } diff --git a/Pester.Tests.ps1 b/Pester.Tests.ps1 index 6455f5134..855eb3a33 100644 --- a/Pester.Tests.ps1 +++ b/Pester.Tests.ps1 @@ -26,11 +26,11 @@ Describe -Tags 'VersionChecks' "Pester manifest and changelog" { $script:manifest.Guid | Should Be 'a699dea5-2c73-4616-a270-1f7abb777e71' } - if (Get-Command git.exe -ErrorAction SilentlyContinue) { + if (Get-Command -Name git -ErrorAction SilentlyContinue) { $skipVersionTest = -not [bool]((git remote -v 2>&1) -match "github.com/Pester/") It "is tagged with a valid version" -skip:$skipVersionTest { - $thisCommit = git.exe log --decorate --oneline HEAD~1..HEAD + $thisCommit = git log --decorate --oneline HEAD~1..HEAD if ($thisCommit -match 'tag:\s*(\d+(?:\.\d+)*)') { @@ -167,7 +167,7 @@ Describe 'Style rules' -Tag StyleRules { if ($badLines.Count -gt 0) { - throw "The following $($badLines.Count) lines contain trailing whitespace: `r`n`r`n$($badLines -join "`r`n")" + throw "The following $($badLines.Count) lines contain trailing whitespace: $([System.Environment]::NewLine)$([System.Environment]::NewLine)$($badLines -join "$([System.Environment]::NewLine)")" } } It 'Spaces are used for indentation in all code files, not tabs' { @@ -188,7 +188,7 @@ Describe 'Style rules' -Tag StyleRules { if ($badLines.Count -gt 0) { - throw "The following $($badLines.Count) lines start with a tab character: `r`n`r`n$($badLines -join "`r`n")" + throw "The following $($badLines.Count) lines start with a tab character: $([System.Environment]::NewLine)$([System.Environment]::NewLine)$($badLines -join "$([System.Environment]::NewLine)")" } } @@ -203,7 +203,7 @@ Describe 'Style rules' -Tag StyleRules { ) if ($badFiles.Count -gt 0) { - throw "The following files do not end with a newline: `r`n`r`n$($badFiles -join "`r`n")" + throw "The following files do not end with a newline: $([System.Environment]::NewLine)$([System.Environment]::NewLine)$($badFiles -join "$([System.Environment]::NewLine)")" } } } diff --git a/Pester.psm1 b/Pester.psm1 index 6226e9807..bf5dd7889 100644 --- a/Pester.psm1 +++ b/Pester.psm1 @@ -86,7 +86,7 @@ $script:SafeCommands = @{ 'Write-Warning' = Get-Command -Name Write-Warning -Module Microsoft.PowerShell.Utility @safeCommandLookupParameters } -# Not all platforms have Get-WmiObject (Nano) +# Not all platforms have Get-WmiObject (Nano or PSCore 6.0.0-beta.x on Linux) # Get-CimInstance is prefered, but we can use Get-WmiObject if it exists # Moreover, it shouldn't really be fatal if neither of those cmdlets # exist diff --git a/README.md b/README.md index a2d9904d3..237602474 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ Learn more about the [usage and syntax](https://github.com/Pester/Pester/wiki) o ## Installation -Pester is compatible with Windows 10, 8, 7, Vista and even 2003. We are also working hard on making it run on Linux and MacOS. +Pester is compatible with Windows PowerShell 2.x - 5.x on Windows 10, 8, 7, Vista and even 2003. +Since version 4.0.9 Pester is compatible also with PowerShell Core 6.x on Windows, Linux, macOS but with some [limitations](https://github.com/pester/Pester/wiki/Pester-on-PSCore-limitations). Pester comes pre-installed with Windows 10, but we recommend updating, by running this PowerShell command _as administrator_: @@ -65,6 +66,12 @@ Install-Module -Name Pester -Force -SkipPublisherCheck Not running Windows 10 or facing problems? See the [full installation and update guide](https://github.com/pester/Pester/wiki/Installation-and-Update). +Please be aware that PowerShell Core 6.0.0-beta.9 comes with bundle Pester version 3.3.9, but we recommend updating, by running this PowerShell command _as administrator_: + +```powershell +Install-Module -Name Pester -Force +``` + ## Features ### Test runner