diff --git a/src/functions/assertions/Be.ps1 b/src/functions/assertions/Be.ps1 index 60db1c194..ffa635e9d 100644 --- a/src/functions/assertions/Be.ps1 +++ b/src/functions/assertions/Be.ps1 @@ -60,6 +60,11 @@ function ShouldBeFailureMessage($ActualValue, $ExpectedValue, $Because) { if ($actualLength -ne $expectedLength) { return "Expected a collection $(Format-Nicely $ExpectedValue) with length $expectedLength,$(if ($null -ne $Because) { Format-Because $Because }) but got a collection $(Format-Nicely $ActualValue) with length $actualLength." } + + $differenceIndex = Get-CollectionDifferenceIndex -ActualValue @($ActualValue) -ExpectedValue @($ExpectedValue) + if ($null -ne $differenceIndex) { + return (Get-CompareCollectionMessage -ActualValue @($ActualValue) -ExpectedValue @($ExpectedValue) -DifferenceIndex $differenceIndex -Because $Because) -join "`n" + } } # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly. @@ -141,6 +146,21 @@ function Should-BeAssertionExactly($ActualValue, $ExpectedValue, $Because) { } function ShouldBeExactlyFailureMessage($ActualValue, $ExpectedValue, $Because) { + $actualIsCollection = $ActualValue -is [System.Collections.IEnumerable] -and $ActualValue -isnot [string] + $expectedIsCollection = $ExpectedValue -is [System.Collections.IEnumerable] -and $ExpectedValue -isnot [string] + + if ($actualIsCollection -and $expectedIsCollection) { + $actualLength = @($ActualValue).Count + $expectedLength = @($ExpectedValue).Count + + if ($actualLength -eq $expectedLength) { + $differenceIndex = Get-CollectionDifferenceIndex -ActualValue @($ActualValue) -ExpectedValue @($ExpectedValue) -CaseSensitive + if ($null -ne $differenceIndex) { + return (Get-CompareCollectionMessage -ActualValue @($ActualValue) -ExpectedValue @($ExpectedValue) -DifferenceIndex $differenceIndex -Because $Because) -join "`n" + } + } + } + # This looks odd; it's to unroll single-element arrays so the "-is [string]" expression works properly. $ActualValue = $($ActualValue) $ExpectedValue = $($ExpectedValue) @@ -245,6 +265,49 @@ function Get-CompareStringMessage { } } +function Get-CollectionDifferenceIndex { + param( + [object[]] $ActualValue, + [object[]] $ExpectedValue, + [switch] $CaseSensitive + ) + + for ($i = 0; $i -lt $ExpectedValue.Count; $i++) { + if ((IsArray $ActualValue[$i]) -or (IsArray $ExpectedValue[$i])) { + if (-not (ArraysAreEqual -First $ActualValue[$i] -Second $ExpectedValue[$i] -CaseSensitive:$CaseSensitive)) { + return $i + } + } + else { + if ($CaseSensitive) { + $comparer = { param($Actual, $Expected) $Expected -ceq $Actual } + } + else { + $comparer = { param($Actual, $Expected) $Expected -eq $Actual } + } + + if (-not (& $comparer $ActualValue[$i] $ExpectedValue[$i])) { + return $i + } + } + } +} + +function Get-CompareCollectionMessage { + param( + [object[]] $ExpectedValue, + [object[]] $ActualValue, + [int] $DifferenceIndex, + $Because + ) + + "Expected collections to be the same,$(if ($null -ne $Because) { Format-Because $Because }) but they were different." + "Collection lengths are both $($ExpectedValue.Count)." + "Collections differ at index $DifferenceIndex." + "Expected: $(Format-Nicely $ExpectedValue)" + "But was: $(Format-Nicely $ActualValue)" +} + function Format-AsExcerpt { param ( [Parameter(Mandatory = $true)] diff --git a/tst/functions/assertions/Be.Tests.ps1 b/tst/functions/assertions/Be.Tests.ps1 index 389e48ecf..281cb508e 100644 --- a/tst/functions/assertions/Be.Tests.ps1 +++ b/tst/functions/assertions/Be.Tests.ps1 @@ -23,6 +23,10 @@ InPesterModuleScope { $array | Should -EQ $array } + It 'Passes for equal arrays with the same length' { + @(1, 2, 3) | Should -Be @(1, 2, 3) + } + It 'Compares arrays with correct case-insensitive behavior' { $string = 'I am a string' $array = @(1, 2, 3, 4, $string) @@ -146,8 +150,8 @@ InPesterModuleScope { ShouldBeFailureMessage (, (1, 2, 3)) (1, 2, 3) -Because 'reason' | Verify-Equal "Expected a collection @(1, 2, 3) with length 3, because reason, but got a collection @(@(1, 2, 3)) with length 1." } - It "Keeps the plain message for collections of equal length" { - ShouldBeFailureMessage (1, 2, 3) (1, 2, 4) | Verify-Equal "Expected @(1, 2, 4), but got @(1, 2, 3)." + It "Outputs the differing index for collections of equal length" { + ShouldBeFailureMessage (1, 2, 3) (1, 2, 4) | Verify-Equal "Expected collections to be the same, but they were different.`nCollection lengths are both 3.`nCollections differ at index 2.`nExpected: @(1, 2, 4)`nBut was: @(1, 2, 3)" } It "Outputs verbose message for two strings of different length" { @@ -209,6 +213,10 @@ InPesterModuleScope { It "Writes verbose message for strings that differ by case" { ShouldBeExactlyFailureMessage "a" "A" -Because "reason" | Verify-Equal "Expected strings to be the same, because reason, but they were different.`nString lengths are both 1.`nStrings differ at index 0.`nExpected: 'A'`nBut was: 'a'`n ^" } + + It "Outputs the differing index for collections of equal length" { + ShouldBeExactlyFailureMessage ('a', 'b') ('a', 'B') | Verify-Equal "Expected collections to be the same, but they were different.`nCollection lengths are both 2.`nCollections differ at index 1.`nExpected: @('a', 'B')`nBut was: @('a', 'b')" + } } }