nohwnd / Assert

A set of advanced assertions for Pester to simplify how you write tests.
MIT License
101 stars 12 forks source link

Assert-Same works incorrectly for value types #6

Closed nohwnd closed 6 years ago

nohwnd commented 7 years ago

I am comparing two integers if they are the same reference and for some cases it work as expected, and for some it does not. To be exact it does not work as expected for values 1..100. What I am guessing is that the language parser has a list of common constants in memory, and points to them to save memory (I think I read about similar optimization in .net but cannot replicate it in C# and can't find the source article. It does similar thing for strings called interning, but I could not find info about doing it for int.)

In theory pointing to the same instance of 1 should not make a difference to the code, unless your code is actually checking whether or not the instances are the same. Then it makes difference and gives us unexpected results.

# comparing value types, those should be immutable and every 
# instantiation should produce a distinct value with distinct "reference"

"providing values directly:"
# false -> as expected those are two distinct values
[object]::ReferenceEquals(1, 1) 

"saving 1 in variables:"
$a = 1
$b = 1
# true -> not expected
[object]::ReferenceEquals($a, $b)

"saving 1034 in variables:"
$a = 1034
$b = 1034
# false -> huh? must work only for some values
[object]::ReferenceEquals($a, $b)

"looping on -15..15:"
-15..15 | foreach { 
    $a = $_
    $b = [int]::parse($_.toString())
    # 30 x false -> haha okay
    [object]::ReferenceEquals($a,$b)
}

function a ($a, $b){
    "value $a, ${b}:"
    [object]::ReferenceEquals($a, $b)
}

"passing 1 through function parameters:"
# true
a -a 1 -b 1

"passing 1034 through function parameters:"
# false
a -a 1034 -b 1034

"executing from script block to give parser opportunity to do it's magic:"
-1..101 | foreach {
    # True for 1..100 and false for everything else
    &([scriptblock]::Create("a -a $_ -b $_"))
}

I guess comparing values types is not useful for real test code, because the expected answer is always false, unless you want to prove that there are some underlying micro-optimizations. So the best solution would be to throw an exception when the expected value is a ValueType or String.

Or is there a better solution?

nohwnd commented 7 years ago

At the moment I throw exception when int is provided, but should throw when any value type or string is provided, and provide -Force to skip the error and run the check. That way people still use this to prove quirks of the system, but won't get bitten in normal usage.