%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2000-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(select_SUITE). -author('pan@erix.ericsson.se'). -export([test/0]). %% %% Define to run outside of test server %% %%-define(STANDALONE,1). %% %% Define for debug output %% %%-define(debug,1). -ifdef(STANDALONE). -define(config(A,B),config(A,B)). -export([config/2]). -else. -include_lib("common_test/include/ct.hrl"). -endif. -define(fmt(A,B), io:format(A, B)). -ifdef(debug). -ifdef(STANDALONE). -define(line, erlang:display({?MODULE,?LINE}), ). -endif. -define(dbgformat(A,B),io:format(A,B)). -else. -ifdef(STANDALONE). -define(line, noop, ). -endif. -define(dbgformat(A,B),noop). -endif. -ifdef(STANDALONE). config(priv_dir,_) -> ".". -else. %% When run in test server. -export([all/0, suite/0, select_test/1, return_values/1]). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. all() -> [return_values, select_test]. %% Test select in numerous ways. select_test(Config) when is_list(Config) -> ct:timetrap({minutes,40}), %% valgrinds needs a lot of time do_test(Config). %% Test return values in specific situations for select/3 and select/1. return_values(Config) when is_list(Config) -> do_return_values(). -endif. table_factor({dets,_}) -> 1; table_factor({ets,_}) -> 100. gen_dets_filename(Config,N) -> filename:join(proplists:get_value(priv_dir,Config), "testdets_" ++ integer_to_list(N) ++ ".dets"). create_tables(Config) -> Hash = ets:new(xxx, []), Tree = ets:new(yyy, [ordered_set]), Bag = ets:new(yyy, [bag]), DBag = ets:new(yyy, [duplicate_bag]), F1 = gen_dets_filename(Config,1), (catch file:delete(F1)), {ok,DetsPlain} = dets:open_file(testdets_1, [{file, F1}]), F3 = gen_dets_filename(Config,3), (catch file:delete(F3)), {ok,DetsBag} = dets:open_file(testdets_3, [{file, F3},{type, bag}]), F4 = gen_dets_filename(Config,4), (catch file:delete(F4)), {ok,DetsDBag} = dets:open_file(testdets_4, [{file, F4},{type, duplicate_bag}]), [{ets,Hash}, {ets,Tree}, {ets,Bag}, {ets,DBag}, {dets, DetsPlain}, {dets, DetsBag}, {dets, DetsDBag}]. gen_key(N,list) -> [N,N+1,N+2]; gen_key(N,tuple) -> {N,N+1,N+2}; gen_key(N,complex) -> {[N,N+1],N+2}. gen_fun(N) -> fun() -> N end. gen_bin(N) -> list_to_binary(integer_to_list(N)). gen_object(N,Type) -> L = integer_to_list(N), A = list_to_atom("a" ++ L), {gen_key(N,Type), L, A, gen_fun(N), gen_bin(N)}. gen_object1(N,Type) -> L = integer_to_list(N), A = list_to_atom("a" ++ L), {gen_key(N,Type), A, L, gen_fun(N), gen_bin(N)}. fill_table(_,0,_) -> ok; fill_table({Mod,Tab},N,Type) -> Obj1 = gen_object1(N,Type), Obj = gen_object(N,Type), Mod:insert(Tab, Obj1), case Mod:info(Tab,type) of bag -> Mod:insert(Tab, Obj); duplicate_bag -> Mod:insert(Tab, Obj), Mod:insert(Tab, Obj1); _ -> ok end, fill_table({Mod,Tab},N-1,Type). table_size(Tab) -> 15 *table_factor(Tab). build_tables(Config,Type) -> L = create_tables(Config), ?dbgformat("Tables: ~p~n",[L]), lists:foreach(fun(TD) -> fill_table(TD,table_size(TD),Type) end, L), L. destroy_tables([]) -> ok; destroy_tables([{ets,Tab}|T]) -> ets:delete(Tab), destroy_tables(T); destroy_tables([{dets,Tab}|T]) -> dets:close(Tab), destroy_tables(T). init_random(Config) -> WriteDir = ReadDir = proplists:get_value(priv_dir,Config), (catch file:make_dir(WriteDir)), Seed = case file:consult(filename:join([ReadDir, "preset_random_seed2.txt"])) of {ok,[X]} -> X; _ -> rand:seed(exsplus), rand:export_seed() end, rand:seed(Seed), {ok, F} = file:open(filename:join([WriteDir, "last_random_seed2.txt"]), [write]), io:format(F,"~p. ~n",[Seed]), file:close(F), ok. create_random_key(N,Type) -> gen_key(rand:uniform(N),Type). create_pb_key(N,list) -> X = rand:uniform(N), case rand:uniform(4) of 3 -> {[X, X+1, '_'], fun([Z,Z1,P1]) -> [Z,Z1,P1] =:= [X,X+1,P1] end}; 2 -> {[X, '_', '_'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end}; 1 -> {[X, X+1, '$1'], fun([Z,Z1,P1]) -> [Z,Z1,P1] =:= [X,X+1,P1] end}; _ -> {[X, '$1', '$2'], fun([Z,P1,P2]) -> [Z,P1,P2] =:= [X,P1,P2] end} end; create_pb_key(N, tuple) -> X = rand:uniform(N), case rand:uniform(2) of 1 -> {{X, X+1, '$1'},fun({Z,Z1,P1}) -> {Z,Z1,P1} =:= {X,X+1,P1} end}; _ -> {{X, '$1', '$2'},fun({Z,P1,P2}) -> {Z,P1,P2} =:= {X,P1,P2} end} end; create_pb_key(N, complex) -> X = rand:uniform(N), case rand:uniform(2) of 1 -> {{[X, X+1], '$1'}, fun({[Z,Z1],P1}) -> {[Z,Z1],P1} =:= {[X,X+1],P1} end}; _ -> {{[X, '$1'], '$2'},fun({[Z,P1],P2}) -> {[Z,P1],P2} =:= {[X,P1],P2} end} end. table_foldl(_Fun, Acc,{_Mod,_Tab},'$end_of_table') -> Acc; table_foldl(Fun, Acc,{Mod,Tab},Key) -> Objs = Mod:lookup(Tab,Key), Acc2 = lists:foldl(Fun,Acc,Objs), ?dbgformat("Objs: ~p, Acc2: ~p~n",[Objs,Acc2]), table_foldl(Fun, Acc2, {Mod,Tab}, Mod:next(Tab,Key)). table_foldl(Fun, Acc,{Mod,Tab}) -> table_foldl(Fun, Acc,{Mod,Tab},Mod:first(Tab)). chunked_select(Mod,Tab,MS,0) -> Mod:select(Tab,MS); chunked_select(Mod,Tab,MS,Chunk) when Chunk > 0-> do_chunk_select(Mod, Mod:select(Tab,MS,Chunk),[]); chunked_select(Mod,Tab,MS,Chunk) when Chunk < 0-> case Mod of ets -> do_chunk_select_reverse(Mod, Mod:select_reverse(Tab,MS,-Chunk),[]); _ -> chunked_select(Mod,Tab,MS,-Chunk) end. do_chunk_select_reverse(_Mod, '$end_of_table',Acc) -> %% OK, all this reversing is only needed for ordered_set, but %% this is only testcases, right? erlang:display(did_chunked_select_reverse), Acc; do_chunk_select_reverse(Mod, {L,C},Acc) -> NewAcc = lists:reverse(L)++Acc, do_chunk_select(Mod, Mod:select(C), NewAcc). do_chunk_select(_Mod, '$end_of_table',Acc) -> %% OK, all this reversing is only needed for ordered_set, but %% this is only testcases, right? lists:reverse(Acc); do_chunk_select(Mod, {L,C},Acc) -> NewAcc = lists:reverse(L)++Acc, do_chunk_select(Mod, Mod:select(C), NewAcc). cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2) -> cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, 0). cmp_ms_to_fun({Mod,Tab}, MS, Fun1, Fun2, ChunkSize) -> MSRes = lists:sort(chunked_select(Mod,Tab,MS,ChunkSize)), FunRes0 = table_foldl(Fun1,[],{Mod,Tab}), FunRes = case Fun2 of F when is_function(F) -> FunRes1 = table_foldl(F,[],{Mod,Tab}), lists:merge(FunRes0,FunRes1); [] -> lists:sort(FunRes0) end, case MSRes =:= FunRes of true -> true; false -> ?fmt("Match_spec result differs from fun result:~n",[]), ?fmt("Parameters: ~p,~p,~p,~p~n", [{Mod,Tab}, MS, Fun1, Fun2]), ?fmt("Match_spec Result: ~p~n", [MSRes]), ?fmt("Fun Result: ~p~n", [FunRes]), Info = (catch Mod:info(Tab)), ?fmt("Table info:~p~n", [Info]), {'EXIT', {hej, ST}} = (catch erlang:error(hej)), ?fmt("Stack backtrace: ~p~n", [ST]), erlang:error(badmatch) end. do_n(0,_) -> ok; do_n(N,Fun) -> Fun(), do_n(N-1,Fun). %% %% We want some misses too, so pretend the tables are slightly %% larger than they really are. %% num_els(Tab) -> 16 * table_factor(Tab). test() -> do_return_values(), do_test([]). do_test(Config) -> init_random(Config), whitebox(), lists:foreach(fun(Type) -> Tabs = build_tables(Config,Type), basic_key(Tabs,Type), ?fmt("basic_key done for type ~w~n",[Type]), basic_pb_key(Tabs,Type), ?fmt("basic_pb_key done for type ~w~n",[Type]), double_pb_key(Tabs,Type), ?fmt("double_pb_key done for type ~w~n",[Type]), multi_key(Tabs,Type), ?fmt("multi_key done for type ~w~n",[Type]), multi_mixed_key(Tabs,Type), ?fmt("multi_mixed_key done for type ~w~n", [Type]), destroy_tables(Tabs) end, [tuple, list, complex]), ok. basic_key(Tabs,Type) -> Fun = fun() -> lists:map(fun(Tab) -> Key = create_random_key(num_els(Tab),Type), MS = [{{Key,'_','_','_','_'},[],['$_']}], MF = fun({Key0,A,B,F,Bi},Acc) -> case Key =:= Key0 of true -> [{Key0,A,B,F,Bi} | Acc]; _ -> Acc end end, cmp_ms_to_fun(Tab,MS,MF,[]) end, Tabs) end, do_n(50,Fun), ok. basic_pb_key(Tabs,Type) -> InnerFun = fun(Tab) -> {Key,KeyFun} = create_pb_key(num_els(Tab),Type), MS = [{{Key,'_','_','_','_'},[],['$_']}], MF = fun({Key0,A,B,F,Bi},Acc) -> case KeyFun(Key0) of true -> [{Key0,A,B,F,Bi} | Acc]; _ -> Acc end end, cmp_ms_to_fun(Tab,MS,MF,[]) end, {Etses, Detses} = split_by_type(Tabs), FunEts = fun() -> lists:foreach(InnerFun, Etses) end, FunDets = fun() -> lists:foreach(InnerFun, Detses) end, do_n(table_factor(hd(Etses)) div 2,FunEts), do_n(10,FunDets), ok. double_pb_key(Tabs,Type) -> InnerFun = fun(Tab) -> {KeyA,KeyFunA} = create_pb_key(num_els(Tab),Type), {KeyB,KeyFunB} = create_pb_key(num_els(Tab),Type), MS = [{{KeyA,'_','_','_','_'},[],['$_']}, {{KeyB,'_','_','_','_'},[],['$_']}], ?dbgformat("Tab: ~p, MS: ~p~n", [Tab,MS]), MF = fun({Key0,A,B,F,Bi},Acc) -> case KeyFunA(Key0) of true -> ?dbgformat ("FunMatched:" " ~p~n", [{Key0,A, B,F,Bi}]), [{Key0,A,B,F,Bi} | Acc]; _ -> case KeyFunB(Key0) of true -> ?dbgformat ("Fun" "Matched:" " ~p~n", [{Key0,A, B,F, Bi}]), [{Key0,A,B, F,Bi} | Acc]; _ -> Acc end end end, cmp_ms_to_fun(Tab,MS,MF,[]) end, {Etses, Detses} = split_by_type(Tabs), FunEts = fun() -> lists:foreach(InnerFun, Etses) end, FunDets = fun() -> lists:foreach(InnerFun, Detses) end, do_n(table_factor(hd(Etses)) div 2,FunEts), do_n(10,FunDets), ok. multi_key(Tabs,Type) -> Fun = fun() -> lists:map(fun(Tab) -> KeyA = create_random_key(num_els(Tab),Type), KeyB = create_random_key(num_els(Tab),Type), KeyC = create_random_key(num_els(Tab),Type), KeyD = create_random_key(num_els(Tab),Type), KeyE = create_random_key(num_els(Tab),Type), KeyF = create_random_key(num_els(Tab),Type), KeyG = create_random_key(num_els(Tab),Type), KeyH = create_random_key(num_els(Tab),Type), KeyI = create_random_key(num_els(Tab),Type), KeyJ = create_random_key(num_els(Tab),Type), KeyK = create_random_key(num_els(Tab),Type), KeyL = create_random_key(num_els(Tab),Type), MS = [{{KeyA,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyB,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyC,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyD,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyE,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyF,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyG,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyH,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyI,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyJ,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyK,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyL,'$1','_','$2','_'},[], [{{'$1','$2'}}]} ], ?dbgformat("Tab: ~p, MS: ~p~n", [Tab,MS]), MF = fun({Key0,A,_B,F,_Bi},Acc) -> case Key0 of KeyA -> [ {A,F} | Acc]; KeyB -> [ {A,F} | Acc]; KeyC -> [ {A,F} | Acc]; KeyD -> [ {A,F} | Acc]; KeyE -> [ {A,F} | Acc]; KeyF -> [ {A,F} | Acc]; KeyG -> [ {A,F} | Acc]; KeyH -> [ {A,F} | Acc]; KeyI -> [ {A,F} | Acc]; KeyJ -> [ {A,F} | Acc]; KeyK -> [ {A,F} | Acc]; KeyL -> [ {A,F} | Acc]; _ -> Acc end end, cmp_ms_to_fun(Tab,MS,MF,[]) end, Tabs) end, do_n(33,Fun), ok. multi_mixed_key(Tabs,Type) -> InnerFun = fun(Tab) -> KeyA = create_random_key(num_els(Tab),Type), KeyB = create_random_key(num_els(Tab),Type), KeyC = create_random_key(num_els(Tab),Type), KeyD = create_random_key(num_els(Tab),Type), {KeyE, FunE} = create_pb_key(num_els(Tab),Type), KeyF = create_random_key(num_els(Tab),Type), {KeyG, FunG} = create_pb_key(num_els(Tab),Type), KeyH = create_random_key(num_els(Tab),Type), KeyI = create_random_key(num_els(Tab),Type), {KeyJ, FunJ} = create_pb_key(num_els(Tab),Type), KeyK = create_random_key(num_els(Tab),Type), KeyL = create_random_key(num_els(Tab),Type), MS = [{{KeyA,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyB,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyC,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyD,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyE,'$100','_','$200','_'},[], [{{'$100','$200'}}]}, {{KeyF,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyG,'$100','_','$200','_'},[], [{{'$100','$200'}}]}, {{KeyH,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyI,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyJ,'$100','_','$200','_'},[], [{{'$100','$200'}}]}, {{KeyK,'$1','_','$2','_'},[], [{{'$1','$2'}}]}, {{KeyL,'$1','_','$2','_'},[], [{{'$1','$2'}}]} ], ?dbgformat("Tab: ~p, MS: ~p~n", [Tab,MS]), MF = fun({Key0,A,_B,F,_Bi},Acc) -> case Key0 of KeyA -> [ {A,F} | Acc]; KeyB -> [ {A,F} | Acc]; KeyC -> [ {A,F} | Acc]; KeyD -> [ {A,F} | Acc]; KeyF -> [ {A,F} | Acc]; KeyH -> [ {A,F} | Acc]; KeyI -> [ {A,F} | Acc]; KeyK -> [ {A,F} | Acc]; KeyL -> [ {A,F} | Acc]; Else -> case FunE(Else) or FunG(Else) or FunJ(Else) of true -> [ {A,F} | Acc]; _ -> Acc end end end, cmp_ms_to_fun(Tab,MS,MF,[]), case Tab of {ets,_} -> cmp_ms_to_fun(Tab,MS,MF,[],1), cmp_ms_to_fun(Tab,MS,MF,[],10), cmp_ms_to_fun(Tab,MS,MF,[],1000000), cmp_ms_to_fun(Tab,MS,MF,[],-1), cmp_ms_to_fun(Tab,MS,MF,[],-10), cmp_ms_to_fun(Tab,MS,MF,[],-1000000); _ -> ok end end, {Etses, Detses} = split_by_type(Tabs), FunEts = fun() -> lists:foreach(InnerFun, Etses) end, FunDets = fun() -> lists:foreach(InnerFun, Detses) end, do_n(table_factor(hd(Etses)) div 2,FunEts), do_n(table_factor(hd(Detses)) div 2,FunDets), ok. split_by_type(List) -> split_by_type(List,[],[]). split_by_type([],AccEts,AccDets) -> {AccEts,AccDets}; split_by_type([{dets,Tab}|T],AccEts,AccDets) -> split_by_type(T,AccEts,[{dets,Tab}|AccDets]); split_by_type([{ets,Tab}|T],AccEts,AccDets) -> split_by_type(T,[{ets,Tab}|AccEts],AccDets). whitebox() -> ets:new(xxx,[named_table, ordered_set]), ets:new(yyy,[named_table]), E = fun(0,_)->ok; (N,F) -> ets:insert(xxx,{N,N rem 10}), ets:insert(yyy,{N,N rem 10}), F(N-1,F) end, E(10000,E), G = fun(F,C,A) -> case ets:select(C) of {L,C2} -> F(F,C2,A+length(L)); '$end_of_table' -> A end end, H=fun({L,C}) -> G(G,C,length(L)) end, 1 = H(ets:select(xxx,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)), 10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],1)), 1 = H(ets:select(yyy,[{{'$1','$2'},[{'<','$1',2}],['$_']}],7)), 10000 = H(ets:select(yyy,[{{'$1','$2'},[],['$_']}],1)), {[{5,5}],_} = ets:select(xxx,[{{5,'$2'},[],['$_']}],1), {[{5,5}],_} = ets:select(yyy,[{{5,'$2'},[],['$_']}],1), I = fun(_,0) -> ok; (I,N) -> 10000 = H(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)), I(I,N-1) end, I(I,2000), J = fun(F,C,A) -> case ets:select(C) of {L,C2} -> F(F,C2,lists:reverse(L)++A); '$end_of_table' -> lists:reverse(A) end end, K = fun({L,C}) -> J(J,C,lists:reverse(L)) end, M = fun(_, _, 0) -> ok; (F, What, N) -> What = K(ets:select(xxx,[{{'$1','$2'},[],['$_']}],N)), F(F, What, N-1) end, N = fun(HM) -> What = ets:select(xxx,[{{'$1','$2'},[],['$_']}]), What = lists:sort(What), M(M, What, HM) end, N(2000), ets:delete(xxx), ets:delete(yyy). do_return_values() -> T = ets:new(xxx,[ordered_set]), U = ets:new(xxx,[]), '$end_of_table' = ets:select(T,[{'_',[],['$_']}],1), '$end_of_table' = ets:select(U,[{'_',[],['$_']}],1), ets:insert(T,{ett,1}), ets:insert(U,{ett,1}), {[{ett,1}],C1} = ets:select(T,[{'_',[],['$_']}],1), '$end_of_table' = ets:select(C1), {[{ett,1}],C2} = ets:select(U,[{'_',[],['$_']}],1), '$end_of_table' = ets:select(C2), {[{ett,1}],C3} = ets:select(T,[{'_',[],['$_']}],2), '$end_of_table' = ets:select(C3), {[{ett,1}],C4} = ets:select(U,[{'_',[],['$_']}],2), '$end_of_table' = ets:select(C4), E = fun(0,_)->ok; (N,F) -> ets:insert(T,{N,N rem 10}), ets:insert(U,{N,N rem 10}), F(N-1,F) end, E(10000,E), '$end_of_table' = ets:select(T,[{{hej, hopp},[],['$_']}],1), '$end_of_table' = ets:select(U,[{{hej,hopp},[],['$_']}],1), {[{ett,1}],CC1} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],1), '$end_of_table' = ets:select(CC1), {[{ett,1}],CC2} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],1), '$end_of_table' = ets:select(CC2), {[{ett,1}],CC3} = ets:select(T,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],2), '$end_of_table' = ets:select(CC3), {[{ett,1}],CC4} = ets:select(U,[{{'$1','_'},[{is_atom, '$1'}], ['$_']}],2), '$end_of_table' = ets:select(CC4), ets:delete(T), ets:delete(U), V = ets:new(xxx,[{keypos, 4}]), X = ets:new(xxx,[ordered_set, {keypos, 4}]), ets:insert(V,{1,1,1,ett}), ets:insert(X,{1,1,1,ett}), '$end_of_table' = ets:select(V,[{{1,1,1},[],['$_']}],1), '$end_of_table' = ets:select(X,[{{1,1,1},[],['$_']}],1), ets:delete(V), ets:delete(X), ok.