<div dir="ltr">Hi OTP team (and other readers),<div><br></div><div>Over the weekend, I've been building up a QuickCheck model of Erlang/OTP maps(). This is to be able to properly test the HAMT maps of the up-and-coming 18.0 release for correctness, but I'm testing on 17.4.1 as well. The following problem occurs for release 17.4.1. (for the curious, 18.0 currently segfaults on these tests):</div><div><br></div><div><div>Erlang/OTP 17 [erts-6.3.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [kernel-poll:false]</div><div><br></div><div>Eshell V6.3.1  (abort with ^G)</div><div>1> Map = #{0 => 0,2147483648 => 0}.</div><div>#{0 => 0,2147483648 => 0}</div><div>2> Bin = term_to_binary(M).</div><div>* 1: variable 'M' is unbound</div><div>3> Bin = term_to_binary(Map).</div><div><<131,116,0,0,0,2,97,0,97,0,110,4,0,0,0,0,128,97,0>></div><div>4> Map2 = binary_to_term(Bin).</div><div>#{2147483648 => 0,0 => 0}</div><div>5> Map =:= Map2.</div><div>false</div><div><br></div><div>(copied verbatim, including my error in 2>)</div><div><br></div><div>The problem is that if I convert the map to a binary and then back again, the two maps are not equal. Furthermore, they print differently:</div><div><br></div><div><div>6> Map.</div><div>#{0 => 0,2147483648 => 0}</div><div>7> Map2.</div><div>#{2147483648 => 0,0 => 0}</div><div>8> </div></div><div><br></div><div>The value 2147483648 seems important and it is 2^31. However, the problem also occurs at 2^31 + 1. Only testing with "small" integers poses no problem at all. I have yet to test against floating point numbers and this requires some additional attention to detail as they have fun equality rules :)</div><div><br></div><div>A second problem occurs in merges, which may be related or may not be related. In the second problem, we are merging maps</div><div><br></div><div><div>[#{-2147483649 => 0,</div><div>   0 => 0,</div><div>   97 => 0,</div><div>   false => 0,</div><div>   flower => 0,</div><div>   #Fun<eqc_gen.133.121384563> => 0,</div><div>   #Fun<eqc_gen.133.121384563> => 0,</div><div>   <<>> => 0},</div><div> #{0 => 1}]</div></div><div><br></div><div>where the #Fun's are generated functions of 0 and 2 parameters respectively. Lets see how to create such a map (one might note that direct creation through #{ ... } syntax would make the keys which are functions illegal, but I am an experiened Erlang programmer and have a way around such petty limitations :P)</div><div><br></div><div><div>9> F1 = fun(_, _) -> 0 end,</div><div>9> F2 = fun(_, _) -> 1 end.</div></div><div><br></div><div>This defines two functions:</div><div><br></div><div><div>11> maps:from_list(</div><div>11> [{-2147483649, 0},</div><div>11>    {0,0}, {97, 0}, {false, 0}, {flower, 0}, {F1, 0}, {F2, 0}, {<<>>, 0}]).</div><div>#{-2147483649 => 0,</div><div>  0 => 0,</div><div>  97 => 0,</div><div>  false => 0,</div><div>  flower => 0,</div><div>  #Fun<erl_eval.12.90072148> => 0,</div><div>  #Fun<erl_eval.12.90072148> => 0,</div><div>  <<>> => 0}</div></div><div><br></div><div>Remarkably, this is well-defined, now lets merge:</div><div><br></div><div><div>12> maps:merge(v(11), #{0 => 1}).                                             </div><div>#{0 => 1,</div><div>  -2147483649 => 0,</div><div>  0 => 0,</div><div>  97 => 0,</div><div>  false => 0,</div><div>  flower => 0,</div><div>  #Fun<erl_eval.12.90072148> => 0,</div><div>  #Fun<erl_eval.12.90072148> => 0,</div><div>  <<>> => 0}</div><div>13> </div></div><div><br></div><div>The resulting map now has to '0' keys! Rejoice!</div><div><br></div><div>The model I am using is the following:</div><div><br></div><div><a href="https://github.com/jlouis/maps_eqc/tree/ac7748ce81781ae6c1ad7b4ed07ca5cd935c125d">https://github.com/jlouis/maps_eqc/tree/ac7748ce81781ae6c1ad7b4ed07ca5cd935c125d</a><br></div><div><br></div><div>(maps_iso_eqc are simple stateless tests, whereas maps_eqc is a more complicated stateful test which also verifies properties about copying maps between processes and that maps are persistent data structures).</div><div><br></div><div>To run:</div><div><br></div><div>make:all([load]).</div><div>eqc:module({testing_budget, 20}, maps_iso_eqc).</div><div><br></div><div>Two test runs of mine:</div><div><br></div><div><a href="https://gist.github.com/jlouis/39de114e0a447af983a5">https://gist.github.com/jlouis/39de114e0a447af983a5</a><br></div><div><br></div><div>Note the latter merge test which managed to shrink over 1200 times!</div><div><br></div><div><br></div>-- <br><div class="gmail_signature">J.</div>
</div></div>