%% %CopyrightBegin% %% %% Copyright Ericsson AB 2013. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(map_SUITE). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2 ]). -export([ t_build_and_match_literals/1, t_update_literals/1,t_match_and_update_literals/1, t_guard_bifs/1, t_guard_sequence/1, t_guard_update/1, t_guard_receive/1, t_guard_fun/1, t_list_comprehension/1, t_map_sort_literals/1, t_size/1, t_map_size/1, %% BIFs t_bif_map_get/1, t_bif_map_find/1, t_bif_map_is_key/1, t_bif_map_keys/1, t_bif_map_new/1, t_bif_map_put/1, t_bif_map_remove/1, t_bif_map_values/1 ]). -include_lib("test_server/include/test_server.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [ t_build_and_match_literals, t_update_literals, t_match_and_update_literals, t_guard_bifs, t_guard_sequence, t_guard_update, t_guard_receive,t_guard_fun, t_list_comprehension, t_map_sort_literals, %% Specific Map BIFs t_bif_map_get,t_bif_map_find,t_bif_map_is_key, t_bif_map_keys,t_bif_map_new,t_bif_map_put, t_bif_map_remove,t_bif_map_values ]. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% tests t_build_and_match_literals(Config) when is_list(Config) -> #{} = id(#{}), #{1=>a} = id(#{1=>a}), #{1=>a,2=>b} = id(#{1=>a,2=>b}), #{1=>a,2=>b,3=>"c"} = id(#{1=>a,2=>b,3=>"c"}), #{1=>a,2=>b,3=>"c","4"=>"d"} = id(#{1=>a,2=>b,3=>"c","4"=>"d"}), #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>} = id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>}), #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"} = id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"}), #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g} = id(#{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g}), #{<<"hi all">> => 1} = id(#{<<"hi",32,"all">> => 1}), #{a=>X,a=>X=3,b=>4} = id(#{a=>3,b=>4}), % weird but ok =) #{ a=>#{ b=>#{c => third, b=>second}}, b=>first} = id(#{ b=>first, a=>#{ b=>#{c => third, b=> second}}}), M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}, M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first} = id(#{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first}), %% error case %V = 32, %{'EXIT',{{badmatch,_},_}} = (catch (#{<<"hi all">> => 1} = id(#{<<"hi",V,"all">> => 1}))), {'EXIT',{{badmatch,_},_}} = (catch (#{x=>3,x=>2} = id(#{x=>3}))), {'EXIT',{{badmatch,_},_}} = (catch (#{x=>2} = id(#{x=>3}))), {'EXIT',{{badmatch,_},_}} = (catch (#{x=>3} = id({a,b,c}))), {'EXIT',{{badmatch,_},_}} = (catch (#{x=>3} = id(#{y=>3}))), {'EXIT',{{badmatch,_},_}} = (catch (#{x=>3} = id(#{x=>"three"}))), ok. %% Tests size(Map). t_size(Config) when is_list(Config) -> % 0 = size(#{}), % 1 = size(#{a=>1}), % 1 = size(#{a=>#{a=>1}}), % 2 = size(#{a=>1, b=>2}), % 3 = size(#{a=>1, b=>2, b=>"3"}), ok. t_map_size(Config) when is_list(Config) -> 0 = map_size(id(#{})), 1 = map_size(id(#{a=>1})), 1 = map_size(id(#{a=>"wat"})), 2 = map_size(id(#{a=>1, b=>2})), 3 = map_size(id(#{a=>1, b=>2, b=>"3","33"=><<"n">>})), true = map_is_size(#{a=>1}, 1), true = map_is_size(#{a=>1, a=>2}, 2), M = #{ "a" => 1, "b" => 2}, true = map_is_size(M#{ "a" => 2}, 2), false = map_is_size(M#{ "a" => 2}, 3), %% Error cases. {'EXIT',{badarg,_}} = (catch map_size([])), {'EXIT',{badarg,_}} = (catch map_size(<<1,2,3>>)), {'EXIT',{badarg,_}} = (catch map_size(1)), ok. map_is_size(M,N) when map_size(M) =:= N -> true; map_is_size(_,_) -> false. % test map updates without matching t_update_literals(Config) when is_list(Config) -> Map = #{x=>1,y=>2,z=>3,q=>4}, #{x=>"d",q=>"4"} = loop_update_literals_x_q(Map, [ {"a","1"},{"b","2"},{"c","3"},{"d","4"} ]), ok. loop_update_literals_x_q(Map, []) -> Map; loop_update_literals_x_q(Map, [{X,Q}|Vs]) -> loop_update_literals_x_q(Map#{q=>Q,x=>X},Vs). % test map updates with matching t_match_and_update_literals(Config) when is_list(Config) -> Map = #{x=>0,y=>"untouched",z=>"also untouched",q=>1}, #{x=>16,q=>21,y=>"untouched",z=>"also untouched"} = loop_match_and_update_literals_x_q(Map, [ {1,2},{3,4},{5,6},{7,8} ]), M0 = id(#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number, 18446744073709551629 => wat}), M1 = id(#{}), M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number, 18446744073709551629 => wat}, M0 = M2, #{ 4 => another_number, int => 3 } = M2#{ 4 => another_number }, ok. loop_match_and_update_literals_x_q(Map, []) -> Map; loop_match_and_update_literals_x_q(#{q=>Q0,x=>X0} = Map, [{X,Q}|Vs]) -> loop_match_and_update_literals_x_q(Map#{q=>Q0+Q,x=>X0+X},Vs). t_guard_bifs(Config) when is_list(Config) -> true = map_guard_head(#{a=>1}), false = map_guard_head([]), true = map_guard_body(#{a=>1}), false = map_guard_body({}), true = map_guard_pattern(#{a=>1, <<"hi">> => "hi" }), false = map_guard_pattern("list"), ok. map_guard_head(M) when is_map(M) -> true; map_guard_head(_) -> false. map_guard_body(M) -> is_map(M). map_guard_pattern(#{}) -> true; map_guard_pattern(_) -> false. t_guard_sequence(Config) when is_list(Config) -> {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}), {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}), {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}), {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}), {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}), {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})), {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})), {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})), {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})), {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})), %% error case {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})), {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})), ok. map_guard_sequence_1(#{seq=>1=Seq, val=>Val}) -> {Seq,Val}; map_guard_sequence_1(#{seq=>2=Seq, val=>Val}) -> {Seq,Val}; map_guard_sequence_1(#{seq=>3=Seq, val=>Val}) -> {Seq,Val}; map_guard_sequence_1(#{seq=>4=Seq, val=>Val}) -> {Seq,Val}; map_guard_sequence_1(#{seq=>5=Seq, val=>Val}) -> {Seq,Val}. map_guard_sequence_2(#{ a=>3 }=M) -> {1, M}; map_guard_sequence_2(#{ a=>4 }=M) -> {2, M}; map_guard_sequence_2(#{ a=>X, a=>X, b=>4 }=M) -> {3,X,M}; map_guard_sequence_2(#{ a=>X, a=>Y, b=>3 }=M) when X =:= Y -> {4,X,Y,M}; map_guard_sequence_2(#{ a=>X, a=>Y }=M) when X =:= Y -> {5,X,Y,M}. t_guard_update(Config) when is_list(Config) -> error = map_guard_update(#{},#{}), first = map_guard_update(#{}, #{x=>first}), second = map_guard_update(#{y=>old}, #{x=>second,y=>old}), ok. map_guard_update(M1, M2) when M1#{x=>first} =:= M2 -> first; map_guard_update(M1, M2) when M1#{x=>second} =:= M2 -> second; map_guard_update(_, _) -> error. t_guard_receive(Config) when is_list(Config) -> M0 = #{ id => 0 }, Pid = spawn_link(fun() -> guard_receive_loop() end), Big = 36893488147419103229, B1 = <<"some text">>, B2 = <<"was appended">>, B3 = <>, #{id=>1, res=>Big} = M1 = call(Pid, M0#{op=>sub,in=>{1 bsl 65, 3}}), #{id=>2, res=>26} = M2 = call(Pid, M1#{op=>idiv,in=>{53,2}}), #{id=>3, res=>832} = M3 = call(Pid, M2#{op=>imul,in=>{26,32}}), #{id=>4, res=>4} = M4 = call(Pid, M3#{op=>add,in=>{1,3}}), #{id=>5, res=>Big} = M5 = call(Pid, M4#{op=>sub,in=>{1 bsl 65, 3}}), #{id=>6, res=>B3} = M6 = call(Pid, M5#{op=>"append",in=>{B1,B2}}), #{id=>7, res=>4} = _ = call(Pid, M6#{op=>add,in=>{1,3}}), %% update old maps and check id update #{id=>2, res=>B3} = call(Pid, M1#{op=>"append",in=>{B1,B2}}), #{id=>5, res=>99} = call(Pid, M4#{op=>add,in=>{33, 66}}), %% cleanup done = call(Pid, done), ok. call(Pid, M) -> Pid ! {self(), M}, receive {Pid, Res} -> Res end. guard_receive_loop() -> receive {Pid, #{ id=>Id, op=>"append", in=>{X,Y}}=M} when is_binary(X), is_binary(Y) -> Pid ! {self(), M#{ id=>Id+1, res=><>}}, guard_receive_loop(); {Pid, #{ id=>Id, op=>add, in=>{X,Y}}} -> Pid ! {self(), #{ id=>Id+1, res=>X+Y}}, guard_receive_loop(); {Pid, #{ id=>Id, op=>sub, in=>{X,Y}}=M} -> Pid ! {self(), M#{ id=>Id+1, res=>X-Y}}, guard_receive_loop(); {Pid, #{ id=>Id, op=>idiv, in=>{X,Y}}=M} -> Pid ! {self(), M#{ id=>Id+1, res=>X div Y}}, guard_receive_loop(); {Pid, #{ id=>Id, op=>imul, in=>{X,Y}}=M} -> Pid ! {self(), M#{ id=>Id+1, res=>X * Y}}, guard_receive_loop(); {Pid, done} -> Pid ! {self(), done}; {Pid, Other} -> Pid ! {error, Other}, guard_receive_loop() end. t_list_comprehension(Config) when is_list(Config) -> [#{k=>1},#{k=>2},#{k=>3}] = [#{k=>I} || I <- [1,2,3]], ok. t_guard_fun(Config) when is_list(Config) -> F1 = fun (#{s=>v,v=>V}) -> {v,V}; (#{s=>t,v=>{V,V}}) -> {t,V}; (#{s=>l,v=>[V,V]}) -> {l,V} end, F2 = fun (#{s=>T,v=>{V,V}}) -> {T,V}; (#{s=>T,v=>[V,V]}) -> {T,V}; (#{s=>T,v=>V}) -> {T,V} end, V = <<"hi">>, {v,V} = F1(#{s=>v,v=>V}), {t,V} = F1(#{s=>t,v=>{V,V}}), {l,V} = F1(#{s=>l,v=>[V,V]}), {v,V} = F2(#{s=>v,v=>V}), {t,V} = F2(#{s=>t,v=>{V,V}}), {l,V} = F2(#{s=>l,v=>[V,V]}), %% error case {'EXIT', {function_clause,[{?MODULE,_,[#{s=>none,v=>none}],_}|_]}} = (catch F1(#{s=>none,v=>none})), ok. t_map_sort_literals(Config) when is_list(Config) -> % test relation %% size order true = #{ a => 1, b => 2} < id(#{ a => 1, b => 1, c => 1}), true = #{ b => 1, a => 1} < id(#{ c => 1, a => 1, b => 1}), false = #{ c => 1, b => 1, a => 1} < id(#{ c => 1, a => 1}), %% key order true = #{ a => 1 } < id(#{ b => 1}), false = #{ b => 1 } < id(#{ a => 1}), true = #{ a => 1, b => 1, c => 1 } < id(#{ b => 1, c => 1, d => 1}), true = #{ b => 1, c => 1, d => 1 } > id(#{ a => 1, b => 1, c => 1}), true = #{ c => 1, b => 1, a => 1 } < id(#{ b => 1, c => 1, d => 1}), true = #{ "a" => 1 } < id(#{ <<"a">> => 1}), false = #{ <<"a">> => 1 } < id(#{ "a" => 1}), false = #{ 1 => 1 } < id(#{ 1.0 => 1}), false = #{ 1.0 => 1 } < id(#{ 1 => 1}), %% value order true = #{ a => 1 } < id(#{ a => 2}), false = #{ a => 2 } < id(#{ a => 1}), false = #{ a => 2, b => 1 } < id(#{ a => 1, b => 3}), true = #{ a => 1, b => 1 } < id(#{ a => 1, b => 3}), true = #{ "a" => "hi", b => 134 } == id(#{ b => 134,"a" => "hi"}), %% lists:sort SortVs = [#{"a"=>1},#{a=>2},#{1=>3},#{<<"a">>=>4}], [#{1=>ok},#{a=>ok},#{"a"=>ok},#{<<"a">>=>ok}] = lists:sort([#{"a"=>ok},#{a=>ok},#{1=>ok},#{<<"a">>=>ok}]), [#{1=>3},#{a=>2},#{"a"=>1},#{<<"a">>=>4}] = lists:sort(SortVs), [#{1=>3},#{a=>2},#{"a"=>1},#{<<"a">>=>4}] = lists:sort(lists:reverse(SortVs)), ok. %% BIFs t_bif_map_get(Config) when is_list(Config) -> 1 = map:get(a, #{ a=> 1}), 2 = map:get(b, #{ a=> 1, b => 2}), "hi" = map:get("hello", #{ a=>1, "hello" => "hi"}), "tuple hi" = map:get({1,1.0}, #{ a=>a, {1,1.0} => "tuple hi"}), M = id(#{ k1=>"v1", <<"k2">> => <<"v3">> }), "v4" = map:get(<<"k2">>, M#{ <<"k2">> => "v4" }), %% error case {'EXIT',{badarg,[{map,get,_,_}|_]}} = (catch map:get(a,[])), {'EXIT',{badarg,[{map,get,_,_}|_]}} = (catch map:get(a,<<>>)), {'EXIT',{bad_key,[{map,get,_,_}|_]}} = (catch map:get({1,1}, #{{1,1.0} => "tuple"})), {'EXIT',{bad_key,[{map,get,_,_}|_]}} = (catch map:get(a,#{})), {'EXIT',{bad_key,[{map,get,_,_}|_]}} = (catch map:get(a,#{ b=>1, c=>2})), ok. t_bif_map_find(Config) when is_list(Config) -> {ok, 1} = map:find(a, #{ a=> 1}), {ok, 2} = map:find(b, #{ a=> 1, b => 2}), {ok, "int"} = map:find(1, #{ 1 => "int"}), {ok, "int"} = map:find(1.0, #{ 1 => "int"}), {ok, "float"} = map:find(1, #{ 1.0 => "float"}), {ok, "float"} = map:find(1.0, #{ 1.0=> "float"}), {ok, "hi"} = map:find("hello", #{ a=>1, "hello" => "hi"}), {ok, "tuple hi"} = map:find({1.0,1}, #{ a=>a, {1,1.0} => "tuple hi"}), % reverse types in tuple key M = id(#{ k1=>"v1", <<"k2">> => <<"v3">> }), {ok, "v4"} = map:find(<<"k2">>, M#{ <<"k2">> => "v4" }), %% error case error = map:find(a,#{}), error = map:find(a,#{b=>1, c=>2}), {'EXIT',{badarg,[{map,find,_,_}|_]}} = (catch map:find(a,[])), {'EXIT',{badarg,[{map,find,_,_}|_]}} = (catch map:find(a,<<>>)), ok. t_bif_map_is_key(Config) when is_list(Config) -> M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, true = map:is_key("hi", M1), true = map:is_key(int, M1), true = map:is_key(<<"key">>, M1), true = map:is_key(4, M1), false = map:is_key(5, M1), false = map:is_key(<<"key2">>, M1), false = map:is_key("h", M1), false = map:is_key("hello", M1), false = map:is_key(atom, M1), false = map:is_key("hi", map:remove("hi", M1)), true = map:is_key("hi", M1), true = map:is_key(1, map:put(1, "number", M1)), false = map:is_key(1.0, map:put(1, "number", M1)), ok. t_bif_map_keys(Config) when is_list(Config) -> [] = map:keys(#{}), [1,2,3,4,5] = map:keys(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e}), [1,2,3,4,5] = map:keys(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c}), % values in key order: [4,int,"hi",<<"key">>] M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, [4,int,"hi",<<"key">>] = map:keys(M1), %% error case {'EXIT',{badarg,[{map,keys,_,_}|_]}} = (catch map:keys(1 bsl 65 + 3)), {'EXIT',{badarg,[{map,keys,_,_}|_]}} = (catch map:keys(154)), {'EXIT',{badarg,[{map,keys,_,_}|_]}} = (catch map:keys(atom)), {'EXIT',{badarg,[{map,keys,_,_}|_]}} = (catch map:keys([])), {'EXIT',{badarg,[{map,keys,_,_}|_]}} = (catch map:keys(<<>>)), ok. t_bif_map_new(Config) when is_list(Config) -> #{} = map:new(), 0 = erlang:map_size(map:new()), ok. t_bif_map_put(Config) when is_list(Config) -> M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number, 18446744073709551629 => wat}, M1 = #{ "hi" => "hello"} = map:put("hi", "hello", #{}), ["hi"] = map:keys(M1), ["hello"] = map:values(M1), M2 = #{ int => 3 } = map:put(int, 3, M1), [int,"hi"] = map:keys(M2), [3,"hello"] = map:values(M2), M3 = #{ <<"key">> => <<"value">> } = map:put(<<"key">>, <<"value">>, M2), [int,"hi",<<"key">>] = map:keys(M3), [3,"hello",<<"value">>] = map:values(M3), M4 = #{ 18446744073709551629 => wat } = map:put(18446744073709551629, wat, M3), [18446744073709551629,int,"hi",<<"key">>] = map:keys(M4), [wat,3,"hello",<<"value">>] = map:values(M4), M0 = #{ 4 => number } = M5 = map:put(4, number, M4), [4,18446744073709551629,int,"hi",<<"key">>] = map:keys(M5), [number,wat,3,"hello",<<"value">>] = map:values(M5), %% error case {'EXIT',{badarg,[{map,put,_,_}|_]}} = (catch map:put(1,a,1 bsl 65 + 3)), {'EXIT',{badarg,[{map,put,_,_}|_]}} = (catch map:put(1,a,154)), {'EXIT',{badarg,[{map,put,_,_}|_]}} = (catch map:put(1,a,atom)), {'EXIT',{badarg,[{map,put,_,_}|_]}} = (catch map:put(1,a,[])), {'EXIT',{badarg,[{map,put,_,_}|_]}} = (catch map:put(1,a,<<>>)), ok. t_bif_map_remove(Config) when is_list(Config) -> M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number, 18446744073709551629 => wat}, M1 = map:remove("hi", M0), [4,18446744073709551629,int,<<"key">>] = map:keys(M1), [number,wat,3,<<"value">>] = map:values(M1), M2 = map:remove(int, M1), [4,18446744073709551629,<<"key">>] = map:keys(M2), [number,wat,<<"value">>] = map:values(M2), M3 = map:remove(<<"key">>, M2), [4,18446744073709551629] = map:keys(M3), [number,wat] = map:values(M3), M4 = map:remove(18446744073709551629, M3), [4] = map:keys(M4), [number] = map:values(M4), M5 = map:remove(4, M4), [] = map:keys(M5), [] = map:values(M5), M0 = map:remove(5,M0), M0 = map:remove("hi there",M0), #{ "hi" => "hello", int => 3, 4 => number} = map:remove(18446744073709551629,map:remove(<<"key">>,M0)), %% error case {'EXIT',{badarg,[{map,remove,_,_}|_]}} = (catch map:remove(a,1 bsl 65 + 3)), {'EXIT',{badarg,[{map,remove,_,_}|_]}} = (catch map:remove(1,154)), {'EXIT',{badarg,[{map,remove,_,_}|_]}} = (catch map:remove(a,atom)), {'EXIT',{badarg,[{map,remove,_,_}|_]}} = (catch map:remove(1,[])), {'EXIT',{badarg,[{map,remove,_,_}|_]}} = (catch map:remove(a,<<>>)), ok. t_bif_map_values(Config) when is_list(Config) -> [] = map:values(#{}), [a,b,c,d,e] = map:values(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e}), [a,b,c,d,e] = map:values(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c}), % values in key order: [4,int,"hi",<<"key">>] M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number}, M2 = M1#{ "hi" => "hello2", <<"key">> => <<"value2">> }, [number,3,"hello2",<<"value2">>] = map:values(M2), [number,3,"hello",<<"value">>] = map:values(M1), %% error case {'EXIT',{badarg,[{map,values,_,_}|_]}} = (catch map:values(1 bsl 65 + 3)), {'EXIT',{badarg,[{map,values,_,_}|_]}} = (catch map:values(atom)), {'EXIT',{badarg,[{map,values,_,_}|_]}} = (catch map:values([])), {'EXIT',{badarg,[{map,values,_,_}|_]}} = (catch map:values(<<>>)), ok. %% Use this function to avoid compile-time evaluation of an expression. id(I) -> I.