%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1999-2011. 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% %% %%%---------------------------------------------------------------------- %%% Purpose : Tests the safe_fixtable functions in both ets and dets. %%%---------------------------------------------------------------------- -module(fixtable_SUITE). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). %%% Test cases -export([multiple_fixes/1, multiple_processes/1, other_process_deletes/1, owner_dies/1, other_process_closes/1,insert_same_key/1]). -export([fixbag/1]). -export([init_per_testcase/2, end_per_testcase/2]). %%% Internal exports -export([command_loop/0,start_commander/0]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [multiple_fixes, multiple_processes, other_process_deletes, owner_dies, other_process_closes, insert_same_key, fixbag]. groups() -> []. init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. -include_lib("test_server/include/test_server.hrl"). %%% I wrote this thinking I would use more than one temporary at a time, but %%% I wasn't... Well, maybe in the future... -define(DETS_TEMPORARIES, [tmp1]). -define(ETS_TEMPORARIES, [gurksmetsmedaljong]). -define(DETS_TMP1,hd(?DETS_TEMPORARIES)). -define(ETS_TMP1,hd(?ETS_TEMPORARIES)). -define(HELPER_NODE, (atom_to_list(?MODULE) ++ "_helper1")). init_per_testcase(_Func, Config) -> PrivDir = ?config(priv_dir,Config), file:make_dir(PrivDir), Dog=test_server:timetrap(test_server:seconds(60)), [{watchdog, Dog}|Config]. end_per_testcase(_Func, Config) -> Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), lists:foreach(fun(X) -> (catch dets:close(X)), (catch file:delete(dets_filename(X,Config))) end, ?DETS_TEMPORARIES), lists:foreach(fun(X) -> (catch ets:delete(X)) end, ?ETS_TEMPORARIES). -ifdef(DEBUG). -define(LOG(X), show(X,?LINE)). show(Term, Line) -> io:format("~p: ~p~n", [Line,Term]), Term. -else. -define(LOG(X),X). -endif. fixbag(doc) -> ["Check for bug OTP-5087, safe_fixtable for bags could give " "incorrect lookups"]; fixbag(suite) -> []; fixbag(Config) when is_list(Config) -> ?line T = ets:new(x,[bag]), ?line ets:insert(T,{a,1}), ?line ets:insert(T,{a,2}), ?line ets:safe_fixtable(T,true), ?line ets:match_delete(T,{a,2}), ?line ets:insert(T,{a,3}), ?line Res = ets:lookup(T,a), ?line ets:safe_fixtable(T,false), ?line Res = ets:lookup(T,a), ok. insert_same_key(doc) -> ["Check correct behaviour if a key is deleted and reinserted during fixation."]; insert_same_key(suite) -> []; insert_same_key(Config) when is_list(Config) -> ?line {ok,Dets1} = dets:open_file(?DETS_TMP1, [{file, dets_filename(?DETS_TMP1,Config)}]), ?line Ets1 = ets:new(ets,[]), ?line insert_same_key(Dets1,dets,Config), ?line insert_same_key(Ets1,ets,Config), ?line ets:insert(Ets1,{1,2}), ?line 1 = ets:info(Ets1,size), ?line dets:insert(Dets1,{1,2}), ?line 1 = dets:info(Dets1,size), ?line dets:close(Dets1), ?line (catch file:delete(dets_filename(Dets1,Config))), ?line ets:delete(Ets1), ?line {ok,Dets2} = dets:open_file(?DETS_TMP1, [{type,bag},{file, dets_filename(?DETS_TMP1,Config)}]), ?line Ets2 = ets:new(ets,[bag]), ?line insert_same_key(Dets2,dets,Config), ?line insert_same_key(Ets2,ets,Config), ?line ets:insert(Ets2,{1,2}), ?line 2 = ets:info(Ets2,size), ?line ets:insert(Ets2,{1,2}), ?line 2 = ets:info(Ets2,size), ?line dets:insert(Dets2,{1,2}), ?line 2 = dets:info(Dets2,size), ?line dets:insert(Dets2,{1,2}), ?line 2 = dets:info(Dets2,size), ?line dets:close(Dets2), ?line (catch file:delete(dets_filename(Dets2,Config))), ?line ets:delete(Ets2), ?line {ok,Dets3} = dets:open_file(?DETS_TMP1, [{type,duplicate_bag}, {file, dets_filename(?DETS_TMP1,Config)}]), ?line Ets3 = ets:new(ets,[duplicate_bag]), ?line insert_same_key(Dets3,dets,Config), ?line insert_same_key(Ets3,ets,Config), ?line ets:insert(Ets3,{1,2}), ?line 2 = ets:info(Ets3,size), ?line ets:insert(Ets3,{1,2}), ?line 3 = ets:info(Ets3,size), ?line dets:insert(Dets3,{1,2}), ?line 2 = dets:info(Dets3,size), ?line dets:insert(Dets3,{1,2}), ?line 3 = dets:info(Dets3,size), ?line dets:close(Dets3), ?line (catch file:delete(dets_filename(Dets3,Config))), ?line ets:delete(Ets3), ok. insert_same_key(Tab,Mod,_Config) -> ?line Mod:insert(Tab,{1,1}), ?line Mod:insert(Tab,{1,2}), ?line Mod:insert(Tab,{2,2}), ?line Mod:insert(Tab,{2,2}), ?line Mod:safe_fixtable(Tab,true), ?line Mod:delete(Tab,1), ?line Mod:insert(Tab,{1,1}), ?line Expect = case Mod:info(Tab,type) of bag -> Mod:insert(Tab,{1,2}), 2; _ -> 1 end, ?line Mod:delete(Tab,2), ?line Mod:safe_fixtable(Tab,false), ?line case Mod:info(Tab,size) of Expect -> ok; _ -> exit({size_field_wrong,{Mod,Mod:info(Tab)}}) end. owner_dies(doc) -> ["Check correct behaviour if the table owner dies."]; owner_dies(suite) -> []; owner_dies(Config) when is_list(Config) -> ?line P1 = start_commander(), ?line Ets1 = command(P1,{ets,new,[ets,[]]}), ?line command(P1,{ets,safe_fixtable,[Ets1,true]}), ?line {_,[{P1,1}]} = ets:info(Ets1, safe_fixed), ?line stop_commander(P1), ?line undefined = ets:info(Ets1, safe_fixed), ?line P2 = start_commander(), ?line Ets2 = command(P2,{ets,new,[ets,[public]]}), ?line command(P2,{ets,safe_fixtable,[Ets2,true]}), ?line ets:safe_fixtable(Ets2,true), ?line true = ets:info(Ets2, fixed), ?line {_,[{_,1},{_,1}]} = ets:info(Ets2, safe_fixed), ?line stop_commander(P2), ?line undefined = ets:info(Ets2, safe_fixed), ?line undefined = ets:info(Ets2, fixed), ?line P3 = start_commander(), ?line {ok,Dets} = ?LOG(command(P3, {dets, open_file, [?DETS_TMP1, [{file, dets_filename(?DETS_TMP1, Config)}]]})), ?line command(P3, {dets, safe_fixtable, [Dets, true]}), ?line {_,[{P3,1}]} = dets:info(Dets, safe_fixed), ?line true = dets:info(Dets, fixed), ?line stop_commander(P3), ?line undefined = dets:info(Dets, safe_fixed), ?line undefined = dets:info(Dets, fixed), ?line P4 = start_commander(), ?line {ok,Dets} = command(P4, {dets, open_file, [?DETS_TMP1, [{file, dets_filename(?DETS_TMP1,Config)}]]}), ?line {ok,Dets} = dets:open_file(?DETS_TMP1, [{file, dets_filename(?DETS_TMP1,Config)}]), ?line false = dets:info(Dets, safe_fixed), ?line command(P4, {dets, safe_fixtable, [Dets, true]}), ?line dets:safe_fixtable(Dets, true), ?line {_,[{_,1},{_,1}]} = dets:info(Dets, safe_fixed), ?line dets:safe_fixtable(Dets, true), ?line stop_commander(P4), ?line S = self(), ?line {_,[{S,2}]} = dets:info(Dets, safe_fixed), ?line true = dets:info(Dets, fixed), ?line dets:close(Dets), ?line undefined = dets:info(Dets, fixed), ?line undefined = dets:info(Dets, safe_fixed), ok. other_process_closes(doc) -> ["When another process closes an dets table, different " "things should happen depending on if it has opened it before."]; other_process_closes(suite) -> []; other_process_closes(Config) when is_list(Config) -> ?line {ok,Dets} = dets:open_file(?DETS_TMP1, [{file, dets_filename(tmp1,Config)}]), ?line P2 = start_commander(), ?line dets:safe_fixtable(Dets,true), ?line S = self(), ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed), ?line command(P2,{dets, safe_fixtable, [Dets, true]}), ?line {_,[_,_]} = dets:info(Dets, safe_fixed), ?line {error, not_owner} = command(P2,{dets, close, [Dets]}), ?line {_,[_,_]} = dets:info(Dets, safe_fixed), ?line command(P2,{dets, open_file,[?DETS_TMP1, [{file, dets_filename(?DETS_TMP1, Config)}]]}), ?line {_,[_,_]} = dets:info(Dets, safe_fixed), ?line command(P2,{dets, close, [Dets]}), ?line stop_commander(P2), ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed), ?line true = dets:info(Dets,fixed), ?line dets:close(Dets), ?line undefined = dets:info(Dets,fixed), ?line undefined = dets:info(Dets, safe_fixed), ok. other_process_deletes(doc) -> ["Check that fixtable structures are cleaned up if another process " "deletes an ets table"]; other_process_deletes(suite) -> []; other_process_deletes(Config) when is_list(Config) -> ?line Ets = ets:new(ets,[public]), ?line P = start_commander(), ?line ets:safe_fixtable(Ets,true), ?line ets:safe_fixtable(Ets,true), ?line true = ets:info(Ets, fixed), ?line {_,_} = ets:info(Ets, safe_fixed), ?line command(P,{ets,delete,[Ets]}), ?line stop_commander(P), ?line undefined = ets:info(Ets, fixed), ?line undefined = ets:info(Ets, safe_fixed), ok. multiple_fixes(doc) -> ["Check that multiple safe_fixtable keeps the reference counter."]; multiple_fixes(suite) -> []; multiple_fixes(Config) when is_list(Config) -> ?line {ok,Dets} = dets:open_file(?DETS_TMP1, [{file, dets_filename(?DETS_TMP1,Config)}]), ?line Ets = ets:new(ets,[]), ?line multiple_fixes(Dets,dets), ?line multiple_fixes(Ets,ets), ?line dets:close(Dets), ok. multiple_fixes(Tab, Mod) -> ?line false = Mod:info(Tab,fixed), ?line false = Mod:info(Tab, safe_fixed), ?line Mod:safe_fixtable(Tab, true), ?line true = Mod:info(Tab,fixed), ?line S = self(), ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed), ?line Mod:safe_fixtable(Tab, true), ?line Mod:safe_fixtable(Tab, true), ?line {_,[{S,3}]} = Mod:info(Tab, safe_fixed), ?line true = Mod:info(Tab,fixed), ?line Mod:safe_fixtable(Tab, false), ?line {_,[{S,2}]} = Mod:info(Tab, safe_fixed), ?line true = Mod:info(Tab,fixed), ?line Mod:safe_fixtable(Tab, false), ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed), ?line true = Mod:info(Tab,fixed), ?line Mod:safe_fixtable(Tab, false), ?line false = Mod:info(Tab, safe_fixed), ?line false = Mod:info(Tab,fixed). multiple_processes(doc) -> ["Check that multiple safe_fixtable across processes are reference " "counted OK"]; multiple_processes(suite) -> []; multiple_processes(Config) when is_list(Config) -> ?line {ok,Dets} = dets:open_file(?DETS_TMP1,[{file, dets_filename(?DETS_TMP1, Config)}]), ?line Ets = ets:new(ets,[public]), ?line multiple_processes(Dets,dets), ?line multiple_processes(Ets,ets), ok. multiple_processes(Tab, Mod) -> ?line io:format("Mod = ~p\n", [Mod]), ?line P1 = start_commander(), ?line P2 = start_commander(), ?line false = Mod:info(Tab,fixed), ?line false = Mod:info(Tab, safe_fixed), ?line command(P1, {Mod, safe_fixtable, [Tab,true]}), ?line true = Mod:info(Tab,fixed), ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed), ?line command(P2, {Mod, safe_fixtable, [Tab,true]}), ?line true = Mod:info(Tab,fixed), ?line {_,L} = Mod:info(Tab,safe_fixed), ?line true = (lists:sort(L) == lists:sort([{P1,1},{P2,1}])), ?line command(P2, {Mod, safe_fixtable, [Tab,true]}), ?line {_,L2} = Mod:info(Tab,safe_fixed), ?line true = (lists:sort(L2) == lists:sort([{P1,1},{P2,2}])), ?line command(P2, {Mod, safe_fixtable, [Tab,false]}), ?line true = Mod:info(Tab,fixed), ?line {_,L3} = Mod:info(Tab,safe_fixed), ?line true = (lists:sort(L3) == lists:sort([{P1,1},{P2,1}])), ?line command(P2, {Mod, safe_fixtable, [Tab,false]}), ?line true = Mod:info(Tab,fixed), ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed), ?line stop_commander(P1), ?line receive after 1000 -> ok end, ?line false = Mod:info(Tab,fixed), ?line false = Mod:info(Tab, safe_fixed), ?line command(P2, {Mod, safe_fixtable, [Tab,true]}), ?line true = Mod:info(Tab,fixed), ?line {_,[{P2,1}]} = Mod:info(Tab, safe_fixed), case Mod of dets -> ?line dets:close(Tab); ets -> ?line ets:delete(Tab) end, ?line stop_commander(P2), ?line receive after 1000 -> ok end, ?line undefined = Mod:info(Tab, safe_fixed), ok. %%% Helpers dets_filename(Base, Config) when is_atom(Base) -> dets_filename(atom_to_list(Base) ++ ".dat", Config); dets_filename(Basename, Config) -> PrivDir = ?config(priv_dir,Config), filename:join(PrivDir, Basename). command_loop() -> receive {From, command, {M,F,A}} -> Res = (catch apply(M, F, A)), From ! {self(), Res}, command_loop(); die -> ok end. start_commander() -> spawn(?MODULE, command_loop, []). stop_commander(Pid) -> process_flag(trap_exit, true), link(Pid), Pid ! die, receive {'EXIT',Pid,_} -> timer:sleep(1), % let other processes handle the signal as well true after 5000 -> exit(stop_timeout) end. command(Pid,MFA) -> Pid ! {self(), command, MFA}, receive {Pid, Res} -> Res after 20000 -> exit(command_timeout) end.