Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

5376 lines (4814 sloc) 174.189 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:format("~s: context switches: ~p", [Name,N]),
if
N >= 5 -> ?line ok;
true -> ?line ?t:fail()
end
end.
delete_large_named_table(doc) ->
"Delete a large name table and try to create a new table with the same name in another process.";
delete_large_named_table(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_named_table_do(Opts,Data) end),
?line verify_etsmem(EtsMem),
ok.
delete_large_named_table_do(Opts,Data) ->
?line delete_large_named_table_1(foo_hash, [named_table | Opts], Data, false),
?line delete_large_named_table_1(foo_tree, [ordered_set,named_table | Opts], Data, false),
?line delete_large_named_table_1(foo_hash, [named_table | Opts], Data, true).
delete_large_named_table_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,
Parent = self(),
Pid = spawn_link(fun() ->
receive
{trace,Parent,call,_} ->
ets:new(Name, [named_table])
end
end),
?line erlang:trace(self(), true, [call,{tracer,Pid}]),
?line erlang:trace_pattern({ets,delete,1}, true, [global]),
?line erlang:yield(), true = ets:delete(Tab),
?line erlang:trace_pattern({ets,delete,1}, false, [global]),
ok.
evil_delete(doc) ->
"Delete a large table, and kill the process during the delete.";
evil_delete(Config) when is_list(Config) ->
?line Data = [{I,I*I} || I <- lists:seq(1, 100000)],
repeat_for_opts(fun(Opts) -> evil_delete_do(Opts,Data) end).
evil_delete_do(Opts,Data) ->
?line EtsMem = etsmem(),
?line evil_delete_owner(foo_hash, Opts, Data, false),
?line verify_etsmem(EtsMem),
?line evil_delete_owner(foo_hash, Opts, Data, true),
?line verify_etsmem(EtsMem),
?line evil_delete_owner(foo_tree, [ordered_set | Opts], Data, false),
?line verify_etsmem(EtsMem),
?line TabA = evil_delete_not_owner(foo_hash, Opts, Data, false),
?line verify_etsmem(EtsMem),
?line TabB = evil_delete_not_owner(foo_hash, Opts, Data, true),
?line verify_etsmem(EtsMem),
?line TabC = evil_delete_not_owner(foo_tree, [ordered_set | Opts], Data, false),
?line verify_etsmem(EtsMem),
?line lists:foreach(fun(T) -> undefined = ets:info(T) end,
[TabA,TabB,TabC]).
evil_delete_not_owner(Name, Flags, Data, Fix) ->
io:format("Not owner: ~p, fix = ~p", [Name,Fix]),
?line Tab = ets:new(Name, [public|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,
?line Pid = my_spawn(fun() ->
P = my_spawn_link(
fun() ->
receive kill -> ok end,
erlang:yield(),
exit(kill_linked_processes_now)
end),
erlang:yield(),
P ! kill,
true = ets:delete(Tab)
end),
?line Ref = erlang:monitor(process, Pid),
?line receive {'DOWN',Ref,_,_,_} -> ok end,
Tab.
evil_delete_owner(Name, Flags, Data, Fix) ->
?line Fun = fun() ->
?line Tab = ets:new(Name, [public|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,
erlang:yield(),
my_spawn_link(fun() ->
erlang:yield(),
exit(kill_linked_processes_now)
end),
true = ets:delete(Tab)
end,
?line Pid = my_spawn(Fun),
?line Ref = erlang:monitor(process, Pid),
?line receive {'DOWN',Ref,_,_,_} -> ok end.
exit_large_table_owner(doc) ->
[];
exit_large_table_owner(suite) ->
[];
exit_large_table_owner(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) -> exit_large_table_owner_do(Opts,Data,Config) end),
?line verify_etsmem(EtsMem).
exit_large_table_owner_do(Opts,Data,Config) ->
?line verify_rescheduling_exit(Config, Data, [named_table | Opts], true, 1, 1),
?line verify_rescheduling_exit(Config, Data, Opts, false, 1, 1).
exit_many_large_table_owner(doc) -> [];
exit_many_large_table_owner(suite) -> [];
exit_many_large_table_owner(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) -> exit_many_large_table_owner_do(Opts,Data,Config) end),
?line verify_etsmem(EtsMem).
exit_many_large_table_owner_do(Opts,Data,Config) ->
?line verify_rescheduling_exit(Config, Data, Opts, true, 1, 4),
?line verify_rescheduling_exit(Config, Data, [named_table | Opts], false, 1, 4).
exit_many_tables_owner(doc) -> [];
exit_many_tables_owner(suite) -> [];
exit_many_tables_owner(Config) when is_list(Config) ->
?line EtsMem = etsmem(),
?line verify_rescheduling_exit(Config, [], [named_table], false, 1000, 1),
?line verify_rescheduling_exit(Config, [], [named_table,{write_concurrency,true}], false, 1000, 1),
?line verify_etsmem(EtsMem).
exit_many_many_tables_owner(doc) -> [];
exit_many_many_tables_owner(suite) -> [];
exit_many_many_tables_owner(Config) when is_list(Config) ->
?line Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 50)],
repeat_for_opts(fun(Opts) -> exit_many_many_tables_owner_do(Opts,Data,Config) end).
exit_many_many_tables_owner_do(Opts,Data,Config) ->
?line verify_rescheduling_exit(Config, Data, [named_table | Opts], true, 200, 5),
?line verify_rescheduling_exit(Config, Data, Opts, false, 200, 5),
?line wait_for_test_procs(),
?line EtsMem = etsmem(),
?line verify_rescheduling_exit(Config, Data, Opts, true, 200, 5),
?line verify_rescheduling_exit(Config, Data, [named_table | Opts], false, 200, 5),
?