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