Permalink
Fetching contributors…
Cannot retrieve contributors at this time
5376 lines (4814 sloc) 170 KB
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2010. 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%
%%
-module(ets_SUITE).
-export([all/1]).
-export([new/1,default/1,setbag/1,badnew/1,verybadnew/1,named/1,keypos2/1,
privacy/1,privacy_owner/2]).
-export([insert/1,empty/1,badinsert/1]).
-export([lookup/1,time_lookup/1,badlookup/1,lookup_order/1]).
-export([delete/1,delete_elem/1,delete_tab/1,delete_large_tab/1,
delete_large_named_table/1,
evil_delete/1,baddelete/1,match_delete/1,table_leak/1]).
-export([match_delete3/1]).
-export([firstnext/1,firstnext_concurrent/1]).
-export([slot/1]).
-export([match/1, match1/1, match2/1, match_object/1, match_object2/1]).
-export([misc/1, dups/1, misc1/1, safe_fixtable/1, info/1, tab2list/1]).
-export([files/1, tab2file/1, tab2file2/1, tab2file3/1, tabfile_ext1/1,
tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1]).
-export([heavy/1, heavy_lookup/1, heavy_lookup_element/1]).
-export([lookup_element/1, lookup_element_mult/1]).
-export([fold/1]).
-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
-export([t_delete_object/1, t_init_table/1, t_whitebox/1,
t_delete_all_objects/1, t_insert_list/1, t_test_ms/1,
t_select_delete/1,t_ets_dets/1]).
-export([do_lookup/2, do_lookup_element/3]).
-export([ordered/1, ordered_match/1, interface_equality/1,
fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
-export([member/1]).
-export([memory/1]).
-export([select_fail/1]).
-export([t_insert_new/1]).
-export([t_repair_continuation/1]).
-export([t_match_spec_run/1]).
-export([t_bucket_disappears/1]).
-export([otp_5340/1]).
-export([otp_6338/1]).
-export([otp_6842_select_1000/1]).
-export([otp_7665/1]).
-export([meta_wb/1]).
-export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]).
-export([meta_smp/1,
meta_lookup_unnamed_read/1, meta_lookup_unnamed_write/1,
meta_lookup_named_read/1, meta_lookup_named_write/1,
meta_newdel_unnamed/1, meta_newdel_named/1]).
-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1, otp_8166/1]).
-export([exit_large_table_owner/1,
exit_many_large_table_owner/1,
exit_many_tables_owner/1,
exit_many_many_tables_owner/1]).
-export([write_concurrency/1, heir/1, give_away/1, setopts/1]).
-export([bad_table/1]).
-export([init_per_testcase/2, fin_per_testcase/2, end_per_suite/1]).
%% Convenience for manual testing
-export([random_test/0]).
% internal exports
-export([dont_make_worse_sub/0, make_better_sub1/0, make_better_sub2/0]).
-export([t_repair_continuation_do/1, default_do/1, t_bucket_disappears_do/1,
select_fail_do/1, whitebox_1/1, whitebox_2/1, t_delete_all_objects_do/1,
t_delete_object_do/1, t_init_table_do/1, t_insert_list_do/1,
update_element_opts/1, update_element_opts/4, update_element/4, update_element_do/4,
update_element_neg/1, update_element_neg_do/1, update_counter_do/1, update_counter_neg/1,
evil_update_counter_do/1, fixtable_next_do/1, heir_do/1, give_away_do/1, setopts_do/1,
rename_do/1, rename_unnamed_do/1, interface_equality_do/1, ordered_match_do/1,
ordered_do/1, privacy_do/1, empty_do/1, badinsert_do/1, time_lookup_do/1,
lookup_order_do/1, lookup_element_mult_do/1, delete_tab_do/1, delete_elem_do/1,
match_delete_do/1, match_delete3_do/1, firstnext_do/1,
slot_do/1, match1_do/1, match2_do/1, match_object_do/1, match_object2_do/1,
misc1_do/1, safe_fixtable_do/1, info_do/1, dups_do/1, heavy_lookup_do/1,
heavy_lookup_element_do/1, member_do/1, otp_5340_do/1, otp_7665_do/1, meta_wb_do/1
]).
-include("test_server.hrl").
init_per_testcase(Case, Config) ->
Seed = {S1,S2,S3} = random:seed0(), %now(),
random:seed(S1,S2,S3),
io:format("*** SEED: ~p ***\n", [Seed]),
start_spawn_logger(),
wait_for_test_procs(), %% Ensure previous case cleaned up
Dog=test_server:timetrap(test_server:minutes(20)),
[{watchdog, Dog}, {test_case, Case} | Config].
fin_per_testcase(_Func, Config) ->
Dog=?config(watchdog, Config),
wait_for_test_procs(true),
test_server:timetrap_cancel(Dog).
end_per_suite(_Config) ->
stop_spawn_logger(),
catch erts_debug:set_internal_state(available_internal_state, false).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all(suite) ->
[
new,insert,lookup,delete,firstnext,firstnext_concurrent,slot,match,
t_match_spec_run,
lookup_element, misc,files, heavy,
ordered, ordered_match, interface_equality,
fixtable_next, fixtable_insert, rename, rename_unnamed, evil_rename,
update_element, update_counter, evil_update_counter, partly_bound,
match_heavy, fold, member,
t_delete_object, t_init_table, t_whitebox,
t_delete_all_objects, t_insert_list, t_test_ms,
t_select_delete, t_ets_dets, memory,
t_bucket_disappears,
select_fail,t_insert_new, t_repair_continuation, otp_5340, otp_6338,
otp_6842_select_1000, otp_7665,
meta_wb,
grow_shrink, grow_pseudo_deleted, shrink_pseudo_deleted,
meta_smp,
smp_insert, smp_fixed_delete, smp_unfix_fix, smp_select_delete, otp_8166,
exit_large_table_owner,
exit_many_large_table_owner,
exit_many_tables_owner,
exit_many_many_tables_owner,
write_concurrency, heir, give_away, setopts,
bad_table
].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
t_bucket_disappears(suite) ->
[];
t_bucket_disappears(doc) ->
["Test that a disappearing bucket during select of a non-fixed table works."];
t_bucket_disappears(Config) when is_list(Config) ->
repeat_for_opts(t_bucket_disappears_do).
t_bucket_disappears_do(Opts) ->
?line EtsMem = etsmem(),
?line ets:new(abcd, [named_table, public, {keypos, 2} | Opts]),
?line ets:insert(abcd, {abcd,1,2}),
?line ets:insert(abcd, {abcd,2,2}),
?line ets:insert(abcd, {abcd,3,2}),
?line {_, Cont} = ets:select(abcd, [{{'_', '$1', '_'},
[{'<', '$1', {const, 10}}],
['$1']}], 1),
?line ets:delete(abcd, 2),
?line ets:select(Cont),
?line true = ets:delete(abcd),
?line verify_etsmem(EtsMem).
t_match_spec_run(suite) ->
[];
t_match_spec_run(doc) ->
["Check ets:match_spec_run/2."];
t_match_spec_run(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line [2,3] = ets:match_spec_run([{1},{2},{3}],
ets:match_spec_compile(
[{{'$1'},[{'>','$1',1}],['$1']}])),
?line Huge = [{X} || X <- lists:seq(1,2500)],
?line L = lists:seq(2476,2500),
?line L = ets:match_spec_run(Huge,
ets:match_spec_compile(
[{{'$1'},[{'>','$1',2475}],['$1']}])),
?line L2 = [{X*16#FFFFFFF} || X <- L],
?line L2 = ets:match_spec_run(Huge,
ets:match_spec_compile(
[{{'$1'},
[{'>','$1',2475}],
[{{{'*','$1',16#FFFFFFF}}}]}])),
?line [500,1000,1500,2000,2500] =
ets:match_spec_run(Huge,
ets:match_spec_compile(
[{{'$1'},
[{'=:=',{'rem','$1',500},0}],
['$1']}])),
?line verify_etsmem(EtsMem).
t_repair_continuation(suite) ->
[];
t_repair_continuation(doc) ->
["Check ets:repair_continuation/2."];
t_repair_continuation(Config) when is_list(Config) ->
repeat_for_opts(t_repair_continuation_do).
t_repair_continuation_do(Opts) ->
?line EtsMem = etsmem(),
?line MS = [{'_',[],[true]}],
?line MS2 = [{{{'$1','_'},'_'},[],['$1']}],
(fun() ->
?line T = ets:new(x,[ordered_set|Opts]),
?line F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(5,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[ordered_set|Opts]),
?line F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,1001),
?line C = '$end_of_table',
?line C3 = ets:repair_continuation(C,MS),
?line '$end_of_table' = ets:select(C3),
?line '$end_of_table' = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[ordered_set|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{integer_to_list(N),N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(5,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[ordered_set|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{{integer_to_list(N),N},N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS2,5),
?line C2 = erlang:setelement(5,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS2),
?line {[_,_,_,_,_],_} = ets:select(C3),
?line {[_,_,_,_,_],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[set|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{N,N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(4,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[set|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{integer_to_list(N),N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(4,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[bag|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{integer_to_list(N),N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(4,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
(fun() ->
?line T = ets:new(x,[duplicate_bag|Opts]),
?line F = fun(0,_)->ok;(N,F) ->
ets:insert(T,{integer_to_list(N),N}),
F(N-1,F)
end,
?line F(1000,F),
?line {_,C} = ets:select(T,MS,5),
?line C2 = erlang:setelement(4,C,<<>>),
?line {'EXIT',{badarg,_}} = (catch ets:select(C2)),
?line C3 = ets:repair_continuation(C2,MS),
?line {[true,true,true,true,true],_} = ets:select(C3),
?line {[true,true,true,true,true],_} = ets:select(C),
?line true = ets:delete(T)
end)(),
?line false = ets:is_compiled_ms(<<>>),
?line true = ets:is_compiled_ms(ets:match_spec_compile(MS)),
?line verify_etsmem(EtsMem).
new(suite) -> [default,setbag,badnew,verybadnew,named,keypos2,privacy].
default(doc) ->
["Test case to check that a new ets table is defined as a `set' and "
"`protected'"];
default(suite) -> [];
default(Config) when is_list(Config) ->
%% Default should be set,protected
repeat_for_opts(default_do).
default_do(Opts) ->
?line EtsMem = etsmem(),
?line Def = ets:new(def,Opts),
?line set = ets:info(Def,type),
?line protected = ets:info(Def,protection),
?line ets:delete(Def),
?line verify_etsmem(EtsMem).
select_fail(doc) ->
["Test that select fails even if nothing can match"];
select_fail(suite) ->
[];
select_fail(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(select_fail_do, [all_types,write_concurrency]),
?line verify_etsmem(EtsMem).
select_fail_do(Opts) ->
?line T = ets:new(x,Opts),
?line ets:insert(T,{a,a}),
?line case (catch
ets:select(T,[{{a,'_'},[],[{snuffla}]}])) of
{'EXIT',{badarg,_}} ->
ok;
Else0 ->
exit({type,ets:info(T,type),
expected,'EXIT',got,Else0})
end,
?line case (catch
ets:select(T,[{{b,'_'},[],[{snuffla}]}])) of
{'EXIT',{badarg,_}} ->
ok;
Else1 ->
exit({type,ets:info(T,type),
expected,'EXIT',got,Else1})
end,
?line ets:delete(T).
-define(S(T),ets:info(T,memory)).
-define(TAB_STRUCT_SZ, erts_debug:get_internal_state('DbTable_words')).
-define(NORMAL_TAB_STRUCT_SZ, 26). %% SunOS5.8, 32-bit, non smp, private heap
%%
%% The hardcoded expected memory sizes (in words) are the ones we expect on:
%% SunOS5.8, 32-bit, non smp, private heap
%%
memory(doc) ->
["Whitebox test of ets:info(X,memory)"];
memory(suite) ->
[];
memory(Config) when is_list(Config) ->
?line erts_debug:set_internal_state(available_internal_state, true),
?line ok = chk_normal_tab_struct_size(),
?line L = [T1,T2,T3,T4] = fill_sets_int(1000),
?line XRes1 = adjust_xmem(L, {16862,16072,16072,16078}),
?line Res1 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
Before = ets:info(T,size),
Key = 2, %894, %%ets:first(T),
Objs = ets:lookup(T,Key),
?line ets:delete(T,Key),
io:format("deleted key ~p from ~p changed size ~p to ~p: ~p\n",
[Key, ets:info(T,type), Before, ets:info(T,size), Objs])
end,
L),
?line XRes2 = adjust_xmem(L, {16849,16060,16048,16054}),
?line Res2 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
Before = ets:info(T,size),
Key = 4, %802, %ets:first(T),
Objs = ets:lookup(T,Key),
?line ets:match_delete(T,{Key,'_'}),
io:format("match_deleted key ~p from ~p changed size ~p to ~p: ~p\n",
[Key, ets:info(T,type), Before, ets:info(T,size), Objs])
end,
L),
?line XRes3 = adjust_xmem(L, {16836,16048,16024,16030}),
?line Res3 = {?S(T1),?S(T2),?S(T3),?S(T4)},
?line lists:foreach(fun(T) ->
?line ets:delete_all_objects(T)
end,
L),
?line XRes4 = adjust_xmem(L, {76,286,286,286}),
?line Res4 = {?S(T1),?S(T2),?S(T3),?S(T4)},
lists:foreach(fun(T) ->
?line ets:delete(T)
end,
L),
?line L2 = [T11,T12,T13,T14] = fill_sets_int(1000),
?line lists:foreach(fun(T) ->
?line ets:select_delete(T,[{'_',[],[true]}])
end,
L2),
?line XRes5 = adjust_xmem(L2, {76,286,286,286}),
?line Res5 = {?S(T11),?S(T12),?S(T13),?S(T14)},
?line ?t:format("XRes1 = ~p~n"
" Res1 = ~p~n~n"
"XRes2 = ~p~n"
" Res2 = ~p~n~n"
"XRes3 = ~p~n"
" Res3 = ~p~n~n"
"XRes4 = ~p~n"
" Res4 = ~p~n~n"
"XRes5 = ~p~n"
" Res5 = ~p~n~n",
[XRes1, Res1,
XRes2, Res2,
XRes3, Res3,
XRes4, Res4,
XRes5, Res5]),
?line XRes1 = Res1,
?line XRes2 = Res2,
?line XRes3 = Res3,
?line XRes4 = Res4,
?line XRes5 = Res5,
?line catch erts_debug:set_internal_state(available_internal_state, false),
?line ok.
chk_normal_tab_struct_size() ->
?line System = {os:type(),
os:version(),
erlang:system_info(wordsize),
erlang:system_info(smp_support),
erlang:system_info(heap_type)},
?line ?t:format("System = ~p~n", [System]),
?line ?t:format("?NORMAL_TAB_STRUCT_SZ=~p~n", [?NORMAL_TAB_STRUCT_SZ]),
?line ?t:format("?TAB_STRUCT_SZ=~p~n", [?TAB_STRUCT_SZ]),
?line case System of
{{unix, sunos}, {5, 8, 0}, 4, false, private} ->
?line ?NORMAL_TAB_STRUCT_SZ = ?TAB_STRUCT_SZ,
?line ok;
_ ->
?line ok
end.
adjust_xmem([T1,T2,T3,T4], {A0,B0,C0,D0} = Mem0) ->
%% Adjust for 64-bit, smp, and os:
%% Table struct size may differ.
Mem1 = case ?TAB_STRUCT_SZ of
?NORMAL_TAB_STRUCT_SZ ->
Mem0;
TabStructSz ->
TabDiff = TabStructSz - ?NORMAL_TAB_STRUCT_SZ,
{A0+TabDiff, B0+TabDiff, C0+TabDiff, D0+TabDiff}
end,
%% Adjust for hybrid and shared heaps:
%% Each record is one word smaller.
Mem2 = case erlang:system_info(heap_type) of
private ->
Mem1;
_ ->
{A1,B1,C1,D1} = Mem1,
{A1-ets:info(T1, size),B1-ets:info(T2, size),
C1-ets:info(T3, size),D1-ets:info(T4, size)}
end,
%%{Mem2,{ets:info(T1,stats),ets:info(T2,stats),ets:info(T3,stats),ets:info(T4,stats)}}.
Mem2.
t_whitebox(doc) ->
["Diverse whitebox testes"];
t_whitebox(suite) ->
[];
t_whitebox(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(whitebox_1),
repeat_for_opts(whitebox_1),
repeat_for_opts(whitebox_1),
repeat_for_opts(whitebox_2),
repeat_for_opts(whitebox_2),
repeat_for_opts(whitebox_2),
?line verify_etsmem(EtsMem).
whitebox_1(Opts) ->
?line T=ets:new(x,[bag | Opts]),
?line ets:insert(T,[{du,glade},{ta,en}]),
?line ets:insert(T,[{hej,hopp2},{du,glade2},{ta,en2}]),
?line {_,C}=ets:match(T,{ta,'$1'},1),
?line ets:select(C),
?line ets:match(C),
?line ets:delete(T),
ok.
whitebox_2(Opts) ->
?line T=ets:new(x,[ordered_set, {keypos,2} | Opts]),
?line T2=ets:new(x,[set, {keypos,2}| Opts]),
?line 0 = ets:select_delete(T,[{{hej},[],[true]}]),
?line 0 = ets:select_delete(T,[{{hej,hopp},[],[true]}]),
?line 0 = ets:select_delete(T2,[{{hej},[],[true]}]),
?line 0 = ets:select_delete(T2,[{{hej,hopp},[],[true]}]),
?line ets:delete(T),
?line ets:delete(T2),
ok.
t_ets_dets(doc) ->
["Test ets:to/from_dets"];
t_ets_dets(suite) ->
[];
t_ets_dets(Config) when is_list(Config) ->
repeat_for_opts(fun(Opts) -> t_ets_dets(Config,Opts) end).
t_ets_dets(Config, Opts) ->
?line Fname = gen_dets_filename(Config,1),
?line (catch file:delete(Fname)),
?line {ok,DTab} = dets:open_file(testdets_1,
[{file, Fname}]),
?line ETab = ets:new(x,Opts),
?line filltabint(ETab,3000),
?line DTab = ets:to_dets(ETab,DTab),
?line ets:delete_all_objects(ETab),
?line 0 = ets:info(ETab,size),
?line true = ets:from_dets(ETab,DTab),
?line 3000 = ets:info(ETab,size),
?line ets:delete(ETab),
?line {'EXIT',{badarg,[{ets,to_dets,[ETab,DTab]}|_]}} =
(catch ets:to_dets(ETab,DTab)),
?line {'EXIT',{badarg,[{ets,from_dets,[ETab,DTab]}|_]}} =
(catch ets:from_dets(ETab,DTab)),
?line ETab2 = ets:new(x,Opts),
?line filltabint(ETab2,3000),
?line dets:close(DTab),
?line {'EXIT',{badarg,[{ets,to_dets,[ETab2,DTab]}|_]}} =
(catch ets:to_dets(ETab2,DTab)),
?line {'EXIT',{badarg,[{ets,from_dets,[ETab2,DTab]}|_]}} =
(catch ets:from_dets(ETab2,DTab)),
?line ets:delete(ETab2),
?line (catch file:delete(Fname)),
ok.
t_delete_all_objects(doc) ->
["Test ets:delete_all_objects/1"];
t_delete_all_objects(suite) ->
[];
t_delete_all_objects(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(t_delete_all_objects_do),
?line verify_etsmem(EtsMem).
t_delete_all_objects_do(Opts) ->
?line T=ets:new(x,Opts),
?line filltabint(T,4000),
?line O=ets:first(T),
?line ets:next(T,O),
?line ets:safe_fixtable(T,true),
?line true = ets:delete_all_objects(T),
?line '$end_of_table' = ets:next(T,O),
?line 0 = ets:info(T,size),
?line 4000 = ets:info(T,kept_objects),
?line ets:safe_fixtable(T,false),
?line 0 = ets:info(T,size),
?line 0 = ets:info(T,kept_objects),
?line filltabint(T,4000),
?line 4000 = ets:info(T,size),
?line true = ets:delete_all_objects(T),
?line 0 = ets:info(T,size),
?line ets:delete(T).
t_delete_object(doc) ->
["Test ets:delete_object/2"];
t_delete_object(suite) ->
[];
t_delete_object(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(t_delete_object_do),
?line verify_etsmem(EtsMem).
t_delete_object_do(Opts) ->
?line T = ets:new(x,Opts),
?line filltabint(T,4000),
?line del_one_by_one_set(T,1,4001),
?line filltabint(T,4000),
?line del_one_by_one_set(T,4000,0),
?line filltabint(T,4000),
?line First = ets:first(T),
?line Next = ets:next(T,First),
?line ets:safe_fixtable(T,true),
?line ets:delete_object(T,{First, integer_to_list(First)}),
?line Next = ets:next(T,First),
?line 3999 = ets:info(T,size),
?line 1 = ets:info(T,kept_objects),
?line ets:safe_fixtable(T,false),
?line 3999 = ets:info(T,size),
?line 0 = ets:info(T,kept_objects),
?line ets:delete(T),
?line T1 = ets:new(x,[ordered_set | Opts]),
?line filltabint(T1,4000),
?line del_one_by_one_set(T1,1,4001),
?line filltabint(T1,4000),
?line del_one_by_one_set(T1,4000,0),
?line ets:delete(T1),
?line T2 = ets:new(x,[bag | Opts]),
?line filltabint2(T2,4000),
?line del_one_by_one_bag(T2,1,4001),
?line filltabint2(T2,4000),
?line del_one_by_one_bag(T2,4000,0),
?line ets:delete(T2),
?line T3 = ets:new(x,[duplicate_bag | Opts]),
?line filltabint3(T3,4000),
?line del_one_by_one_dbag_1(T3,1,4001),
?line filltabint3(T3,4000),
?line del_one_by_one_dbag_1(T3,4000,0),
?line filltabint(T3,4000),
?line filltabint3(T3,4000),
?line del_one_by_one_dbag_2(T3,1,4001),
?line filltabint(T3,4000),
?line filltabint3(T3,4000),
?line del_one_by_one_dbag_2(T3,4000,0),
?line filltabint2(T3,4000),
?line filltabint(T3,4000),
?line del_one_by_one_dbag_3(T3,4000,0),
?line ets:delete(T3),
ok.
make_init_fun(N) when N > 4000->
fun(read) ->
end_of_input;
(close) ->
exit(close_not_expected)
end;
make_init_fun(N) ->
fun(read) ->
case N rem 2 of
0 ->
{[{N, integer_to_list(N)}, {N, integer_to_list(N)}],
make_init_fun(N + 1)};
1 ->
{[], make_init_fun(N + 1)}
end;
(close) ->
exit(close_not_expected)
end.
t_init_table(doc) ->
["Test ets:init_table/2"];
t_init_table(suite) ->
[];
t_init_table(Config) when is_list(Config)->
?line EtsMem = etsmem(),
repeat_for_opts(t_init_table_do),
?line verify_etsmem(EtsMem).
t_init_table_do(Opts) ->
?line T = ets:new(x,[duplicate_bag | Opts]),
?line filltabint(T,4000),
?line ets:init_table(T, make_init_fun(1)),
?line del_one_by_one_dbag_1(T,4000,0),
?line ets:delete(T),
ok.
do_fill_dbag_using_lists(T,0) ->
T;
do_fill_dbag_using_lists(T,N) ->
ets:insert(T,[{N,integer_to_list(N)},
{N + N rem 2,integer_to_list(N + N rem 2)}]),
do_fill_dbag_using_lists(T,N - 1).
t_insert_new(doc) ->
["Test the insert_new function"];
t_insert_new(suite) ->
[];
t_insert_new(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line L = fill_sets_int(1000) ++ fill_sets_int(1000,[{write_concurrency,true}]),
lists:foreach(fun(Tab) ->
?line false = ets:insert_new(Tab,{2,"2"}),
?line true = ets:insert_new(Tab,{2002,"2002"}),
?line false = ets:insert_new(Tab,{2002,"2002"}),
?line true = ets:insert(Tab,{2002,"2002"}),
?line false = ets:insert_new(Tab,[{2002,"2002"}]),
?line false = ets:insert_new(Tab,[{2002,"2002"},
{2003,"2003"}]),
?line false = ets:insert_new(Tab,[{2001,"2001"},
{2002,"2002"},
{2003,"2003"}]),
?line false = ets:insert_new(Tab,[{2001,"2001"},
{2002,"2002"}]),
?line true = ets:insert_new(Tab,[{2001,"2001"},
{2003,"2003"}]),
?line false = ets:insert_new(Tab,{2001,"2001"}),
?line false = ets:insert_new(Tab,{2002,"2002"}),
?line false = ets:insert_new(Tab,{2003,"2003"}),
?line true = ets:insert_new(Tab,{2004,"2004"}),
?line true = ets:insert_new(Tab,{2000,"2000"}),
?line true = ets:insert_new(Tab,[{2005,"2005"},
{2006,"2006"},
{2007,"2007"}]),
?line Num =
case ets:info(Tab,type) of
bag ->
?line true =
ets:insert(Tab,{2004,"2004-2"}),
?line false =
ets:insert_new(Tab,{2004,"2004-3"}),
1009;
duplicate_bag ->
?line true =
ets:insert(Tab,{2004,"2004"}),
?line false =
ets:insert_new(Tab,{2004,"2004"}),
1010;
_ ->
1008
end,
?line Num = ets:info(Tab,size),
?line List = ets:tab2list(Tab),
?line ets:delete_all_objects(Tab),
?line true = ets:insert_new(Tab,List),
?line false = ets:insert_new(Tab,List),
?line ets:delete(Tab)
end,
L),
?line verify_etsmem(EtsMem).
t_insert_list(doc) ->
["Test ets:insert/2 with list of objects."];
t_insert_list(suite) ->
[];
t_insert_list(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(t_insert_list_do),
?line verify_etsmem(EtsMem).
t_insert_list_do(Opts) ->
?line T = ets:new(x,[duplicate_bag | Opts]),
?line do_fill_dbag_using_lists(T,4000),
?line del_one_by_one_dbag_2(T,4000,0),
?line ets:delete(T).
t_test_ms(doc) ->
["Test interface of ets:test_ms/2"];
t_test_ms(suite) ->
[];
t_test_ms(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line {ok,[a,b]} = ets:test_ms({a,b},
[{{'$1','$2'},[{'<','$1','$2'}],['$$']}]),
?line {ok,false} = ets:test_ms({a,b},
[{{'$1','$2'},[{'>','$1','$2'}],['$$']}]),
?line {error,[{error,String}]} = ets:test_ms({a,b},
[{{'$1','$2'},
[{'flurp','$1','$2'}],
['$$']}]),
?line true = (if is_list(String) -> true; true -> false end),
?line verify_etsmem(EtsMem).
t_select_delete(doc) ->
["Test the ets:select_delete/2 and ets:select_count/2 BIF's"];
t_select_delete(suite) ->
[];
t_select_delete(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line Tables = fill_sets_int(10000) ++ fill_sets_int(10000,[{write_concurrency,true}]),
lists:foreach
(fun(Table) ->
?line 4000 = ets:select_count(Table,[{{'$1', '_'},
[{'>',
{'rem',
'$1', 5},
2}],
[true]}]),
?line 4000 = ets:select_delete(Table,[{{'$1', '_'},
[{'>',
{'rem',
'$1', 5},
2}],
[true]}]),
?line check(Table,
fun({N,_}) when (N rem 5) =< 2 ->
true;
(_) ->
false
end,
6000)
end,
Tables),
lists:foreach
(fun(Table) ->
?line ets:select_delete(Table,[{'_',[],[true]}]),
?line xfilltabint(Table,4000),
?line successive_delete(Table,1,4001,bound),
?line 0 = ets:info(Table,size),
?line xfilltabint(Table,4000),
?line successive_delete(Table,4000,0, bound),
?line 0 = ets:info(Table,size),
?line xfilltabint(Table,4000),
?line successive_delete(Table,1,4001,unbound),
?line 0 = ets:info(Table,size),
?line xfilltabint(Table,4000),
?line successive_delete(Table,4000,0, unbound),
?line 0 = ets:info(Table,size)
end,
Tables),
lists:foreach
(fun(Table) ->
F = case ets:info(Table,type) of
X when X == bag; X == duplicate_bag ->
2;
_ ->
1
end,
?line xfilltabstr(Table, 4000),
?line 1000 = ets:select_count(Table,
[{{[$3 | '$1'], '_'},
[{'==',
{'length', '$1'},
3}],[true]}]) div F,
?line 1000 = ets:select_delete(Table,
[{{[$3 | '$1'], '_'},
[{'==',
{'length', '$1'},
3}],[true]}]) div F,
?line check(Table, fun({[3,_,_,_],_}) -> false;
(_) -> true
end, 3000*F),
?line 8 = ets:select_count(Table,
[{{"7",'_'},[],[false]},
{{['_'], '_'},
[],[true]}]) div F,
?line 8 = ets:select_delete(Table,
[{{"7",'_'},[],[false]},
{{['_'], '_'},
[],[true]}]) div F,
?line check(Table, fun({"7",_}) -> true;
({[_],_}) -> false;
(_) -> true
end, 2992*F),
?line xfilltabstr(Table, 4000),
%% This happens to be interesting for other select types too
?line 200 = length(ets:select(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}])) div F,
?line 200 = ets:select_count(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}]) div F,
?line 200 = length(element(1,ets:select(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}],
1000))) div F,
?line 200 = length(
ets:select_reverse(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}])) div F,
?line 200 = length(
element(1,
ets:select_reverse
(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}],
1000))) div F,
?line 200 = ets:select_delete(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}]) div F,
?line 0 = ets:select_count(Table,
[{{[$3,'_','_'],'_'},
[],[true]},
{{[$1,'_','_'],'_'},
[],[true]}]) div F,
?line check(Table, fun({[$3,_,_],_}) -> false;
({[$1,_,_],_}) -> false;
(_) -> true
end, 3800*F)
end,
Tables),
lists:foreach(fun(Tab) -> ets:delete(Tab) end,Tables),
?line verify_etsmem(EtsMem).
partly_bound(doc) ->
["Test that partly bound keys gives faster matches"];
partly_bound(suite) ->
[];
partly_bound(Config) when is_list(Config) ->
case os:type() of
{win32,_} ->
{skip,"Inaccurate measurements on Windows"};
_ ->
?line EtsMem = etsmem(),
?line dont_make_worse(),
?line make_better(),
?line verify_etsmem(EtsMem)
end.
dont_make_worse() ->
seventyfive_percent_success({?MODULE,dont_make_worse_sub,[]},0,0,10).
dont_make_worse_sub() ->
?line T = build_table([a,b],[a,b],15000),
?line T1 = time_match_object(T,{'_',a,a,1500}, [{{a,a,1500},a,a,1500}]),
?line T2 = time_match_object(T,{{a,a,'_'},a,a,1500},
[{{a,a,1500},a,a,1500}]),
?line ets:delete(T),
?line true = (T1 > T2),
ok.
make_better() ->
fifty_percent_success({?MODULE,make_better_sub2,[]},0,0,10),
fifty_percent_success({?MODULE,make_better_sub1,[]},0,0,10).
make_better_sub1() ->
?line T = build_table2([a,b],[a,b],15000),
?line T1 = time_match_object(T,{'_',1500,a,a}, [{{1500,a,a},1500,a,a}]),
?line T2 = time_match_object(T,{{1500,a,'_'},1500,a,a},
[{{1500,a,a},1500,a,a}]),
?line ets:delete(T),
?line io:format("~p>~p~n",[(T1 / 100),T2]),
?line true = ((T1 / 100) > T2), % More marginal than needed.
ok.
make_better_sub2() ->
?line T = build_table2([a,b],[a,b],15000),
?line T1 = time_match(T,{'$1',1500,a,a}),
?line T2 = time_match(T,{{1500,a,'$1'},1500,a,a}),
?line ets:delete(T),
?line io:format("~p>~p~n",[(T1 / 100),T2]),
?line true = ((T1 / 100) > T2), % More marginal than needed.
ok.
match_heavy(doc) ->
["Heavy random matching, comparing set with ordered_set."];
match_heavy(suite) ->
[];
match_heavy(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir,Config),
DataDir = ?config(data_dir, Config),
%% Easier to have in process dictionary when manually
%% running the test function.
put(where_to_read,DataDir),
put(where_to_write,PrivDir),
Dog=?config(watchdog, Config),
test_server:timetrap_cancel(Dog),
NewDog=test_server:timetrap(test_server:seconds(1000)),
NewConfig = [{watchdog, NewDog} | lists:keydelete(watchdog,1,Config)],
random_test(),
drop_match(),
NewConfig.
%%% Extra safety for the very low probability that this is not
%%% caught by the random test (Statistically impossible???)
drop_match() ->
?line EtsMem = etsmem(),
?line T = build_table([a,b],[a],1500),
?line [{{a,a,1},a,a,1},{{b,a,1},b,a,1}] =
ets:match_object(T, {'_','_','_',1}),
?line true = ets:delete(T),
?line verify_etsmem(EtsMem).
ets_match(Tab,Expr) ->
case random:uniform(2) of
1 ->
ets:match(Tab,Expr);
_ ->
match_chunked(Tab,Expr)
end.
match_chunked(Tab,Expr) ->
match_chunked_collect(ets:match(Tab,Expr,
random:uniform(1999) + 1)).
match_chunked_collect('$end_of_table') ->
[];
match_chunked_collect({Results, Continuation}) ->
Results ++ match_chunked_collect(ets:match(Continuation)).
ets_match_object(Tab,Expr) ->
case random:uniform(2) of
1 ->
ets:match_object(Tab,Expr);
_ ->
match_object_chunked(Tab,Expr)
end.
match_object_chunked(Tab,Expr) ->
match_object_chunked_collect(ets:match_object(Tab,Expr,
random:uniform(1999) + 1)).
match_object_chunked_collect('$end_of_table') ->
[];
match_object_chunked_collect({Results, Continuation}) ->
Results ++ match_object_chunked_collect(ets:match_object(Continuation)).
random_test() ->
?line ReadDir = get(where_to_read),
?line WriteDir = get(where_to_write),
?line (catch file:make_dir(WriteDir)),
?line Seed = case file:consult(filename:join([ReadDir,
"preset_random_seed.txt"])) of
{ok,[X]} ->
X;
_ ->
{A,B,C} = erlang:now(),
random:seed(A,B,C),
get(random_seed)
end,
put(random_seed,Seed),
?line {ok, F} = file:open(filename:join([WriteDir,
"last_random_seed.txt"]),
[write]),
io:format(F,"~p. ~n",[Seed]),
file:close(F),
io:format("Random seed ~p written to ~s, copy to ~s to rerun with "
"same seed.",[Seed,
filename:join([WriteDir, "last_random_seed.txt"]),
filename:join([ReadDir,
"preset_random_seed.txt"])]),
do_random_test().
do_random_test() ->
?line EtsMem = etsmem(),
?line OrdSet = ets:new(xxx,[ordered_set]),
?line Set = ets:new(xxx,[]),
?line do_n_times(fun() ->
?line Key = create_random_string(25),
?line Value = create_random_tuple(25),
?line ets:insert(OrdSet,{Key,Value}),
?line ets:insert(Set,{Key,Value})
end, 5000),
?line io:format("~nData inserted~n"),
?line do_n_times(fun() ->
?line I = random:uniform(25),
?line Key = create_random_string(I) ++ '_',
?line L1 = ets_match_object(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end,
2000),
?line io:format("~nData matched~n"),
?line ets:match_delete(OrdSet,'_'),
?line ets:match_delete(Set,'_'),
?line do_n_times(fun() ->
?line Value = create_random_string(25),
?line Key = create_random_tuple(25),
?line ets:insert(OrdSet,{Key,Value}),
?line ets:insert(Set,{Key,Value})
end, 2000),
?line io:format("~nData inserted~n"),
(fun() ->
?line Key = list_to_tuple(lists:duplicate(25,'_')),
?line L1 = ets_match_object(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
?line 2000 = length(L1),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end)(),
(fun() ->
?line Key = {'$1','$2','$3','$4',
'$5','$6','$7','$8',
'$9','$10','$11','$12',
'$13','$14','$15','$16',
'$17','$18','$19','$20',
'$21','$22','$23','$24',
'$25'},
?line L1 = ets_match_object(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
?line 2000 = length(L1),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end)(),
(fun() ->
?line Key = {'$1','$2','$3','$4',
'$5','$6','$7','$8',
'$9','$10','$11','$12',
'$13','$14','$15','$16',
'$17','$18','$19','$20',
'$21','$22','$23','$24',
'$25'},
?line L1 = ets_match(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match(Set,{Key,'_'})),
?line 2000 = length(L1),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end)(),
?line ets:match_delete(OrdSet,'_'),
?line ets:match_delete(Set,'_'),
?line do_n_times(fun() ->
?line Value = create_random_string(25),
?line Key = create_random_tuple(25),
?line ets:insert(OrdSet,{Key,Value}),
?line ets:insert(Set,{Key,Value})
end, 2000),
?line io:format("~nData inserted~n"),
do_n_times(fun() ->
?line Key = create_partly_bound_tuple(25),
?line L1 = ets_match_object(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end,
2000),
?line do_n_times(fun() ->
?line Key = create_partly_bound_tuple2(25),
?line L1 = ets_match_object(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end,
2000),
do_n_times(fun() ->
?line Key = create_partly_bound_tuple2(25),
?line L1 = ets_match(OrdSet,{Key,'_'}),
?line L2 = lists:sort(ets_match(Set,{Key,'_'})),
case L1 == L2 of
false ->
io:format("~p != ~p~n",
[L1,L2]),
?line exit({not_eq, L1, L2});
true ->
ok
end
end,
2000),
io:format("~nData matched~n"),
ets:match_delete(OrdSet,'_'),
ets:match_delete(Set,'_'),
do_n_times(fun() ->
do_n_times(fun() ->
?line Value =
create_random_string(25),
?line Key = create_random_tuple(25),
?line ets:insert(OrdSet,{Key,Value}),
?line ets:insert(Set,{Key,Value})
end, 500),
io:format("~nData inserted~n"),
do_n_times(fun() ->
?line Key =
create_partly_bound_tuple(25),
ets:match_delete(OrdSet,{Key,'_'}),
ets:match_delete(Set,{Key,'_'}),
L1 = ets:info(OrdSet,size),
L2 = ets:info(Set,size),
[] = ets_match_object(OrdSet,
{Key,'_'}),
case L1 == L2 of
false ->
io:format("~p != ~p "
"(deleted ~p)~n",
[L1,L2,Key]),
exit({not_eq, L1, L2,
{deleted,Key}});
true ->
ok
end
end,
50),
io:format("~nData deleted~n")
end,
10),
ets:delete(OrdSet),
ets:delete(Set),
?line verify_etsmem(EtsMem).
update_element(doc) ->
["test various variants of update_element"];
update_element(suite) ->
[];
update_element(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(update_element_opts),
?line verify_etsmem(EtsMem).
update_element_opts(Opts) ->
TupleCases = [{{key,val}, 1 ,2},
{{val,key}, 2, 1},
{{key,val}, 1 ,[2]},
{{key,val,val}, 1, [2,3]},
{{val,key,val,val}, 2, [3,4,1]},
{{val,val,key,val}, 3, [1,4,1,2]}, % update pos1 twice
{{val,val,val,key}, 4, [2,1,2,3]}],% update pos2 twice
lists:foreach(fun({Tuple,KeyPos,UpdPos}) -> update_element_opts(Tuple,KeyPos,UpdPos,Opts) end,
TupleCases),
update_element_neg(Opts).
update_element_opts(Tuple,KeyPos,UpdPos,Opts) ->
Set = ets:new(set,[{keypos,KeyPos} | Opts]),
OrdSet = ets:new(ordered_set,[ordered_set,{keypos,KeyPos} | Opts]),
update_element(Set,Tuple,KeyPos,UpdPos),
update_element(OrdSet,Tuple,KeyPos,UpdPos),
true = ets:delete(Set),
true = ets:delete(OrdSet),
ok.
update_element(T,Tuple,KeyPos,UpdPos) ->
KeyList = [Key || Key <- lists:seq(1,100)],
lists:foreach(fun(Key) ->
TupleWithKey = setelement(KeyPos,Tuple,Key),
update_element_do(T,TupleWithKey,Key,UpdPos)
end,
KeyList).
update_element_do(Tab,Tuple,Key,UpdPos) ->
% Strategy: Step around in Values array and call ets:update_element for the values.
% Take Length number of steps of size 1, then of size 2, ..., Length-1.
% This will try all combinations of {fromValue,toValue}
%
% IMPORTANT: size(Values) must be a prime number for this to work!!!
Big32 = 16#12345678,
Big64 = 16#123456789abcdef0,
Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32,
-Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64,
"A", "Sverker", [], {12,-132}, {},
<<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)),
(fun(X) -> X*Big32 end),
make_ref(), make_ref(), self(), ok, update_element, 28, 29 },
Length = size(Values),
PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) ->
NextIx = (ToIx+Rand) rem Length,
MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand, MeF);
(_ToIx, ResList, [], _Rand, _MeF) ->
ResList;
(ToIx, [], Pos, _Rand, _MeF) ->
{Pos, element(ToIx+1,Values)} % single {pos,value} arg
end,
NewTupleF = fun({Pos,Val}, Tpl, _MeF) ->
setelement(Pos, Tpl, Val);
([{Pos,Val} | Tail], Tpl, MeF) ->
MeF(Tail,setelement(Pos, Tpl, Val),MeF);
([], Tpl, _MeF) ->
Tpl
end,
UpdateF = fun(ToIx,Rand) ->
PosValArg = PosValArgF(ToIx,[],UpdPos,Rand,PosValArgF),
%%io:format("update_element(~p)~n",[PosValArg]),
ArgHash = erlang:phash2({Tab,Key,PosValArg}),
?line true = ets:update_element(Tab, Key, PosValArg),
?line ArgHash = erlang:phash2({Tab,Key,PosValArg}),
NewTuple = NewTupleF(PosValArg,Tuple,NewTupleF),
?line [NewTuple] = ets:lookup(Tab,Key)
end,
LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length ->
Checksum; % done
(FromIx, Incr, 0, Checksum, MeF) ->
MeF(FromIx, Incr+1, Length, Checksum, MeF);
(FromIx, Incr, Times, Checksum, MeF) ->
ToIx = (FromIx + Incr) rem Length,
UpdateF(ToIx,Checksum),
if
Incr =:= 0 -> UpdateF(ToIx,Checksum); % extra update to same value
true -> true
end,
MeF(ToIx, Incr, Times-1, Checksum+ToIx+1, MeF)
end,
FirstTuple = Tuple,
?line true = ets:insert(Tab,FirstTuple),
?line [FirstTuple] = ets:lookup(Tab,Key),
Checksum = LoopF(0, 1, Length, 0, LoopF),
?line Checksum = (Length-1)*Length*(Length+1) div 2, % if Length is a prime
ok.
update_element_neg(Opts) ->
Set = ets:new(set,Opts),
OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
update_element_neg_do(Set),
update_element_neg_do(OrdSet),
ets:delete(Set),
?line {'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1})),
ets:delete(OrdSet),
?line {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1})),
?line Bag = ets:new(bag,[bag | Opts]),
?line DBag = ets:new(duplicate_bag,[duplicate_bag | Opts]),
?line {'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1})),
?line {'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1})),
true = ets:delete(Bag),
true = ets:delete(DBag),
ok.
update_element_neg_do(T) ->
Object = {key, 0, "Hej"},
?line true = ets:insert(T,Object),
UpdateF = fun(Arg3) ->
ArgHash = erlang:phash2({T,key,Arg3}),
?line {'EXIT',{badarg,_}} = (catch ets:update_element(T,key,Arg3)),
?line ArgHash = erlang:phash2({T,key,Arg3}),
?line [Object] = ets:lookup(T,key)
end,
%% List of invalid {Pos,Value} tuples
InvList = [false, {2}, {2,1,false}, {false,1}, {0,1}, {1,1}, {-1,1}, {4,1}],
lists:foreach(UpdateF, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl]) end, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{2,1}]) end, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([{2,1},{3,"Hello"},InvTpl]) end, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([{3,"Hello"},{2,1},InvTpl]) end, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl,{3,"Hello"}]) end, InvList),
lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{3,"Hello"},{2,1}]) end, InvList),
UpdateF([{2,1} | {3,1}]),
lists:foreach(fun(InvTpl) -> UpdateF([{2,1} | InvTpl]) end, InvList),
?line true = ets:update_element(T,key,[]),
?line false = ets:update_element(T,false,[]),
?line false = ets:update_element(T,false,{2,1}),
?line ets:delete(T,key),
?line false = ets:update_element(T,key,{2,1}),
ok.
update_counter(doc) ->
["test various variants of update_counter"];
update_counter(suite) ->
[];
update_counter(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
repeat_for_opts(update_counter_do),
?line verify_etsmem(EtsMem).
update_counter_do(Opts) ->
Set = ets:new(set,Opts),
OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
update_counter_for(Set),
update_counter_for(OrdSet),
ets:delete(Set),
ets:delete(OrdSet),
update_counter_neg(Opts).
update_counter_for(T) ->
?line ets:insert(T,{a,1,1}),
?line 101 = ets:update_counter(T,a,100),
?line [{a,101,1}] = ets:lookup(T,a),
?line 101 = ets:update_counter(T,a,{3,100}),
?line [{a,101,101}] = ets:lookup(T,a),
LooperF = fun(Obj, 0, _, _) ->
Obj;
(Obj, Times, Arg3, Myself) ->
?line {NewObj, Ret} = uc_mimic(Obj,Arg3),
ArgHash = erlang:phash2({T,a,Arg3}),
?line Ret = ets:update_counter(T,a,Arg3),
?line ArgHash = erlang:phash2({T,a,Arg3}),
%%io:format("NewObj=~p~n ",[NewObj]),
?line [NewObj] = ets:lookup(T,a),
Myself(NewObj,Times-1,Arg3,Myself)
end,
LoopF = fun(Obj, Times, Arg3) ->
%%io:format("Loop start:\nObj = ~p\nArg3=~p\n",[Obj,Arg3]),
LooperF(Obj,Times,Arg3,LooperF)
end,
SmallMax32 = (1 bsl 27) - 1,
SmallMax64 = (1 bsl (27+32)) - 1,
Big1Max32 = (1 bsl 32) - 1,
Big1Max64 = (1 bsl 64) - 1,
Steps = 100,
Obj0 = {a,0,0,0,0},
?line ets:insert(T,Obj0),
?line Obj1 = LoopF(Obj0, Steps, {2,(SmallMax32 div Steps)*2}),
?line Obj2 = LoopF(Obj1, Steps, {3,(SmallMax64 div Steps)*2}),
?line Obj3 = LoopF(Obj2, Steps, {4,(Big1Max32 div Steps)*2}),
?line Obj4 = LoopF(Obj3, Steps, {5,(Big1Max64 div Steps)*2}),
?line Obj5 = LoopF(Obj4, Steps, {2,-(SmallMax32 div Steps)*4}),
?line Obj6 = LoopF(Obj5, Steps, {3,-(SmallMax64 div Steps)*4}),
?line Obj7 = LoopF(Obj6, Steps, {4,-(Big1Max32 div Steps)*4}),
?line Obj8 = LoopF(Obj7, Steps, {5,-(Big1Max64 div Steps)*4}),
?line Obj9 = LoopF(Obj8, Steps, {2,(SmallMax32 div Steps)*2}),
?line ObjA = LoopF(Obj9, Steps, {3,(SmallMax64 div Steps)*2}),
?line ObjB = LoopF(ObjA, Steps, {4,(Big1Max32 div Steps)*2}),
?line Obj0 = LoopF(ObjB, Steps, {5,(Big1Max64 div Steps)*2}),
%% back at zero, same trip again with lists
?line Obj4 = LoopF(Obj0,Steps,[{2, (SmallMax32 div Steps)*2},
{3, (SmallMax64 div Steps)*2},
{4, (Big1Max32 div Steps)*2},
{5, (Big1Max64 div Steps)*2}]),
?line Obj8 = LoopF(Obj4,Steps,[{4, -(Big1Max32 div Steps)*4},
{2, -(SmallMax32 div Steps)*4},
{5, -(Big1Max64 div Steps)*4},
{3, -(SmallMax64 div Steps)*4}]),
?line Obj0 = LoopF(Obj8,Steps,[{5, (Big1Max64 div Steps)*2},
{2, (SmallMax32 div Steps)*2},
{4, (Big1Max32 div Steps)*2},
{3, (SmallMax64 div Steps)*2}]),
%% make them shift size at the same time
?line ObjC = LoopF(Obj0,Steps,[{5, (Big1Max64 div Steps)*2},
{3, (Big1Max64 div Steps)*2 + 1},
{2, -(Big1Max64 div Steps)*2},
{4, -(Big1Max64 div Steps)*2 + 1}]),
%% update twice in same list
?line ObjD = LoopF(ObjC,Steps,[{5, -(Big1Max64 div Steps) + 1},
{3, -(Big1Max64 div Steps)*2 - 1},
{5, -(Big1Max64 div Steps) - 1},
{4, (Big1Max64 div Steps)*2 - 1}]),
?line Obj0 = LoopF(ObjD,Steps,[{2, (Big1Max64 div Steps) - 1},
{4, Big1Max64*2},
{2, (Big1Max64 div Steps) + 1},
{4, -Big1Max64*2}]),
%% warping with list
?line ObjE = LoopF(Obj0,1000,
[{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
{4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2},
{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
%% warping without list
?line ObjF = LoopF(ObjE,1000,{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}),
?line ObjG = LoopF(ObjF,1000,{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}),
?line ObjH = LoopF(ObjG,1000,{4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2}),
?line ObjI = LoopF(ObjH,1000,{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}),
%% mixing it up
?line LoopF(ObjI,1000,
[{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
{5,-SmallMax64*4 div 3},
{3,-SmallMax32*4 div 11},
{5,0},
{4,1},
{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
ok.
%% uc_mimic works kind of like the real ets:update_counter
%% Obj = Tuple in ets
%% Pits = {Pos,Incr} | {Pos,Incr,Thres,Warp}
%% Returns {Updated tuple in ets, Return value from update_counter}
uc_mimic(Obj, Pits) when is_tuple(Pits) ->
?line Pos = element(1,Pits),
?line NewObj = setelement(Pos, Obj, uc_adder(element(Pos,Obj),Pits)),
?line {NewObj, element(Pos,NewObj)};
uc_mimic(Obj, PitsList) when is_list(PitsList) ->
?line {NewObj,ValList} = uc_mimic(Obj,PitsList,[]),
?line {NewObj,lists:reverse(ValList)}.
uc_mimic(Obj, [], Acc) ->
?line {Obj,Acc};
uc_mimic(Obj, [Pits|Tail], Acc) ->
?line {NewObj,NewVal} = uc_mimic(Obj,Pits),
?line uc_mimic(NewObj,Tail,[NewVal|Acc]).
uc_adder(Init, {_Pos, Add}) ->
Init + Add;
uc_adder(Init, {_Pos, Add, Thres, Warp}) ->
case Init + Add of
X when X > Thres, Add > 0 ->
Warp;
Y when Y < Thres, Add < 0 ->
Warp;
Z ->
Z
end.
update_counter_neg(Opts) ->
Set = ets:new(set,Opts),
OrdSet = ets:new(ordered_set,[ordered_set | Opts]),
update_counter_neg_for(Set),
update_counter_neg_for(OrdSet),
ets:delete(Set),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(Set,key,1)),
ets:delete(OrdSet),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(OrdSet,key,1)),
?line Bag = ets:new(bag,[bag | Opts]),
?line DBag = ets:new(duplicate_bag,[duplicate_bag | Opts]),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(Bag,key,1)),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(DBag,key,1)),
true = ets:delete(Bag),
true = ets:delete(DBag),
ok.
update_counter_neg_for(T) ->
Object = {key,0,false,1},
?line true = ets:insert(T,Object),
UpdateF = fun(Arg3) ->
ArgHash = erlang:phash2({T,key,Arg3}),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,Arg3)),
?line ArgHash = erlang:phash2({T,key,Arg3}),
?line [Object] = ets:lookup(T,key)
end,
%% List of invalid arg3-tuples
InvList = [false, {2}, {2,false}, {false,1},
{0,1}, {-1,1}, % BUG < R12B-2
{1,1}, {3,1}, {5,1}, {2,1,100}, {2,1,100,0,false}, {2,1,false,0}, {2,1,0,false}],
lists:foreach(UpdateF, InvList),
lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv]) end, InvList),
lists:foreach(fun(Inv) -> UpdateF([Inv,{2,1}]) end, InvList),
lists:foreach(fun(Inv) -> UpdateF([{2,1},{4,-100},Inv]) end, InvList),
lists:foreach(fun(Inv) -> UpdateF([{4,100,50,0},{2,1},Inv]) end, InvList),
lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv,{4,100,50,0}]) end, InvList),
lists:foreach(fun(Inv) -> UpdateF([Inv,{4,100,50,0},{2,1}]) end, InvList),
UpdateF([{2,1} | {4,1}]),
lists:foreach(fun(Inv) -> UpdateF([{2,1} | Inv]) end, InvList),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,false,1)),
?line ets:delete(T,key),
?line {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,1)),
ok.
evil_update_counter(Config) when is_list(Config) ->
%% The code server uses ets table. Pre-load modules that might not be
%% already loaded.
gb_sets:module_info(),
math:module_info(),
ordsets:module_info(),
random:module_info(),
repeat_for_opts(evil_update_counter_do).
evil_update_counter_do(Opts) ->
?line EtsMem = etsmem(),
?line process_flag(trap_exit, true),
?line Pids = [spawn_link(fun() -> evil_counter(I,Opts) end) || I <- lists:seq(1, 40)],
?line wait_for_all(gb_sets:from_list(Pids)),
?line verify_etsmem(EtsMem),
ok.
wait_for_all(Pids0) ->
case gb_sets:is_empty(Pids0) of
true ->
ok;
false ->
receive
{'EXIT',Pid,normal} ->
?line Pids = gb_sets:delete(Pid, Pids0),
wait_for_all(Pids);
Other ->
io:format("unexpected: ~p\n", [Other]),
?line ?t:fail()
end
end.
evil_counter(I,Opts) ->
T = ets:new(a, Opts),
Start0 = case I rem 3 of
0 -> 16#12345678;
1 -> 16#12345678FFFFFFFF;
2 -> 16#7777777777FFFFFFFF863648726743
end,
Start = Start0 + random:uniform(100000),
ets:insert(T, {dracula,Start}),
Iter = 90000,
End = Start + Iter,
End = evil_counter_1(Iter, T),
ets:delete(T).
evil_counter_1(0, T) ->
[{dracula,Count}] = ets:lookup(T, dracula),
Count;
evil_counter_1(Iter, T) ->
ets:update_counter(T, dracula, 1),
evil_counter_1(Iter-1, T).
fixtable_next(doc) ->
["Check that a first-next sequence always works on a fixed table"];
fixtable_next(suite) ->
[];
fixtable_next(Config) when is_list(Config) ->
repeat_for_opts(fixtable_next_do, [write_concurrency,all_types]).
fixtable_next_do(Opts) ->
?line EtsMem = etsmem(),
?line do_fixtable_next(ets:new(set,[public | Opts])),
?line verify_etsmem(EtsMem).
do_fixtable_next(Tab) ->
?line F = fun(X,T,FF) -> case X of
0 -> true;
_ ->
ets:insert(T, {X,
integer_to_list(X),
X rem 10}),
FF(X-1,T,FF)
end
end,
?line F(100,Tab,F),
?line ets:safe_fixtable(Tab,true),
?line First = ets:first(Tab),
?line ets:delete(Tab, First),
?line ets:next(Tab, First),
?line ets:match_delete(Tab,{'_','_','_'}),
?line '$end_of_table' = ets:next(Tab, First),
?line true = ets:info(Tab, fixed),
?line ets:safe_fixtable(Tab, false),
?line false = ets:info(Tab, fixed),
?line ets:delete(Tab).
fixtable_insert(doc) ->
["Check inserts of deleted keys in fixed bags"];
fixtable_insert(suite) ->
[];
fixtable_insert(Config) when is_list(Config) ->
Combos = [[Type,{write_concurrency,WC}] || Type<- [bag,duplicate_bag],
WC <- [false,true]],
lists:foreach(fun(Opts) -> fixtable_insert_do(Opts) end,
Combos),
ok.
fixtable_insert_do(Opts) ->
io:format("Opts = ~p\n",[Opts]),
Ets = make_table(ets, Opts, [{a,1}, {a,2}, {b,1}, {b,2}]),
ets:safe_fixtable(Ets,true),
ets:match_delete(Ets,{b,1}),
First = ets:first(Ets),
?line Next = case First of
a -> b;
b -> a
end,
?line Next = ets:next(Ets,First),
ets:delete(Ets,Next),
?line '$end_of_table' = ets:next(Ets,First),
ets:insert(Ets, {Next,1}),
?line false = ets:insert_new(Ets, {Next,1}),
?line Next = ets:next(Ets,First),
?line '$end_of_table' = ets:next(Ets,Next),
ets:delete(Ets,Next),
'$end_of_table' = ets:next(Ets,First),
ets:insert(Ets, {Next,2}),
?line false = ets:insert_new(Ets, {Next,1}),
Next = ets:next(Ets,First),
'$end_of_table' = ets:next(Ets,Next),
ets:delete(Ets,First),
?line Next = ets:first(Ets),
?line '$end_of_table' = ets:next(Ets,Next),
ets:delete(Ets,Next),
?line '$end_of_table' = ets:next(Ets,First),
?line true = ets:insert_new(Ets,{Next,1}),
?line false = ets:insert_new(Ets,{Next,2}),
?line Next = ets:next(Ets,First),
ets:delete_object(Ets,{Next,1}),
?line '$end_of_table' = ets:next(Ets,First),
?line true = ets:insert_new(Ets,{Next,2}),
?line false = ets:insert_new(Ets,{Next,1}),
?line Next = ets:next(Ets,First),
ets:delete(Ets,First),
ets:safe_fixtable(Ets,false),
{'EXIT',{badarg,_}} = (catch ets:next(Ets,First)),
ok.
write_concurrency(doc) -> ["The 'write_concurrency' option"];
write_concurrency(suite) -> [];
write_concurrency(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
Yes1 = ets:new(foo,[public,{write_concurrency,true}]),
Yes2 = ets:new(foo,[protected,{write_concurrency,true}]),
No1 = ets:new(foo,[private,{write_concurrency,true}]),
Yes3 = ets:new(foo,[bag,public,{write_concurrency,true}]),
Yes4 = ets:new(foo,[bag,protected,{write_concurrency,true}]),
No2 = ets:new(foo,[bag,private,{write_concurrency,true}]),
Yes5 = ets:new(foo,[duplicate_bag,public,{write_concurrency,true}]),
Yes6 = ets:new(foo,[duplicate_bag,protected,{write_concurrency,true}]),
No3 = ets:new(foo,[duplicate_bag,private,{write_concurrency,true}]),
No4 = ets:new(foo,[ordered_set,public,{write_concurrency,true}]),
No5 = ets:new(foo,[ordered_set,protected,{write_concurrency,true}]),
No6 = ets:new(foo,[ordered_set,private,{write_concurrency,true}]),
No7 = ets:new(foo,[public,{write_concurrency,false}]),
No8 = ets:new(foo,[protected,{write_concurrency,false}]),
?line YesMem = ets:info(Yes1,memory),
?line NoHashMem = ets:info(No1,memory),
?line NoTreeMem = ets:info(No4,memory),
io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p\n",[YesMem,NoHashMem,NoTreeMem]),
?line YesMem = ets:info(Yes2,memory),
?line YesMem = ets:info(Yes3,memory),
?line YesMem = ets:info(Yes4,memory),
?line YesMem = ets:info(Yes5,memory),
?line YesMem = ets:info(Yes6,memory),
?line NoHashMem = ets:info(No2,memory),
?line NoHashMem = ets:info(No3,memory),
?line NoTreeMem = ets:info(No5,memory),
?line NoTreeMem = ets:info(No6,memory),
?line NoHashMem = ets:info(No7,memory),
?line NoHashMem = ets:info(No8,memory),
case erlang:system_info(smp_support) of
true ->
?line true = YesMem > NoHashMem,
?line true = YesMem > NoTreeMem;
false ->
?line true = YesMem =:= NoHashMem
end,
?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency,foo}])),
?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency}])),
?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,{write_concurrency,true,foo}])),
?line {'EXIT',{badarg,_}} = (catch ets:new(foo,[public,write_concurrency])),
lists:foreach(fun(T) -> ets:delete(T) end,
[Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,
No1,No2,No3,No4,No5,No6,No7,No8]),
?line verify_etsmem(EtsMem),
ok.
heir(doc) -> ["The 'heir' option"];
heir(suite) -> [];
heir(Config) when is_list(Config) ->
repeat_for_opts(heir_do).
heir_do(Opts) ->
?line EtsMem = etsmem(),
Master = self(),
%% Different types of heir data and link/monitor relations
TestFun = fun(Arg) -> {EtsMem,Arg} end,
Combos = [{Data,Mode} || Data<-[foo_data, <<"binary">>,
lists:seq(1,10), {17,TestFun,self()},
"The busy heir"],
Mode<-[none,link,monitor]],
?line lists:foreach(fun({Data,Mode})-> heir_1(Data,Mode,Opts) end,
Combos),
%% No heir
{Founder1,MrefF1} = spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
Founder1 ! {go, none},
?line {"No heir",Founder1} = receive_any(),
?line {'DOWN', MrefF1, process, Founder1, normal} = receive_any(),
?line undefined = ets:info(foo),
%% An already dead heir
{Heir2,MrefH2} = spawn_monitor(fun()->die end),
?line {'DOWN', MrefH2, process, Heir2, normal} = receive_any(),
{Founder2,MrefF2} = spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
Founder2 ! {go, Heir2},
?line {"No heir",Founder2} = receive_any(),
?line {'DOWN', MrefF2, process, Founder2, normal} = receive_any(),
?line undefined = ets:info(foo),
%% When heir dies before founder
{Founder3,MrefF3} = spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
{Heir3,MrefH3} = spawn_monitor(fun()->heir_heir(Founder3)end),
Founder3 ! {go, Heir3},
?line {'DOWN', MrefH3, process, Heir3, normal} = receive_any(),
Founder3 ! die_please,
?line {'DOWN', MrefF3, process, Founder3, normal} = receive_any(),
?line undefined = ets:info(foo),
%% When heir dies and pid reused before founder dies
% erts_debug:set_internal_state(available_internal_state,true),
% NextPidIx = erts_debug:get_internal_state(next_pid),
% {Founder4,MrefF4} = spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
% {Heir4,MrefH4} = spawn_monitor(fun()->heir_heir(Founder4)end),
% Founder4 ! {go, Heir4},
% ?line {'DOWN', MrefH4, process, Heir4, normal} = receive_any(),
% erts_debug:set_internal_state(next_pid, NextPidIx),
% erts_debug:set_internal_state(available_internal_state,false),
% {Heir4,MrefH4_B} = spawn_monitor_with_pid(Heir4,
% fun()-> ?line die_please = receive_any() end),
% Founder4 ! die_please,
% ?line {'DOWN', MrefF4, process, Founder4, normal} = receive_any(),
% Heir4 ! die_please,
% ?line {'DOWN', MrefH4_B, process, Heir4, normal} = receive_any(),
% ?line undefined = ets:info(foo),
?line verify_etsmem(EtsMem).
heir_founder(Master, HeirData, Opts) ->
?line {go,Heir} = receive_any(),
HeirTpl = case Heir of
none -> {heir,none};
_ -> {heir, Heir, HeirData}
end,
?line T = ets:new(foo,[named_table, private, HeirTpl | Opts]),
?line true = ets:insert(T,{key,1}),
?line [{key,1}] = ets:lookup(T,key),
Self = self(),
?line Self = ets:info(T,owner),
?line case ets:info(T,heir) of
none ->
io:format("case 1 Heir=~p~n", [Heir]),
?line true = (Heir =:= none) orelse (not is_process_alive(Heir)),
Master ! {"No heir",self()};
Heir ->
io:format("case 2 Heir=~p~n", [Heir]),
?line true = is_process_alive(Heir),
Heir ! {table,T,HeirData},
die_please = receive_any()
end.
heir_heir(Founder) ->
heir_heir(Founder, none).
heir_heir(Founder, Mode) ->
?line {table,T,HeirData} = receive_any(),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
?line case HeirData of
"The dying heir" -> exit(normal);
_ -> ok
end,
?line Mref = case Mode of
link -> process_flag(trap_exit, true),
link(Founder);
monitor -> erlang:monitor(process,Founder);
none -> ok
end,
?line Founder ! die_please,
io:format("xx1~n"),
?line Msg = case HeirData of
"The busy heir" -> receive_any_spinning();
_ -> receive_any()
end,
io:format("xx2 expecting ~p~n", [{'ETS-TRANSFER', T, Founder, HeirData}]),
?line {'ETS-TRANSFER', T, Founder, HeirData} = Msg,
io:format("xx3~n"),
?line foo = T,
?line Self = self(),
?line Self = ets:info(T,owner),
?line Self = ets:info(T,heir),
?line [{key,1}] = ets:lookup(T,key),
?line true = ets:insert(T,{key,2}),
?line [{key,2}] = ets:lookup(T,key),
?line case Mode of % Verify that EXIT or DOWN comes after ETS-TRANSFER
link ->
{'EXIT',Founder,normal} = receive_any(),
process_flag(trap_exit, false);
monitor ->
{'DOWN', Mref, process, Founder, normal} = receive_any();
none -> ok
end.
heir_1(HeirData,Mode,Opts) ->
io:format("test with heir_data = ~p\n", [HeirData]),
Master = self(),
?line Founder = spawn_link(fun() -> heir_founder(Master,HeirData,Opts) end),
io:format("founder spawned = ~p\n", [Founder]),
?line {Heir,Mref} = spawn_monitor(fun() -> heir_heir(Founder,Mode) end),
io:format("heir spawned = ~p\n", [{Heir,Mref}]),
?line Founder ! {go, Heir},
?line {'DOWN', Mref, process, Heir, normal} = receive_any().
give_away(doc) -> ["ets:give_way/3"];
give_away(suite) -> [];
give_away(Config) when is_list(Config) ->
repeat_for_opts(give_away_do).
give_away_do(Opts) ->
?line T = ets:new(foo,[named_table, private | Opts]),
?line true = ets:insert(T,{key,1}),
?line [{key,1}] = ets:lookup(T,key),
Parent = self(),
%% Give and then give back
?line {Receiver,Mref} = spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
?line give_me = receive_any(),
?line true = ets:give_away(T,Receiver,here_you_are),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
?line Receiver ! give_back,
?line {'ETS-TRANSFER',T,Receiver,"Tillbakakaka"} = receive_any(),
?line [{key,2}] = ets:lookup(T,key),
?line {'DOWN', Mref, process, Receiver, normal} = receive_any(),
%% Give and then let receiver keep it
?line true = ets:insert(T,{key,1}),
?line {Receiver3,Mref3} = spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
?line give_me = receive_any(),
?line true = ets:give_away(T,Receiver3,here_you_are),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
?line Receiver3 ! die_please,
?line {'DOWN', Mref3, process, Receiver3, normal} = receive_any(),
?line undefined = ets:info(T),
%% Give and then kill receiver to get back
?line T2 = ets:new(foo,[private | Opts]),
?line true = ets:insert(T2,{key,1}),
?line ets:setopts(T2,{heir,self(),"Som en gummiboll..."}),
?line {Receiver2,Mref2} = spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
?line give_me = receive_any(),
?line true = ets:give_away(T2,Receiver2,here_you_are),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T2,key)),
?line Receiver2 ! die_please,
?line {'ETS-TRANSFER',T2,Receiver2,"Som en gummiboll..."} = receive_any(),
?line [{key,2}] = ets:lookup(T2,key),
?line {'DOWN', Mref2, process, Receiver2, normal} = receive_any(),
%% Some negative testing
?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,Receiver,"To a dead one")),
?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,self(),"To myself")),
?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,"not a pid","To wrong type")),
?line true = ets:delete(T2),
?line {ReceiverNeg,MrefNeg} = spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
?line give_me = receive_any(),
?line {'EXIT',{badarg,_}} = (catch ets:give_away(T2,ReceiverNeg,"A deleted table")),
?line T3 = ets:new(foo,[public | Opts]),
spawn_link(fun()-> {'EXIT',{badarg,_}} = (catch ets:give_away(T3,ReceiverNeg,"From non owner")),
Parent ! done
end),
?line done = receive_any(),
?line ReceiverNeg ! no_soup_for_you,
?line {'DOWN', MrefNeg, process, ReceiverNeg, normal} = receive_any(),
ok.
give_away_receiver(T, Giver) ->
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
?line Giver ! give_me,
?line case receive_any() of
{'ETS-TRANSFER',T,Giver,here_you_are} ->
?line [{key,1}] = ets:lookup(T,key),
?line true = ets:insert(T,{key,2}),
?line case receive_any() of
give_back ->
?line true = ets:give_away(T,Giver,"Tillbakakaka"),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(T,key));
die_please ->
ok
end;
no_soup_for_you ->
ok
end.
setopts(doc) -> ["ets:setopts/2"];
setopts(suite) -> [];
setopts(Config) when is_list(Config) ->
repeat_for_opts(setopts_do,[write_concurrency,all_types]).
setopts_do(Opts) ->
Self = self(),
?line T = ets:new(foo,[named_table, private | Opts]),
?line none = ets:info(T,heir),
Heir = spawn_link(fun()->heir_heir(Self) end),
?line ets:setopts(T,{heir,Heir,"Data"}),
?line Heir = ets:info(T,heir),
?line ets:setopts(T,{heir,self(),"Data"}),
?line Self = ets:info(T,heir),
?line ets:setopts(T,[{heir,Heir,"Data"}]),
?line Heir = ets:info(T,heir),
?line ets:setopts(T,[{heir,none}]),
?line none = ets:info(T,heir),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,[{heir,self(),"Data"},false])),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,self()})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,heir)),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false,"Data"})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{false,self(),"Data"})),
?line ets:setopts(T,{protection,protected}),
?line ets:setopts(T,{protection,public}),
?line ets:setopts(T,{protection,private}),
?line ets:setopts(T,[{protection,protected}]),
?line ets:setopts(T,[{protection,public}]),
?line ets:setopts(T,[{protection,private}]),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,false})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,private,false})),
?line {'EXIT',{badarg,_}} = (catch ets:setopts(T,protection)),
?line ets:delete(T),
ok.
bad_table(doc) -> ["All kinds of operations with bad table argument"];
bad_table(suite) -> [];
bad_table(Config) when is_list(Config) ->
%% Open and close disk_log to stabilize etsmem.
Name = make_ref(),
?line File = filename:join([?config(priv_dir, Config),"bad_table.dummy"]),
?line {ok, Name} = disk_log:open([{name, Name}, {file, File}]),
?line disk_log:close(Name),
file:delete(File),
?line EtsMem = etsmem(),
repeat_for_opts(fun(Opts) -> bad_table_do(Opts,File) end,
[write_concurrency, all_types]),
?line verify_etsmem(EtsMem),
ok.
bad_table_do(Opts, DummyFile) ->
Parent = self(),
{Pid,Mref} = spawn_opt(fun()-> ets:new(priv,[private,named_table | Opts]),
Priv = ets:new(priv,[private | Opts]),
ets:new(prot,[protected,named_table | Opts]),
Prot = ets:new(prot,[protected | Opts]),
Parent ! {self(),Priv,Prot},
die_please = receive_any()
end,
[link, monitor]),
{Pid,Priv,Prot} = receive_any(),
MatchSpec = {{key,'_'}, [], ['$$']},
Fun = fun(X,_) -> X end,
OpList = [{delete,[key],update},
{delete_all_objects,[],update},
{delete_object,[{key,data}],update},
{first,[],read},
{foldl,[Fun, 0], read, tabarg_last},
{foldr,[Fun, 0], read, tabarg_last},
%%{from_dets,[DetsTab], update},
{give_away,[Pid, data], update},
%%{info, [], read},
%%{info, [safe_fixed], read},
%%{init_table,[Name, InitFun],update},
{insert, [{key,data}], update},
{insert_new, [{key,data}], update},
{insert_new, [[{key,data},{other,data}]], update},
{last, [], read},
{lookup, [key], read},
{lookup_element, [key, 2], read},
{match, [{}], read},
{match, [{},17], read},
{match_delete, [{}], update},
{match_object, [{}], read},
{match_object, [{},17], read},
{member,[key], read},
{next, [key], read},
{prev, [key], read},
{rename, [new_name], update},
{safe_fixtable, [true], read},
{select,[MatchSpec], read},
{select,[MatchSpec,17], read},
{select_count,[MatchSpec], read},
{select_delete,[MatchSpec], update},
{setopts, [{heir,none}], update},
{slot, [0], read},
{tab2file, [DummyFile], read, {return,{error,badtab}}},
{tab2file, [DummyFile,[]], read, {return,{error,badtab}}},
{tab2list, [], read},
%%{table,[], read},
%%{to_dets, [DetsTab], read},
{update_counter,[key,1], update},
{update_element,[key,{2,new_data}], update}
],
Info = {Opts, Priv, Prot},
lists:foreach(fun(Op) -> bad_table_op(Info, Op) end,
OpList),
Pid ! die_please,
{'DOWN', Mref, process, Pid, normal} = receive_any(),
ok.
bad_table_op({Opts,Priv,Prot}, Op) ->
%%io:format("Doing Op=~p on ~p's\n",[Op,Type]),
T1 = ets:new(noname,Opts),
bad_table_call(noname,Op),
ets:delete(T1),
bad_table_call(T1,Op),
T2 = ets:new(named,[named_table | Opts]),
ets:delete(T2),
bad_table_call(named,Op),
bad_table_call(T2,Op),
bad_table_call(priv,Op),
bad_table_call(Priv,Op),
case element(3,Op) of
update ->
bad_table_call(prot,Op),
bad_table_call(Prot,Op);
read -> ok
end.
bad_table_call(T,{F,Args,_}) ->
?line {'EXIT',{badarg,_}} = (catch apply(ets, F, [T|Args]));
bad_table_call(T,{F,Args,_,tabarg_last}) ->
?line {'EXIT',{badarg,_}} = (catch apply(ets, F, Args++[T]));
bad_table_call(T,{F,Args,_,{return,Return}}) ->
try
?line Return = apply(ets, F, [T|Args])
catch
error:badarg -> ok
end.
rename(doc) ->
["Check rename of ets tables"];
rename(suite) ->
[];
rename(Config) when is_list(Config) ->
repeat_for_opts(rename_do, [write_concurrency, all_types]).
rename_do(Opts) ->
?line EtsMem = etsmem(),
ets:new(foobazz,[named_table, public | Opts]),
ets:insert(foobazz,{foo,bazz}),
ungermanbazz = ets:rename(foobazz,ungermanbazz),
{'EXIT',{badarg, _}} = (catch ets:lookup(foobazz,foo)),
[{foo,bazz}] = ets:lookup(ungermanbazz,foo),
{'EXIT',{badarg,_}} = (catch ets:rename(ungermanbazz,"no atom")),
ets:delete(ungermanbazz),
?line verify_etsmem(EtsMem).
rename_unnamed(doc) ->
["Check rename of unnamed ets table"];
rename_unnamed(suite) ->
[];
rename_unnamed(Config) when is_list(Config) ->
repeat_for_opts(rename_unnamed_do,[write_concurrency,all_types]).
rename_unnamed_do(Opts) ->
?line EtsMem = etsmem(),
?line Tab = ets:new(bonkz,[public | Opts]),
?line {'EXIT',{badarg, _}} = (catch ets:insert(bonkz,{foo,bazz})),
?line bonkz = ets:info(Tab, name),
?line Tab = ets:rename(Tab, tjabonkz),
?line {'EXIT',{badarg, _}} = (catch ets:insert(tjabonkz,{foo,bazz})),
?line tjabonkz = ets:info(Tab, name),
?line ets:delete(Tab),
?line verify_etsmem(EtsMem).
evil_rename(doc) ->
"Rename a table with many fixations, and at the same time delete it.";
evil_rename(Config) when is_list(Config) ->
?line evil_rename_1(old_hash, new_hash, [public,named_table]),
?line EtsMem = etsmem(),
?line evil_rename_1(old_tree, new_tree, [public,ordered_set,named_table]),
?line verify_etsmem(EtsMem).
evil_rename_1(Old, New, Flags) ->
?line process_flag(trap_exit, true),
?line Old = ets:new(Old, Flags),
?line Fixer = fun() -> ets:safe_fixtable(Old, true) end,
?line crazy_fixtable(15000, Fixer),
?line erlang:yield(),
?line New = ets:rename(Old, New),
?line erlang:yield(),
ets:delete(New),
ok.
crazy_fixtable(N, Fixer) ->
Dracula = ets:new(count_dracula, [public]),
ets:insert(Dracula, {count,0}),
SpawnFun = fun() ->
Fixer(),
case ets:update_counter(Dracula, count, 1) rem 15 of
0 -> evil_creater_destroyer();
_ -> erlang:hibernate(erlang, error, [dont_wake_me])
end
end,
crazy_fixtable_1(N, SpawnFun),
crazy_fixtable_wait(N, Dracula),
Dracula.
crazy_fixtable_wait(N, Dracula) ->
case ets:lookup(Dracula, count) of
[{count,N}] ->
ets:delete(Dracula);
Other ->
io:format("~p\n", [Other]),
receive after 10 -> ok end,
crazy_fixtable_wait(N, Dracula)
end.
crazy_fixtable_1(0, _) ->
ok;
crazy_fixtable_1(N, Fun) ->
spawn_link(Fun),
crazy_fixtable_1(N-1, Fun).
evil_creater_destroyer() ->
T1 = evil_create_fixed_tab(),
ets:delete(T1).
evil_create_fixed_tab() ->
T = ets:new(arne, [public]),
ets:safe_fixtable(T, true),
T.
interface_equality(doc) ->
["Tests that the return values and errors are equal for set's and"
" ordered_set's where applicable"];
interface_equality(suite) ->
[];
interface_equality(Config) when is_list(Config) ->
repeat_for_opts(interface_equality_do).
interface_equality_do(Opts) ->
?line EtsMem = etsmem(),
?line Set = ets:new(set,[set | Opts]),
?line OrderedSet = ets:new(ordered_set,[ordered_set | Opts]),
?line F = fun(X,T,FF) -> case X of
0 -> true;
_ ->
ets:insert(T, {X,
integer_to_list(X),
X rem 10}),
FF(X-1,T,FF)
end
end,
?line F(100,Set,F),
?line F(100,OrderedSet,F),
?line equal_results(ets, insert, Set, OrderedSet, [{a,"a"}]),
?line equal_results(ets, insert, Set, OrderedSet, [{1,1,"1"}]),
?line equal_results(ets, lookup, Set, OrderedSet, [10]),
?line equal_results(ets, lookup, Set, OrderedSet, [1000]),
?line equal_results(ets, delete, Set, OrderedSet, [10]),
?line equal_results(ets, delete, Set, OrderedSet, [nott]),
?line equal_results(ets, lookup, Set, OrderedSet, [1000]),
?line equal_results(ets, insert, Set, OrderedSet, [10]),
?line equal_results(ets, next, Set, OrderedSet, ['$end_of_table']),
?line equal_results(ets, prev, Set, OrderedSet, ['$end_of_table']),
?line equal_results(ets, match, Set, OrderedSet, [{'_','_','_'}]),
?line equal_results(ets, match, Set, OrderedSet, [{'_','_','_','_'}]),
?line equal_results(ets, match, Set, OrderedSet, [{$3,$2,2}]),
?line equal_results(ets, match, Set, OrderedSet, ['_']),
?line equal_results(ets, match, Set, OrderedSet, ['$1']),
?line equal_results(ets, match, Set, OrderedSet, [{'_','$50',3}]),
?line equal_results(ets, match, Set, OrderedSet, [['_','$50',3]]),
?line equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
?line equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',5}]),
?line equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
?line equal_results(ets, match_object, Set, OrderedSet, ['_']),
?line equal_results(ets, match_object, Set, OrderedSet, ['$5011']),
?line equal_results(ets, match_delete, Set, OrderedSet, ['$20']),
?line equal_results(ets, lookup_element, Set, OrderedSet, [13,2]),
?line equal_results(ets, lookup_element, Set, OrderedSet, [13,4]),
?line equal_results(ets, lookup_element, Set, OrderedSet, [14,2]),
?line equal_results(ets, delete, Set, OrderedSet, []),
?line verify_etsmem(EtsMem).
equal_results(M, F, FirstArg1, FirstArg2 ,ACommon) ->
Res = maybe_sort((catch apply(M,F, [FirstArg1 | ACommon]))),
Res = maybe_sort((catch apply(M,F,[FirstArg2 | ACommon]))).
maybe_sort(L) when is_list(L) ->
lists:sort(L);
%maybe_sort({'EXIT',{Reason, [{Module, Function, _}|_]}}) ->
% {'EXIT',{Reason, [{Module, Function, '_'}]}};
maybe_sort({'EXIT',{Reason, List}}) when is_list(List) ->
{'EXIT',{Reason, lists:map(fun({Module, Function, _}) ->
{Module, Function, '_'}
end,
List)}};
maybe_sort(Any) ->
Any.
ordered_match(doc) ->
["Test match, match_object and match_delete in ordered set's"];
ordered_match(suite) ->
[];
ordered_match(Config) when is_list(Config)->
repeat_for_opts(ordered_match_do).
ordered_match_do(Opts) ->
?line EtsMem = etsmem(),
?line F = fun(X,T,FF) -> case X of
0 -> true;
_ ->
ets:insert(T, {X,
integer_to_list(X),
X rem 10,
X rem 100,
X rem 1000}),
FF(X-1,T,FF)
end
end,
?line T1 = ets:new(xxx,[ordered_set| Opts]),
?line F(3000,T1,F),
?line [[3,3],[3,3],[3,3]] = ets:match(T1, {'_','_','$1','$2',3}),
?line F2 = fun(X,Rem,Res,FF) -> case X of
0 -> [];
_ ->
case X rem Rem of
Res ->
FF(X-1,Rem,Res,FF) ++
[{X,
integer_to_list(X),
X rem 10,
X rem 100,
X rem 1000}];
_ ->
FF(X-1,Rem,Res,FF)
end
end
end,
?line OL1 = F2(3000,100,2,F2),
?line OL1 = ets:match_object(T1, {'_','_','_',2,'_'}),
?line true = ets:match_delete(T1,{'_','_','_',2,'_'}),
?line [] = ets:match_object(T1, {'_','_','_',2,'_'}),
?line OL2 = F2(3000,100,3,F2),
?line OL2 = ets:match_object(T1, {'_','_','_',3,'_'}),
?line ets:delete(T1),
?line verify_etsmem(EtsMem).
ordered(doc) ->
["Test basic functionality in ordered_set's."];
ordered(suite) ->
[];
ordered(Config) when is_list(Config) ->
repeat_for_opts(ordered_do).
ordered_do(Opts) ->
?line EtsMem = etsmem(),
?line T = ets:new(oset, [ordered_set | Opts]),
?line InsList = [
25,26,27,28,
5,6,7,8,
21,22,23,24,
9,10,11,12,
1,2,3,4,
17,18,19,20,
13,14,15,16
],
?line lists:foreach(fun(X) ->
ets:insert(T,{X,integer_to_list(X)})
end,
InsList),
?line IL2 = lists:map(fun(X) -> {X,integer_to_list(X)} end, InsList),
?line L1 = pick_all_forward(T),
?line L2 = pick_all_backwards(T),
?line S1 = lists:sort(IL2),
?line S2 = lists:reverse(lists:sort(IL2)),
?line S1 = L1,
?line S2 = L2,
?line [{1,"1"}] = ets:slot(T,0),
?line [{28,"28"}] = ets:slot(T,27),
?line 27 = ets:prev(T,28),
?line [{7,"7"}] = ets:slot(T,6),
?line '$end_of_table' = ets:next(T,28),
?line [{12,"12"}] = ets:slot(T,11),
?line '$end_of_table' = ets:slot(T,28),
?line [{1,"1"}] = ets:slot(T,0),
?line 28 = ets:prev(T,29),
?line 1 = ets:next(T,0),
?line pick_all_forward(T),
?line [{7,"7"}] = ets:slot(T,6),
?line L2 = pick_all_backwards(T),
?line [{7,"7"}] = ets:slot(T,6),
?line ets:delete(T),
?line verify_etsmem(EtsMem).
pick_all(_T,'$end_of_table',_How) ->
[];
pick_all(T,Last,How) ->
?line This = case How of
next ->
?line ets:next(T,Last);
prev ->
?line ets:prev(T,Last)
end,
?line [LastObj] = ets:lookup(T,Last),
?line [LastObj | pick_all(T,This,How)].
pick_all_forward(T) ->
?line pick_all(T,ets:first(T),next).
pick_all_backwards(T) ->
?line pick_all(T,ets:last(T),prev).
setbag(doc) -> ["Small test case for both set and bag type ets tables."];
setbag(suite) -> [];
setbag(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line Set = ets:new(set,[set]),
?line Bag = ets:new(bag,[bag]),
?line Key = {foo,bar},
%% insert some value
?line ets:insert(Set,{Key,val1}),
?line ets:insert(Bag,{Key,val1}),
%% insert new value for same key again
?line ets:insert(Set,{Key,val2}),
?line ets:insert(Bag,{Key,val2}),
%% check
?line [{Key,val2}] = ets:lookup(Set,Key),
?line [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
true = ets:delete(Set),
true = ets:delete(Bag),
?line verify_etsmem(EtsMem).
badnew(doc) ->
["Test case to check proper return values for illegal ets:new() calls."];
badnew(suite) -> [];
badnew(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line {'EXIT',{badarg,_}} = (catch ets:new(12,[])),
?line {'EXIT',{badarg,_}} = (catch ets:new({a,b},[])),
?line {'EXIT',{badarg,_}} = (catch ets:new(name,[foo])),
?line {'EXIT',{badarg,_}} = (catch ets:new(name,{bag})),
?line {'EXIT',{badarg,_}} = (catch ets:new(name,bag)),
?line verify_etsmem(EtsMem).
verybadnew(doc) ->
["Test case to check that a not well formed list does not crash the "
"emulator. OTP-2314 "];
verybadnew(suite) -> [];
verybadnew(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line {'EXIT',{badarg,_}} = (catch ets:new(verybad,[set|protected])),
?line verify_etsmem(EtsMem).
named(doc) -> ["Small check to see if named tables work."];
named(suite) -> [];
named(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line Tab = make_table(foo,
[named_table],
[{key,val}]),
?line [{key,val}] = ets:lookup(foo,key),
?line true = ets:delete(Tab),
?line verify_etsmem(EtsMem).
keypos2(doc) -> ["Test case to check if specified keypos works."];
keypos2(suite) -> [];
keypos2(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line Tab = make_table(foo,
[set,{keypos,2}],
[{val,key}, {val2,key}]),
?line [{val2,key}] = ets:lookup(Tab,key),
?line true = ets:delete(Tab),
?line verify_etsmem(EtsMem).
privacy(doc) ->
["Privacy check. Check that a named(public/private/protected) table "
"cannot be read by",
"the wrong process(es)."];
privacy(suite) -> [];
privacy(Config) when is_list(Config) ->
repeat_for_opts(privacy_do).
privacy_do(Opts) ->
?line EtsMem = etsmem(),
?line process_flag(trap_exit,true),
?line Owner = my_spawn_link(?MODULE,privacy_owner,[self(),Opts]),
receive
{'EXIT',Owner,Reason} ->
?line exit({privacy_test,Reason});
ok ->
ok
end,
privacy_check(pub,prot,priv),
Owner ! {shift,1,{pub,prot,priv}},
receive {Pub1,Prot1,Priv1} -> ok end,
privacy_check(Pub1,Prot1,Priv1),
Owner ! {shift,2,{Pub1,Prot1,Priv1}},
receive {Pub2,Prot2,Priv2} -> ok end,
privacy_check(Pub2,Prot2,Priv2),
Owner ! {shift,0,{Pub2,Prot2,Priv2}},
receive {Pub2,Prot2,Priv2} -> ok end,
privacy_check(Pub2,Prot2,Priv2),
Owner ! die,
receive {'EXIT',Owner,_} -> ok end,
?line verify_etsmem(EtsMem).
privacy_check(Pub,Prot,Priv) ->
%% check read rights
?line [] = ets:lookup(Pub, foo),
?line [] = ets:lookup(Prot,foo),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(Priv,foo)),
%% check write rights
?line true = ets:insert(Pub, {1,foo}),
?line {'EXIT',{badarg,_}} = (catch ets:insert(Prot,{2,foo})),
?line {'EXIT',{badarg,_}} = (catch ets:insert(Priv,{3,foo})),
%% check that it really wasn't written, either
?line [] = ets:lookup(Prot,foo).
privacy_owner(Boss, Opts) ->
ets:new(pub, [public,named_table | Opts]),
ets:new(prot,[protected,named_table | Opts]),
ets:new(priv,[private,named_table | Opts]),
Boss ! ok,
privacy_owner_loop(Boss).
privacy_owner_loop(Boss) ->
receive
{shift,N,Pub_Prot_Priv} ->
{Pub,Prot,Priv} = rotate_tuple(Pub_Prot_Priv, N),
ets:setopts(Pub,{protection,public}),
ets:setopts(Prot,{protection,protected}),
ets:setopts(Priv,{protection,private}),
Boss ! {Pub,Prot,Priv},
privacy_owner_loop(Boss);
die -> ok
end.
rotate_tuple(Tuple, 0) ->
Tuple;
rotate_tuple(Tuple, N) ->
[H|T] = tuple_to_list(Tuple),
rotate_tuple(list_to_tuple(T ++ [H]), N-1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
insert(doc) -> ["Test proper and improper inserts into a table."];
insert(suite) -> [empty,badinsert].
empty(doc) ->
["Check lookup in an empty table and lookup of a non-existing key"];
empty(suite) -> [];
empty(Config) when is_list(Config) ->
repeat_for_opts(empty_do).
empty_do(Opts) ->
?line EtsMem = etsmem(),
?line Tab = ets:new(foo,Opts),
?line [] = ets:lookup(Tab,key),
?line true = ets:insert(Tab,{key2,val}),
?line [] = ets:lookup(Tab,key),
?line true = ets:delete(Tab),
?line verify_etsmem(EtsMem).
badinsert(doc) ->
["Check proper return values for illegal insert operations."];
badinsert(suite) -> [];
badinsert(Config) when is_list(Config) ->
repeat_for_opts(badinsert_do).
badinsert_do(Opts) ->
?line EtsMem = etsmem(),
?line {'EXIT',{badarg,_}} = (catch ets:insert(foo,{key,val})),
?line Tab = ets:new(foo,Opts),
?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab,{})),
?line Tab3 = ets:new(foo,[{keypos,3}| Opts]),
?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab3,{a,b})),
?line {'EXIT',{badarg,_}} = (catch ets:insert(Tab,[key,val2])),
?line true = ets:delete(Tab),
?line true = ets:delete(Tab3),
?line verify_etsmem(EtsMem).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
lookup(doc) -> ["Some tests for lookups (timing, bad lookups, etc.)."];
lookup(suite) -> [time_lookup,badlookup,lookup_order].
time_lookup(doc) -> ["Lookup timing."];
time_lookup(suite) -> [];
time_lookup(Config) when is_list(Config) ->
%% just for timing, really
?line EtsMem = etsmem(),
Values = repeat_for_opts(time_lookup_do),
?line verify_etsmem(EtsMem),
?line {comment,lists:flatten(io_lib:format(
"~p ets lookups/s",[Values]))}.
time_lookup_do(Opts) ->
?line Tab = ets:new(foo,Opts),
?line fill_tab(Tab,foo),
?line ets:insert(Tab,{{a,key},foo}),
?line {Time,_} = ?t:timecall(test_server,do_times,
[10000,ets,lookup,[Tab,{a,key}]]),
?line true = ets:delete(Tab),
round(10000 / Time). % lookups/s
badlookup(doc) ->
["Check proper return values from bad lookups in existing/non existing "
" ets tables"];
badlookup(suite) -> [];
badlookup(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(foo,key)),
?line Tab = ets:new(foo,[]),
?line ets:delete(Tab),
?line {'EXIT',{badarg,_}} = (catch ets:lookup(Tab,key)),
?line verify_etsmem(EtsMem).
lookup_order(doc) -> ["Test that lookup returns objects in order of insertion for bag and dbag."];
lookup_order(suite) -> [];
lookup_order(Config) when is_list(Config) ->
EtsMem = etsmem(),
repeat_for_opts(lookup_order_do, [write_concurrency,[bag,duplicate_bag]]),
?line verify_etsmem(EtsMem),
ok.
lookup_order_do(Opts) ->
lookup_order_2(Opts, false),
lookup_order_2(Opts, true).
lookup_order_2(Opts, Fixed) ->
io:format("Opts=~p Fixed=~p\n",[Opts,Fixed]),
A = 1, B = 2, C = 3,
ABC = [A,B,C],
Pair = [{A,B},{B,A},{A,C},{C,A},{B,C},{C,B}],
Combos = [{D1,D2,D3} || D1<-ABC, D2<-Pair, D3<-Pair],
lists:foreach(fun({D1,{D2a,D2b},{D3a,D3b}}) ->
T = ets:new(foo,Opts),
case Fixed of
true -> ets:safe_fixtable(T,true);
false -> ok
end,
S10 = {T,[],key},
S20 = check_insert(S10,A),
S30 = check_insert(S20,B),
S40 = check_insert(S30,C),
S50 = check_delete(S40,D1),
S55 = check_insert(S50,D1),
S60 = check_insert(S55,D1),
S70 = check_delete(S60,D2a),
S80 = check_delete(S70,D2b),
S90 = check_insert(S80,D2a),
SA0 = check_delete(S90,D3a),
SB0 = check_delete(SA0,D3b),
check_insert_new(SB0,D3b),
true = ets:delete(T)
end,
Combos).
check_insert({T,List0,Key},Val) ->
%%io:format("insert ~p into ~p\n",[Val,List0]),
ets:insert(T,{Key,Val}),
List1 = case (ets:info(T,type) =:= bag andalso
lists:member({Key,Val},List0)) of
true -> List0;
false -> [{Key,Val} | List0]
end,
check_check({T,List1,Key}).
check_insert_new({T,List0,Key},Val) ->
%%io:format("insert_new ~p into ~p\n",[Val,List0]),
Ret = ets:insert_new(T,{Key,Val}),
?line Ret = (List0 =:= []),
List1 = case Ret of
true -> [{Key,Val}];
false -> List0
end,
check_check({T,List1,Key}).
check_delete({T,List0,Key},Val) ->
%%io:format("delete ~p from ~p\n",[Val,List0]),
ets:delete_object(T,{Key,Val}),
List1 = lists:filter(fun(Obj) -> Obj =/= {Key,Val} end,
List0),
check_check({T,List1,Key}).
check_check(S={T,List,Key}) ->
case lists:reverse(ets:lookup(T,Key)) of
List -> ok;
ETS -> io:format("check failed:\nETS: ~p\nCHK: ~p\n", [ETS,List]),
?t:fail("Invalid return value from ets:lookup")
end,
?line Items = ets:info(T,size),
?line Items = length(List),
S.
fill_tab(Tab,Val) ->
?line ets:insert(Tab,{key,Val}),
?line ets:insert(Tab,{{a,144},Val}),
?line ets:insert(Tab,{{a,key2},Val}),
?line ets:insert(Tab,{14,Val}),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
lookup_element(doc) -> ["Some tests for lookup_element."];
lookup_element(suite) -> [lookup_element_mult].
lookup_element_mult(doc) -> ["Multiple return elements (OTP-2386)"];
lookup_element_mult(suite) -> [];
lookup_element_mult(Config) when is_list(Config) ->
repeat_for_opts(lookup_element_mult_do).
lookup_element_mult_do(Opts) ->
?line EtsMem = etsmem(),
?line T = ets:new(service, [bag, {keypos, 2} | Opts]),
?line D = lists:reverse(lem_data()),
?line lists:foreach(fun(X) -> ets:insert(T, X) end, D),
?line ok = lem_crash_3(T),
?line true = ets:delete(T),
?line verify_etsmem(EtsMem).
lem_data() ->
[
{service,'eddie2@boromir',{150,236,14,103},httpd88,self()},
{service,'eddie2@boromir',{150,236,14,103},httpd80,self()},
{service,'eddie3@boromir',{150,236,14,107},httpd88,self()},
{service,'eddie3@boromir',{150,236,14,107},httpd80,self()},
{service,'eddie4@boromir',{150,236,14,108},httpd88,self()}
].
lem_crash(T) ->
L = ets:lookup_element(T, 'eddie2@boromir', 3),
{erlang:phash(L, 256), L}.
lem_crash_3(T) ->
lem_crash(T),
io:format("Survived once~n"),
lem_crash(T),
io:format("Survived twice~n"),
lem_crash(T),
io:format("Survived all!~n"),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
delete(doc) ->
["Check delete functionality (proper/improper deletes)"];
delete(suite) ->
[delete_elem,delete_tab,delete_large_tab,delete_large_named_table,evil_delete,
table_leak,baddelete,match_delete,match_delete3].
delete_elem(doc) ->
["Check delete of an element inserted in a `filled' table."];
delete_elem(suite) -> [];
delete_elem(Config) when is_list(Config) ->
repeat_for_opts(delete_elem_do, [write_concurrency, all_types]).
delete_elem_do(Opts) ->
?line EtsMem = etsmem(),
?line Tab = ets:new(foo,Opts),
?line fill_tab(Tab,foo),
?line ets:insert(Tab,{{b,key},foo}),
?line ets:insert(Tab,{{c,key},foo}),
?line true = ets:delete(Tab,{b,key}),
?line [] = ets:lookup(Tab,{b,key}),
?line [{{c,key},foo}] = ets:lookup(Tab,{c,key}),
?line true = ets:delete(Tab),
?line verify_etsmem(EtsMem).
delete_tab(doc) ->
["Check that ets:delete() works and releases the name of the deleted "
"table."];
delete_tab(suite) -> [];
delete_tab(Config) when is_list(Config) ->
repeat_for_opts(delete_tab_do,[write_concurrency,all_types]).
delete_tab_do(Opts) ->
Name = foo,
?line EtsMem = etsmem(),
?line Name = ets:new(Name, [named_table | Opts]),
?line true = ets:delete(foo),
%% The name should be available again.
?line Name = ets:new(Name, [named_table | Opts]),
?line true = ets:delete(Name),
?line verify_etsmem(EtsMem).
delete_large_tab(doc) ->
"Check that ets:delete/1 works and that other processes can run.";
delete_large_tab(Config) when is_list(Config) ->
?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
?line EtsMem = etsmem(),
repeat_for_opts(fun(Opts) -> delete_large_tab_do(Opts,Data) end),
?line verify_etsmem(EtsMem).
delete_large_tab_do(Opts,Data) ->
?line delete_large_tab_1(foo_hash, Opts, Data, false),
?line delete_large_tab_1(foo_tree, [ordered_set | Opts], Data, false),
?line delete_large_tab_1(foo_hash, Opts, Data, true).
delete_large_tab_1(Name, Flags, Data, Fix) ->
?line Tab = ets:new(Name, Flags),
?line ets:insert(Tab, Data),
case Fix of
false -> ok;
true ->
?line true = ets:safe_fixtable(Tab, true),
?line lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
end,
{priority, Prio} = process_info(self(), priority),
?line Deleter = self(),
?line [SchedTracer]
= start_loopers(1,
Prio,
fun (SC) ->
receive
{trace, Deleter, out, _} ->
undefined = ets:info(Tab),
SC+1;
{trace,
Deleter,
register,
delete_large_tab_done_marker}->
Deleter ! {schedule_count, SC},
exit(normal);
_ ->
SC
end
end,
0),
?line Loopers = start_loopers(erlang:system_info(schedulers),
Prio,
fun (_) -> erlang:yield() end,
ok),
?line erlang:yield(),
?line 1 = erlang:trace(self(),true,[running,procs,{tracer,SchedTracer}]),
?line true = ets:delete(Tab),
%% The register stuff is just a trace marker
?line true = register(delete_large_tab_done_marker, self()),
?line true = unregister(delete_large_tab_done_marker),
?line undefined = ets:info(Tab),
?line ok = stop_loopers(Loopers),
?line receive
{schedule_count, N} ->
?line io: