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