1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1999-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: %%%----------------------------------------------------------------------
   20: %%% Purpose : Tests the safe_fixtable functions in both ets and dets.
   21: %%%----------------------------------------------------------------------
   22: 
   23: -module(fixtable_SUITE).
   24: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   25: 	 init_per_group/2,end_per_group/2]).
   26: %%% Test cases
   27: -export([multiple_fixes/1, multiple_processes/1,
   28: 	 other_process_deletes/1, owner_dies/1,
   29: 	 other_process_closes/1,insert_same_key/1]).
   30: -export([fixbag/1]).
   31: -export([init_per_testcase/2, end_per_testcase/2]).
   32: %%% Internal exports
   33: -export([command_loop/0,start_commander/0]).
   34: 
   35: suite() -> [{ct_hooks,[ts_install_cth]}].
   36: 
   37: all() -> 
   38:     [multiple_fixes, multiple_processes,
   39:      other_process_deletes, owner_dies, other_process_closes,
   40:      insert_same_key, fixbag].
   41: 
   42: groups() -> 
   43:     [].
   44: 
   45: init_per_suite(Config) ->
   46:     Config.
   47: 
   48: end_per_suite(_Config) ->
   49:     ok.
   50: 
   51: init_per_group(_GroupName, Config) ->
   52:     Config.
   53: 
   54: end_per_group(_GroupName, Config) ->
   55:     Config.
   56: 
   57: 
   58: -include_lib("test_server/include/test_server.hrl").
   59: 
   60: %%% I wrote this thinking I would use more than one temporary at a time, but 
   61: %%% I wasn't... Well, maybe in the future...
   62: -define(DETS_TEMPORARIES, [tmp1]).
   63: -define(ETS_TEMPORARIES, [gurksmetsmedaljong]).
   64: -define(DETS_TMP1,hd(?DETS_TEMPORARIES)).
   65: -define(ETS_TMP1,hd(?ETS_TEMPORARIES)).
   66: 
   67: -define(HELPER_NODE, (atom_to_list(?MODULE) ++ "_helper1")).
   68: 
   69: init_per_testcase(_Func, Config) ->
   70:     PrivDir = ?config(priv_dir,Config),    
   71:     file:make_dir(PrivDir),
   72:     Dog=test_server:timetrap(test_server:seconds(60)),
   73:     [{watchdog, Dog}|Config].
   74: 
   75: end_per_testcase(_Func, Config) ->
   76:     Dog=?config(watchdog, Config),
   77:     test_server:timetrap_cancel(Dog),
   78:     lists:foreach(fun(X) ->
   79: 			  (catch dets:close(X)),
   80: 			  (catch file:delete(dets_filename(X,Config)))
   81: 		  end,
   82: 		  ?DETS_TEMPORARIES),
   83:     lists:foreach(fun(X) ->
   84: 			  (catch ets:delete(X))
   85: 		  end,
   86: 		  ?ETS_TEMPORARIES).
   87: 
   88: 
   89: -ifdef(DEBUG).
   90: -define(LOG(X), show(X,?LINE)).
   91: 
   92: show(Term, Line) ->
   93:     io:format("~p: ~p~n", [Line,Term]),
   94:     Term.
   95: -else.
   96: -define(LOG(X),X).
   97: -endif.
   98: 
   99: 
  100: fixbag(doc) ->
  101:     ["Check for bug OTP-5087, safe_fixtable for bags could give "
  102:      "incorrect lookups"];
  103: fixbag(suite) ->
  104:     [];
  105: fixbag(Config) when is_list(Config) ->
  106:     ?line T = ets:new(x,[bag]),
  107:     ?line ets:insert(T,{a,1}),
  108:     ?line ets:insert(T,{a,2}),
  109:     ?line ets:safe_fixtable(T,true),
  110:     ?line ets:match_delete(T,{a,2}),
  111:     ?line ets:insert(T,{a,3}),
  112:     ?line Res = ets:lookup(T,a),
  113:     ?line ets:safe_fixtable(T,false),
  114:     ?line Res = ets:lookup(T,a),
  115:     ok.
  116: 
  117: 
  118: 
  119: insert_same_key(doc) ->
  120:     ["Check correct behaviour if a key is deleted and reinserted during fixation."];
  121: insert_same_key(suite) ->
  122:     [];
  123: insert_same_key(Config) when is_list(Config) ->
  124:     ?line {ok,Dets1} = dets:open_file(?DETS_TMP1,
  125: 			       [{file, dets_filename(?DETS_TMP1,Config)}]),
  126:     ?line Ets1 = ets:new(ets,[]),
  127:     ?line insert_same_key(Dets1,dets,Config),
  128:     ?line insert_same_key(Ets1,ets,Config),
  129:     ?line ets:insert(Ets1,{1,2}),
  130:     ?line 1 = ets:info(Ets1,size),
  131:     ?line dets:insert(Dets1,{1,2}),
  132:     ?line 1 = dets:info(Dets1,size),
  133:     ?line dets:close(Dets1),
  134:     ?line (catch file:delete(dets_filename(Dets1,Config))),
  135:     ?line ets:delete(Ets1),
  136:     ?line {ok,Dets2} = dets:open_file(?DETS_TMP1,
  137: 			       [{type,bag},{file, dets_filename(?DETS_TMP1,Config)}]),
  138:     ?line Ets2 = ets:new(ets,[bag]),
  139:     ?line insert_same_key(Dets2,dets,Config),
  140:     ?line insert_same_key(Ets2,ets,Config),
  141:     ?line ets:insert(Ets2,{1,2}),
  142:     ?line 2 = ets:info(Ets2,size),
  143:     ?line ets:insert(Ets2,{1,2}),
  144:     ?line 2 = ets:info(Ets2,size),
  145:     ?line dets:insert(Dets2,{1,2}),
  146:     ?line 2 = dets:info(Dets2,size),
  147:     ?line dets:insert(Dets2,{1,2}),
  148:     ?line 2 = dets:info(Dets2,size),
  149:     ?line dets:close(Dets2),
  150:     ?line (catch file:delete(dets_filename(Dets2,Config))),
  151:     ?line ets:delete(Ets2),
  152:     ?line {ok,Dets3} = dets:open_file(?DETS_TMP1,
  153: 			       [{type,duplicate_bag},
  154: 				{file, dets_filename(?DETS_TMP1,Config)}]),
  155:     ?line Ets3 = ets:new(ets,[duplicate_bag]),
  156:     ?line insert_same_key(Dets3,dets,Config),
  157:     ?line insert_same_key(Ets3,ets,Config),
  158:     ?line ets:insert(Ets3,{1,2}),
  159:     ?line 2 = ets:info(Ets3,size),
  160:     ?line ets:insert(Ets3,{1,2}),
  161:     ?line 3 = ets:info(Ets3,size),
  162:     ?line dets:insert(Dets3,{1,2}),
  163:     ?line 2 = dets:info(Dets3,size),
  164:     ?line dets:insert(Dets3,{1,2}),
  165:     ?line 3 = dets:info(Dets3,size),
  166:     ?line dets:close(Dets3),
  167:     ?line (catch file:delete(dets_filename(Dets3,Config))),
  168:     ?line ets:delete(Ets3),
  169:     ok.
  170: 
  171: insert_same_key(Tab,Mod,_Config) ->
  172:     ?line Mod:insert(Tab,{1,1}),
  173:     ?line Mod:insert(Tab,{1,2}),
  174:     ?line Mod:insert(Tab,{2,2}),
  175:     ?line Mod:insert(Tab,{2,2}),
  176:     ?line Mod:safe_fixtable(Tab,true),
  177:     ?line Mod:delete(Tab,1),
  178:     ?line Mod:insert(Tab,{1,1}),
  179:     ?line Expect = case Mod:info(Tab,type) of
  180: 	      bag ->
  181: 		  Mod:insert(Tab,{1,2}),
  182: 		  2;
  183: 	      _ -> 
  184: 		  1
  185: 	  end,
  186:     ?line Mod:delete(Tab,2),
  187:     ?line Mod:safe_fixtable(Tab,false),
  188:     ?line case Mod:info(Tab,size) of
  189: 	      Expect ->
  190: 		  ok;
  191: 	      _ -> 
  192: 		  exit({size_field_wrong,{Mod,Mod:info(Tab)}})
  193: 	  end.
  194:     
  195:     
  196: 
  197: 
  198: owner_dies(doc) ->
  199:     ["Check correct behaviour if the table owner dies."];
  200: owner_dies(suite) ->
  201:     [];
  202: owner_dies(Config) when is_list(Config) ->
  203:     ?line P1 = start_commander(),
  204:     ?line Ets1 = command(P1,{ets,new,[ets,[]]}),
  205:     ?line command(P1,{ets,safe_fixtable,[Ets1,true]}),
  206:     ?line {_,[{P1,1}]} = ets:info(Ets1, safe_fixed),
  207:     ?line stop_commander(P1),
  208:     ?line undefined = ets:info(Ets1, safe_fixed),
  209:     ?line P2 = start_commander(),
  210:     ?line Ets2 = command(P2,{ets,new,[ets,[public]]}),
  211:     ?line command(P2,{ets,safe_fixtable,[Ets2,true]}),
  212:     ?line ets:safe_fixtable(Ets2,true),
  213:     ?line true = ets:info(Ets2, fixed),
  214:     ?line {_,[{_,1},{_,1}]} = ets:info(Ets2, safe_fixed),
  215:     ?line stop_commander(P2),
  216:     ?line undefined = ets:info(Ets2, safe_fixed),
  217:     ?line undefined = ets:info(Ets2, fixed),
  218:     ?line P3 = start_commander(),
  219:     ?line {ok,Dets} = ?LOG(command(P3, {dets, open_file, 
  220: 					[?DETS_TMP1,
  221: 					 [{file, 
  222: 					   dets_filename(?DETS_TMP1,
  223: 							 Config)}]]})),
  224:     ?line command(P3, {dets, safe_fixtable, [Dets, true]}),
  225:     ?line {_,[{P3,1}]} = dets:info(Dets, safe_fixed),
  226:     ?line true = dets:info(Dets, fixed),
  227:     ?line stop_commander(P3),
  228:     ?line undefined = dets:info(Dets, safe_fixed),
  229:     ?line undefined = dets:info(Dets, fixed),
  230:     ?line P4 = start_commander(),
  231:     ?line {ok,Dets} = command(P4, {dets, open_file, 
  232: 			     [?DETS_TMP1,
  233: 			      [{file, dets_filename(?DETS_TMP1,Config)}]]}),
  234:     ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
  235: 			       [{file, dets_filename(?DETS_TMP1,Config)}]),
  236:     ?line false = dets:info(Dets, safe_fixed),
  237:     ?line command(P4, {dets, safe_fixtable, [Dets, true]}),
  238:     ?line dets:safe_fixtable(Dets, true),
  239:     ?line {_,[{_,1},{_,1}]} = dets:info(Dets, safe_fixed),
  240:     ?line dets:safe_fixtable(Dets, true),
  241:     ?line stop_commander(P4),
  242:     ?line S = self(),
  243:     ?line {_,[{S,2}]} = dets:info(Dets, safe_fixed),
  244:     ?line true = dets:info(Dets, fixed),
  245:     ?line dets:close(Dets),
  246:     ?line undefined = dets:info(Dets, fixed),
  247:     ?line undefined = dets:info(Dets, safe_fixed),
  248:     ok.
  249:    
  250: 
  251: other_process_closes(doc) ->
  252:     ["When another process closes an dets table, different "
  253:      "things should happen depending on if it has opened it before."];
  254: 
  255: other_process_closes(suite) ->
  256:     [];
  257: 
  258: other_process_closes(Config) when is_list(Config) ->
  259:     ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
  260: 			       [{file, dets_filename(tmp1,Config)}]),
  261:     ?line P2 = start_commander(),
  262:     ?line dets:safe_fixtable(Dets,true),
  263:     ?line S = self(),
  264:     ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed),
  265:     ?line command(P2,{dets, safe_fixtable, [Dets, true]}),
  266:     ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
  267:     ?line {error, not_owner} = command(P2,{dets, close, [Dets]}),
  268:     ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
  269:     ?line command(P2,{dets, open_file,[?DETS_TMP1,
  270: 				 [{file, 
  271: 				   dets_filename(?DETS_TMP1, Config)}]]}),
  272:     ?line {_,[_,_]} = dets:info(Dets, safe_fixed),
  273:     ?line command(P2,{dets, close, [Dets]}),
  274:     ?line stop_commander(P2),
  275:     ?line {_,[{S,1}]} = dets:info(Dets, safe_fixed),
  276:     ?line true = dets:info(Dets,fixed),
  277:     ?line dets:close(Dets),
  278:     ?line undefined = dets:info(Dets,fixed),
  279:     ?line undefined = dets:info(Dets, safe_fixed),
  280:     ok.
  281:     
  282: other_process_deletes(doc) ->
  283:     ["Check that fixtable structures are cleaned up if another process "
  284:      "deletes an ets table"];
  285: other_process_deletes(suite) ->
  286:     [];
  287: other_process_deletes(Config) when is_list(Config) ->
  288:     ?line Ets = ets:new(ets,[public]),
  289:     ?line P = start_commander(),
  290:     ?line ets:safe_fixtable(Ets,true),
  291:     ?line ets:safe_fixtable(Ets,true),
  292:     ?line true = ets:info(Ets, fixed),
  293:     ?line {_,_} = ets:info(Ets, safe_fixed),
  294:     ?line command(P,{ets,delete,[Ets]}),
  295:     ?line stop_commander(P),
  296:     ?line undefined = ets:info(Ets, fixed),
  297:     ?line undefined = ets:info(Ets, safe_fixed),
  298:     ok.
  299: 
  300: multiple_fixes(doc) ->
  301:     ["Check that multiple safe_fixtable keeps the reference counter."];
  302: multiple_fixes(suite) ->
  303:     [];
  304: multiple_fixes(Config) when is_list(Config) ->
  305:     ?line {ok,Dets} = dets:open_file(?DETS_TMP1,
  306: 			       [{file, dets_filename(?DETS_TMP1,Config)}]),
  307:     ?line Ets = ets:new(ets,[]),
  308:     ?line multiple_fixes(Dets,dets),
  309:     ?line multiple_fixes(Ets,ets),
  310:     ?line dets:close(Dets),
  311:     ok.
  312: 
  313: multiple_fixes(Tab, Mod) ->
  314:     ?line false = Mod:info(Tab,fixed),
  315:     ?line false = Mod:info(Tab, safe_fixed),
  316:     ?line Mod:safe_fixtable(Tab, true),
  317:     ?line true = Mod:info(Tab,fixed),
  318:     ?line S = self(),
  319:     ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed),
  320:     ?line Mod:safe_fixtable(Tab, true),
  321:     ?line Mod:safe_fixtable(Tab, true),
  322:     ?line {_,[{S,3}]} = Mod:info(Tab, safe_fixed),
  323:     ?line true = Mod:info(Tab,fixed),
  324:     ?line Mod:safe_fixtable(Tab, false),
  325:     ?line {_,[{S,2}]} = Mod:info(Tab, safe_fixed),
  326:     ?line true = Mod:info(Tab,fixed),
  327:     ?line Mod:safe_fixtable(Tab, false),
  328:     ?line {_,[{S,1}]} = Mod:info(Tab, safe_fixed),
  329:     ?line true = Mod:info(Tab,fixed),
  330:     ?line Mod:safe_fixtable(Tab, false),
  331:     ?line false = Mod:info(Tab, safe_fixed),
  332:     ?line false = Mod:info(Tab,fixed).
  333: 
  334: multiple_processes(doc) ->
  335:     ["Check that multiple safe_fixtable across processes are reference "
  336:      "counted OK"];
  337: multiple_processes(suite) ->
  338:     [];
  339: multiple_processes(Config) when is_list(Config) ->
  340:     ?line {ok,Dets} = dets:open_file(?DETS_TMP1,[{file, 
  341: 					    dets_filename(?DETS_TMP1,
  342: 							  Config)}]),
  343:     ?line Ets = ets:new(ets,[public]),
  344:     ?line multiple_processes(Dets,dets),
  345:     ?line multiple_processes(Ets,ets),
  346:     ok.
  347: 
  348: multiple_processes(Tab, Mod) ->
  349:     ?line io:format("Mod = ~p\n", [Mod]),
  350:     ?line P1 = start_commander(),
  351:     ?line P2 = start_commander(),
  352:     ?line false = Mod:info(Tab,fixed),
  353:     ?line false = Mod:info(Tab, safe_fixed),
  354:     ?line command(P1, {Mod, safe_fixtable, [Tab,true]}),
  355:     ?line true = Mod:info(Tab,fixed),
  356:     ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed),
  357:     ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
  358:     ?line true = Mod:info(Tab,fixed),
  359:     ?line {_,L} = Mod:info(Tab,safe_fixed),
  360:     ?line true = (lists:sort(L) == lists:sort([{P1,1},{P2,1}])),
  361:     ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
  362:     ?line {_,L2} = Mod:info(Tab,safe_fixed),
  363:     ?line true = (lists:sort(L2) == lists:sort([{P1,1},{P2,2}])),
  364:     ?line command(P2, {Mod, safe_fixtable, [Tab,false]}),
  365:     ?line true = Mod:info(Tab,fixed),
  366:     ?line {_,L3} = Mod:info(Tab,safe_fixed),
  367:     ?line true = (lists:sort(L3) == lists:sort([{P1,1},{P2,1}])),
  368:     ?line command(P2, {Mod, safe_fixtable, [Tab,false]}),
  369:     ?line true = Mod:info(Tab,fixed),
  370:     ?line {_,[{P1,1}]} = Mod:info(Tab, safe_fixed),
  371:     ?line stop_commander(P1),
  372:     ?line receive after 1000 -> ok end,
  373:     ?line false = Mod:info(Tab,fixed),
  374:     ?line false = Mod:info(Tab, safe_fixed),
  375:     ?line command(P2, {Mod, safe_fixtable, [Tab,true]}),
  376:     ?line true = Mod:info(Tab,fixed),
  377:     ?line {_,[{P2,1}]} = Mod:info(Tab, safe_fixed),
  378:     case Mod of
  379: 	dets ->
  380: 	    ?line dets:close(Tab);
  381: 	ets ->
  382: 	    ?line ets:delete(Tab)
  383:     end,
  384:     ?line stop_commander(P2),
  385:     ?line receive after 1000 -> ok end,
  386:     ?line undefined = Mod:info(Tab, safe_fixed),
  387:     ok.
  388:     
  389:     
  390: 
  391: %%% Helpers
  392: dets_filename(Base, Config) when is_atom(Base) ->
  393:     dets_filename(atom_to_list(Base) ++ ".dat", Config);
  394: dets_filename(Basename, Config) ->
  395:     PrivDir = ?config(priv_dir,Config),
  396:     filename:join(PrivDir, Basename).
  397: 
  398: command_loop() ->
  399:     receive 
  400: 	{From, command, {M,F,A}} ->
  401: 	    Res = (catch apply(M, F, A)),
  402: 	    From ! {self(), Res},
  403: 	    command_loop();
  404: 	die ->
  405: 	    ok
  406:     end.
  407: 
  408: start_commander() ->
  409:     spawn(?MODULE, command_loop, []).
  410: 
  411: stop_commander(Pid) ->
  412:     process_flag(trap_exit, true),
  413:     link(Pid),
  414:     Pid ! die,
  415:     receive
  416: 	{'EXIT',Pid,_} ->
  417:             timer:sleep(1), % let other processes handle the signal as well
  418: 	    true
  419:     after 5000 ->
  420: 	    exit(stop_timeout)
  421:     end.
  422: 
  423: command(Pid,MFA) ->
  424:     Pid ! {self(), command, MFA},
  425:     receive
  426: 	{Pid, Res} ->
  427: 	    Res
  428:     after 20000 ->
  429: 	    exit(command_timeout)
  430:     end.
  431: 
  432: 
  433: