[erlang-bugs] Maps equality failure / Maps merge failure

Jesper Louis Andersen jesper.louis.andersen@REDACTED
Tue Mar 24 15:16:27 CET 2015


Hi OTP team (and other readers),

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):

Erlang/OTP 17 [erts-6.3.1] [source] [64-bit] [smp:8:8] [ds:8:8:10]
[async-threads:10] [kernel-poll:false]

Eshell V6.3.1  (abort with ^G)
1> Map = #{0 => 0,2147483648 => 0}.
#{0 => 0,2147483648 => 0}
2> Bin = term_to_binary(M).
* 1: variable 'M' is unbound
3> Bin = term_to_binary(Map).
<<131,116,0,0,0,2,97,0,97,0,110,4,0,0,0,0,128,97,0>>
4> Map2 = binary_to_term(Bin).
#{2147483648 => 0,0 => 0}
5> Map =:= Map2.
false

(copied verbatim, including my error in 2>)

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:

6> Map.
#{0 => 0,2147483648 => 0}
7> Map2.
#{2147483648 => 0,0 => 0}
8>

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 :)

A second problem occurs in merges, which may be related or may not be
related. In the second problem, we are merging maps

[#{-2147483649 => 0,
   0 => 0,
   97 => 0,
   false => 0,
   flower => 0,
   #Fun<eqc_gen.133.121384563> => 0,
   #Fun<eqc_gen.133.121384563> => 0,
   <<>> => 0},
 #{0 => 1}]

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)

9> F1 = fun(_, _) -> 0 end,
9> F2 = fun(_, _) -> 1 end.

This defines two functions:

11> maps:from_list(
11> [{-2147483649, 0},
11>    {0,0}, {97, 0}, {false, 0}, {flower, 0}, {F1, 0}, {F2, 0}, {<<>>,
0}]).
#{-2147483649 => 0,
  0 => 0,
  97 => 0,
  false => 0,
  flower => 0,
  #Fun<erl_eval.12.90072148> => 0,
  #Fun<erl_eval.12.90072148> => 0,
  <<>> => 0}

Remarkably, this is well-defined, now lets merge:

12> maps:merge(v(11), #{0 => 1}).

#{0 => 1,
  -2147483649 => 0,
  0 => 0,
  97 => 0,
  false => 0,
  flower => 0,
  #Fun<erl_eval.12.90072148> => 0,
  #Fun<erl_eval.12.90072148> => 0,
  <<>> => 0}
13>

The resulting map now has to '0' keys! Rejoice!

The model I am using is the following:

https://github.com/jlouis/maps_eqc/tree/ac7748ce81781ae6c1ad7b4ed07ca5cd935c125d

(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).

To run:

make:all([load]).
eqc:module({testing_budget, 20}, maps_iso_eqc).

Two test runs of mine:

https://gist.github.com/jlouis/39de114e0a447af983a5

Note the latter merge test which managed to shrink over 1200 times!


-- 
J.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-bugs/attachments/20150324/e560383f/attachment.htm>


More information about the erlang-bugs mailing list