diff --git a/Format/src/Format.psm1 b/Format/src/Format.psm1 index 6e5a7ff..227fd77 100644 --- a/Format/src/Format.psm1 +++ b/Format/src/Format.psm1 @@ -72,7 +72,7 @@ function Format-Dictionary ($Value) { } function Format-Nicely ($Value, [switch]$Pretty) { - if ($null -eq $Value) + if (Is-Null -Value $Value) { return Format-Null -Value $Value } diff --git a/TypeClass/src/TypeClass.psm1 b/TypeClass/src/TypeClass.psm1 index 11581f9..b6cae49 100644 --- a/TypeClass/src/TypeClass.psm1 +++ b/TypeClass/src/TypeClass.psm1 @@ -1,3 +1,8 @@ +function Is-Null ($Value) { + # Since PowerShell 7.0 [DBNull] -eq $null. + $null -eq $Value -or $Value -is [DBNull] -or $Value.Psobject.TypeNames[0] -like '*System.DBNull' +} + function Is-Value ($Value) { $Value = $($Value) $Value -is [ValueType] -or $Value -is [string] -or $value -is [scriptblock] @@ -50,6 +55,7 @@ function Is-DataRow ($Value) { } Export-ModuleMember -Function @( + 'Is-Null' 'Is-Value' 'Is-Collection' 'Is-DataTable' diff --git a/src/Equivalence/Assert-Equivalent.ps1 b/src/Equivalence/Assert-Equivalent.ps1 index f1221af..3919272 100644 --- a/src/Equivalence/Assert-Equivalent.ps1 +++ b/src/Equivalence/Assert-Equivalent.ps1 @@ -19,7 +19,8 @@ function Get-ValueNotEquivalentMessage ($Expected, $Actual, $Property, $Options) $Expected = Format-Nicely -Value $Expected $Actual = Format-Nicely -Value $Actual $propertyInfo = if ($Property) { " property $Property with value" } - $comparison = if ("Equality" -eq $Options.Comparator) { 'equal' } else { 'equivalent' } + $comparison = if ("Equality" -eq $Options.Comparator) { 'equal' } + elseif ("StrictEquality" -eq $Options.Comparator) { 'strictly equal' } else { 'equivalent' } "Expected$propertyInfo '$Expected' to be $comparison to the actual value, but got '$Actual'." } @@ -81,7 +82,7 @@ function Compare-CollectionEquivalent ($Expected, $Actual, $Property, $Options) v "`nSearching for `$Expected[$e]:" $currentExpected = $Expected[$e] $found = $false - if ($StrictOrder) { + if ($Options.StrictOrder) { $currentActual = $Actual[$e] if ($taken -notcontains $e -and (-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options))) { @@ -161,7 +162,7 @@ function Compare-DataTableEquivalent ($Expected, $Actual, $Property, $Options) { for ($e = 0; $e -lt $eEnd; $e++) { $currentExpected = $Expected.Rows[$e] $found = $false - if ($StrictOrder) { + if ($Options.StrictOrder) { $currentActual = $Actual.Rows[$e] if ((-not (Compare-Equivalent -Expected $currentExpected -Actual $currentActual -Path $Property -Options $Options)) -and $taken -notcontains $e) { $taken += $e @@ -191,6 +192,48 @@ function Compare-DataTableEquivalent ($Expected, $Actual, $Property, $Options) { } } +function Compare-NullEquivalent ($Actual, $Expected, $Property, $Options) { + if ("Equivalency" -eq $Options.Comparator) { + v "Equivalency comparator is used, values will be compared for equivalency." + if (Is-Null -Value $Actual) + { + v -Equivalence "`$Actual is equivalent to `$null." + return + } + } + elseif ("StrictEquality" -eq $Options.Comparator) + { + v "StrictEquality comparator is used, values will be compared for equality." + if ($Expected -isnot [Object] -and $Actual -isnot [Object]) + { + v -Equivalence "`$Actual is equal to `$null, because it is `$null." + return + } + if ($Expected -is [Object] -and $Actual -is [Object] -and + $Expected.Psobject.TypeNames[0] -like '*System.DBNull' -and + $Actual.Psobject.TypeNames[0] -like '*System.DBNull') + { + v -Equivalence "`$Actual is equal to DBNull, because it is DBNull." + return + } + } + else + { + v "Equality comparator is used, values will be compared for equality." + if ($Expected -is [Object] -and $Expected.Psobject.TypeNames[0] -like '*System.DBNull') {$Expected = [DBNull]::Value} + if ($Actual -is [Object] -and $Actual.Psobject.TypeNames[0] -like '*System.DBNull') {$Actual = [DBNull]::Value} + if ($Expected -eq $Actual) + { + v -Equivalence "`$Actual is equal to DBNull, because it is DBNull." + return + } + } + # we terminate here, either we passed the test and return nothing, or we did not + # and return message here + v -Difference "`$Actual is not equivalent to $(Format-Nicely $Expected)." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Property -Options $Options +} + function Compare-ValueEquivalent ($Actual, $Expected, $Property, $Options) { $Expected = $($Expected) if (-not (Is-Value -Value $Expected)) @@ -248,6 +291,16 @@ function Compare-ValueEquivalent ($Actual, $Expected, $Property, $Options) { return } } + elseif ("StrictEquality" -eq $Options.Comparator) + { + v "StrictEquality comparator is used, values will be compared for strict equality." + if ($Expected.GetType() -ne $Actual.GetType()) + { + v -Difference "`$Actual is not equivalent to `$Expected because their type differ." + return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path -Options $Options + } + v "types are matching, will be tested for values." + } else { v "Equality comparator is used, values will be compared for equality." @@ -575,21 +628,13 @@ function Compare-Equivalent { #start by null checks to avoid implementing null handling #logic in the functions that follow - if ($null -eq $Expected) - { + if (Is-Null -Value $Expected) { v "`$Expected is `$null, so we are expecting `$null." - if ($Expected -ne $Actual) - { - v -Difference "`$Actual is not equivalent to $(Format-Nicely $Expected), because it has a value of type $(Format-Nicely $Actual.GetType())." - return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path -Options $Options - } - # we terminate here, either we passed the test and return nothing, or we did not - # and the previous statement returned message - v -Equivalence "`$Actual is equivalent to `$null, because it is `$null." + Compare-NullEquivalent -Expected $Expected -Actual $Actual -Property $Path -Options $Options return } - if ($null -eq $Actual) + if (Is-Null -Value $Actual) { v -Difference "`$Actual is $(Format-Nicely), but `$Expected has value of type $(Format-Nicely Get-Type $Expected), so they are not equivalent." return Get-ValueNotEquivalentMessage -Expected $Expected -Actual $Actual -Property $Path @@ -660,8 +705,7 @@ function Assert-Equivalent { param( $Actual, $Expected, - $Options = (Get-EquivalencyOption), - [Switch] $StrictOrder + $Options = (Get-EquivalencyOption) ) $areDifferent = Compare-Equivalent -Actual $Actual -Expected $Expected -Options $Options | Out-String @@ -681,14 +725,16 @@ function Get-EquivalencyOption { param( [string[]] $ExcludePath = @(), [switch] $ExcludePathsNotOnExpected, - [ValidateSet('Equivalency', 'Equality')] - [string] $Comparator = 'Equivalency' + [ValidateSet('Equivalency', 'Equality', 'StrictEquality')] + [string] $Comparator = 'Equivalency', + [switch] $StrictOrder ) [PSCustomObject]@{ - ExcludedPaths = [string[]] $ExcludePath + ExcludedPaths = [string[]] $ExcludePath ExcludePathsNotOnExpected = [bool] $ExcludePathsNotOnExpected - Comparator = [string] $Comparator + Comparator = [string] $Comparator + StrictOrder = [bool] $StrictOrder } } diff --git a/tst/Equivalence/Assert-Equivalent.Options.Tests.ps1 b/tst/Equivalence/Assert-Equivalent.Options.Tests.ps1 index 632b1d3..89cdf73 100644 --- a/tst/Equivalence/Assert-Equivalent.Options.Tests.ps1 +++ b/tst/Equivalence/Assert-Equivalent.Options.Tests.ps1 @@ -356,8 +356,30 @@ $options = Get-EquivalencyOption -Comparator Equality { Assert-Equivalent -Actual $actual -Expected $expected -Options $options } | Verify-AssertionFailed } - } + $OptionsEquality = Get-EquivalencyOption -Comparator Equality + $OptionsStrictEquality = Get-EquivalencyOption -Comparator StrictEquality + $DBNull = [DBNull]::Value + It "Test vs with -Comparator 'Equivalency', 'Equality', 'StrictEquality'" -TestCases @( + @{ Actual = 'False' ; Expected = $false } + @{ Actual = $null; Expected = $DBNull } + @{ Actual = 5; Expected = '5' } + @{ Actual = { 'String' }; Expected = { 'String' } } + ) { + param($Expected, $Actual) + # Equivalent + Assert-Equivalent -Actual $Actual -Expected $Expected + Assert-Equivalent -Actual $Expected -Expected $Actual + # StrictEquality + { Assert-Equivalent -Actual $Actual -Expected $Expected -Options $OptionsStrictEquality } | Verify-AssertionFailed + { Assert-Equivalent -Actual $Expected -Expected $Actual -Options $OptionsStrictEquality } | Verify-AssertionFailed + # Equality + $AssertResult = try { Assert-Equivalent -Actual $Actual -Expected $Expected -Options $OptionsEquality ; $true} catch {$false} + $AssertResult -eq ($Expected -eq $Actual) | Verify-True + $AssertResult = try { Assert-Equivalent -Actual $Expected -Expected $Actual -Options $OptionsEquality ; $true} catch {$false} + $AssertResult -eq ($Actual -eq $Expected) | Verify-True + } + } Describe "Printing Options into difference report" { diff --git a/tst/Equivalence/Assert-Equivalent.Tests.ps1 b/tst/Equivalence/Assert-Equivalent.Tests.ps1 index 4a32fce..1963150 100644 --- a/tst/Equivalence/Assert-Equivalent.Tests.ps1 +++ b/tst/Equivalence/Assert-Equivalent.Tests.ps1 @@ -440,30 +440,15 @@ InModuleScope -ModuleName Assert { Assert-Equivalent -Actual $Actual -Expected $Expected function SerializeDeserialize ($InputObject) { - # psv2 compatibility - # $ExpectedDeserialized = [System.Management.Automation.PSSerializer]::Deserialize([System.Management.Automation.PSSerializer]::Serialize($Expected)) - # Alternatively this could be done in memory via https://github.com/Jaykul/Reflection/blob/master/CliXml.psm1, but I don't want to fiddle with more - # relfection right now - try { - $path = [IO.Path]::GetTempFileName() - - Export-Clixml -Path $path -InputObject $InputObject -Force | Out-Null - Import-Clixml -Path $path - } - finally { - if ($null -ne $path -and (Test-Path $path)) { - Remove-Item -Path $path -Force - } - } + , [System.Management.Automation.PSSerializer]::Deserialize([System.Management.Automation.PSSerializer]::Serialize($InputObject, 3)) } - $ExpectedDeserialized = SerializeDeserialize $Expected $ActualDeserialized = SerializeDeserialize $Actual Assert-Equivalent -Actual $ActualDeserialized -Expected $ExpectedDeserialized Assert-Equivalent -Actual $Actual -Expected $ExpectedDeserialized - {Assert-Equivalent -Actual $Actual -Expected $Expected -StrictOrder} | Should -Throw + {Assert-Equivalent -Actual $Actual -Expected $Expected -Options (Get-EquivalencyOption -StrictOrder)} | Should -Throw $Actual.Rows[1].Name = 'D' {Assert-Equivalent -Actual $Actual -Expected $Expected} | Should -Throw