aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/ets_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/test/ets_SUITE.erl')
-rw-r--r--lib/stdlib/test/ets_SUITE.erl5355
1 files changed, 5355 insertions, 0 deletions
diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl
new file mode 100644
index 0000000000..6016bc9bdc
--- /dev/null
+++ b/lib/stdlib/test/ets_SUITE.erl
@@ -0,0 +1,5355 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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 ->
+ ?line true = (Heir =:= none) orelse (not is_process_alive(Heir)),
+ Master ! {"No heir",self()};
+
+ 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,
+ ?line Msg = case HeirData of
+ "The busy heir" -> receive_any_spinning();
+ _ -> receive_any()
+ end,
+ ?line {'ETS-TRANSFER', T, Founder, HeirData} = Msg,
+ ?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),
+ ?line verify_etsmem(EtsMem).
+
+
+count_exit_sched(TP) ->
+ receive
+ {trace, TP, in_exiting, 0} ->
+ count_exit_sched_out(TP, 1);
+ {trace, TP, out_exiting, 0} ->
+ count_exit_sched_in(TP, 1);
+ {trace, TP, out_exited, 0} ->
+ 0
+ end.
+
+count_exit_sched_in(TP, N) ->
+ receive
+ {trace, TP, in_exiting, 0} ->
+ count_exit_sched_out(TP, N);
+ {trace, TP, _, _} = Msg ->
+ exit({unexpected_trace_msg, Msg})
+ end.
+
+count_exit_sched_out(TP, N) ->
+ receive
+ {trace, TP, out_exiting, 0} ->
+ count_exit_sched_in(TP, N+1);
+ {trace, TP, out_exited, 0} ->
+ N;
+ {trace, TP, _, _} = Msg ->
+ exit({unexpected_trace_msg, Msg})
+ end.
+
+vre_fix_tables(Tab) ->
+ Parent = self(),
+ Go = make_ref(),
+ my_spawn_link(fun () ->
+ true = ets:safe_fixtable(Tab, true),
+ Parent ! Go,
+ receive infinity -> ok end
+ end),
+ receive Go -> ok end,
+ ok.
+
+verify_rescheduling_exit(Config, Data, Flags, Fix, NOTabs, NOProcs) ->
+ ?line NoFix = 5,
+ ?line TestCase = atom_to_list(?config(test_case, Config)),
+ ?line Parent = self(),
+ ?line KillMe = make_ref(),
+ ?line PFun =
+ fun () ->
+ repeat(
+ fun () ->
+ {A, B, C} = now(),
+ ?line Name = list_to_atom(
+ TestCase
+ ++ "-" ++ integer_to_list(A)
+ ++ "-" ++ integer_to_list(B)
+ ++ "-" ++ integer_to_list(C)),
+ Tab = ets:new(Name, Flags),
+ ets:insert(Tab, Data),
+ case Fix of
+ false -> ok;
+ true ->
+ lists:foreach(fun (_) ->
+ vre_fix_tables(Tab)
+ end,
+ lists:seq(1,NoFix)),
+ lists:foreach(fun({K,_}) ->
+ ets:delete(Tab, K)
+ end,
+ Data)
+ end
+ end,
+ NOTabs),
+ Parent ! {KillMe, self()},
+ receive after infinity -> ok end
+ end,
+ ?line TPs = lists:map(fun (_) ->
+ ?line TP = my_spawn_link(PFun),
+ ?line 1 = erlang:trace(TP, true, [exiting]),
+ TP
+ end,
+ lists:seq(1, NOProcs)),
+ ?line lists:foreach(fun (TP) ->
+ receive {KillMe, TP} -> ok end
+ end,
+ TPs),
+ ?line LPs = start_loopers(erlang:system_info(schedulers),
+ normal,
+ fun (_) ->
+ erlang:yield()
+ end,
+ ok),
+ ?line lists:foreach(fun (TP) ->
+ ?line unlink(TP),
+ ?line exit(TP, bang)
+ end,
+ TPs),
+ ?line lists:foreach(fun (TP) ->
+ ?line XScheds = count_exit_sched(TP),
+ ?line ?t:format("~p XScheds=~p~n",
+ [TP, XScheds]),
+ ?line true = XScheds >= 5
+ end,
+ TPs),
+ ?line stop_loopers(LPs),
+ ?line ok.
+
+
+
+table_leak(doc) ->
+ "Make sure that slots for ets tables are cleared properly.";
+table_leak(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> table_leak_1(Opts,20000) end).
+
+table_leak_1(_,0) -> ok;
+table_leak_1(Opts,N) ->
+ ?line T = ets:new(fooflarf, Opts),
+ ?line true = ets:delete(T),
+ table_leak_1(Opts,N-1).
+
+baddelete(doc) ->
+ ["Check proper return values for illegal delete operations."];
+baddelete(suite) -> [];
+baddelete(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line {'EXIT',{badarg,_}} = (catch ets:delete(foo)),
+ ?line Tab = ets:new(foo,[]),
+ ?line true = ets:delete(Tab),
+ ?line {'EXIT',{badarg,_}} = (catch ets:delete(Tab)),
+ ?line verify_etsmem(EtsMem).
+
+match_delete(doc) ->
+ ["Check that match_delete works. Also tests tab2list function."];
+match_delete(suite) -> [];
+match_delete(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ repeat_for_opts(match_delete_do,[write_concurrency,all_types]),
+ ?line verify_etsmem(EtsMem).
+
+match_delete_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(kad,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line ets:insert(Tab,{{c,key},bar}),
+ ?line _ = ets:match_delete(Tab,{'_',foo}),
+ ?line [{{c,key},bar}] = ets:tab2list(Tab),
+ ?line _ = ets:match_delete(Tab,'_'),
+ ?line [] = ets:tab2list(Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_delete3(doc) ->
+ ["OTP-3005: check match_delete with constant argument."];
+match_delete3(suite) -> [];
+match_delete3(Config) when is_list(Config) ->
+ repeat_for_opts(match_delete3_do).
+
+match_delete3_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ T = make_table(test,
+ [duplicate_bag | Opts],
+ [{aa,17},
+ {cA,1000},
+ {cA,17},
+ {cA,1000},
+ {aa,17}]),
+ %% 'aa' and 'cA' have the same hash value in the current
+ %% implementation. This causes the aa's to precede the cA's, to make
+ %% the test more interesting.
+ [{cA,1000},{cA,1000}] = ets:match_object(T, {'_', 1000}),
+ ets:match_delete(T, {cA,1000}),
+ [] = ets:match_object(T, {'_', 1000}),
+ ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+firstnext(doc) -> ["Tests ets:first/1 & ets:next/2."];
+firstnext(suite) -> [];
+firstnext(Config) when is_list(Config) ->
+ repeat_for_opts(firstnext_do).
+
+firstnext_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line [] = firstnext_collect(Tab,ets:first(Tab),[]),
+ ?line fill_tab(Tab,foo),
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line Len = length(firstnext_collect(Tab,ets:first(Tab),[])),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+firstnext_collect(_Tab,'$end_of_table',List) ->
+ ?line List;
+firstnext_collect(Tab,Key,List) ->
+ ?line firstnext_collect(Tab,ets:next(Tab,Key),[Key|List]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+firstnext_concurrent(doc) -> "Tests ets:first/1 & ets:next/2.";
+firstnext_concurrent(Config) when is_list(Config) ->
+ register(master, self()),
+ ets_init(?MODULE, 20),
+ [dynamic_go() || _ <- lists:seq(1, 2)],
+ receive
+ after 5000 -> ok
+ end.
+
+ets_init(Tab, N) ->
+ ets:new(Tab, [named_table,public,ordered_set]),
+ cycle(Tab, lists:seq(1,N+1)).
+
+cycle(_Tab, [H|T]) when H > length(T)-> ok;
+cycle(Tab, L) ->
+ ets:insert(Tab,list_to_tuple(L)),
+ cycle(Tab, tl(L)++[hd(L)]).
+
+dynamic_go() -> spawn_link(fun dynamic_init/0).
+
+dynamic_init() -> [dyn_lookup(?MODULE) || _ <- lists:seq(1, 10)].
+
+dyn_lookup(T) -> dyn_lookup(T, ets:first(T)).
+
+dyn_lookup(_T, '$end_of_table') -> [];
+dyn_lookup(T, K) ->
+ NextKey=ets:next(T,K),
+ case ets:next(T,K) of
+ NextKey ->
+ dyn_lookup(T, NextKey);
+ NK ->
+ io:fwrite("hmmm... ~p =/= ~p~n", [NextKey,NK]),
+ exit(failed)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+slot(suite) -> [];
+slot(Config) when is_list(Config) ->
+ repeat_for_opts(slot_do).
+
+slot_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line Elts = ets:info(Tab,size),
+ ?line Elts = slot_loop(Tab,0,0),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+slot_loop(Tab,SlotNo,EltsSoFar) ->
+ ?line case ets:slot(Tab,SlotNo) of
+ '$end_of_table' ->
+ ?line {'EXIT',{badarg,_}} =
+ (catch ets:slot(Tab,SlotNo+1)),
+ ?line EltsSoFar;
+ Elts ->
+ ?line slot_loop(Tab,SlotNo+1,EltsSoFar+length(Elts))
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+match(suite) -> [match1, match2, match_object, match_object2].
+
+match1(suite) -> [];
+match1(Config) when is_list(Config) ->
+ repeat_for_opts(match1_do).
+
+match1_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line fill_tab(Tab,foo),
+ ?line [] = ets:match(Tab,{}),
+ ?line ets:insert(Tab,{{one,4},4}),
+ ?line ets:insert(Tab,{{one,5},5}),
+ ?line ets:insert(Tab,{{two,4},4}),
+ ?line ets:insert(Tab,{{two,5},6}),
+ ?line case ets:match(Tab,{{one,'_'},'$0'}) of
+ [[4],[5]] -> ok;
+ [[5],[4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$1'},'$0'}) of
+ [[4,4],[6,5]] -> ok;
+ [[6,5],[4,4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$9'},'$4'}) of
+ [[4,4],[6,5]] -> ok;
+ [[6,5],[4,4]] -> ok
+ end,
+ ?line case ets:match(Tab,{{two,'$9'},'$22'}) of
+ [[4,4],[5,6]] -> ok;
+ [[5,6],[4,4]] -> ok
+ end,
+ ?line [[4]] = ets:match(Tab,{{two,'$0'},'$0'}),
+ ?line Len = length(ets:match(Tab,'$0')),
+ ?line Len = length(ets:match(Tab,'_')),
+ ?line if Len > 4 -> ok end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match2(doc) -> ["Tests match with specified keypos bag table."];
+match2(suite) -> [];
+match2(Config) when is_list(Config) ->
+ repeat_for_opts(match2_do).
+
+match2_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foobar,
+ [bag, named_table, {keypos, 2} | Opts],
+ [{value1, key1},
+ {value2_1, key2},
+ {value2_2, key2},
+ {value3_1, key3},
+ {value3_2, key3},
+ {value2_1, key2_wannabe}]),
+ ?line case length(ets:match(Tab, '$1')) of
+ 6 -> ok;
+ _ -> ?t:fail("Length of matched list is wrong.")
+ end,
+ ?line [[value3_1],[value3_2]] = ets:match(Tab, {'$1', key3}),
+ ?line [[key1]] = ets:match(Tab, {value1, '$1'}),
+ ?line [[key2_wannabe],[key2]] = ets:match(Tab, {value2_1, '$2'}),
+ ?line [] = ets:match(Tab,{'$1',nosuchkey}),
+ ?line [] = ets:match(Tab,{'$1',kgY2}), % same hash as key2
+ ?line [] = ets:match(Tab,{nosuchvalue,'$1'}),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_object(doc) -> ["Some ets:match_object test."];
+match_object(suite) -> [];
+match_object(Config) when is_list(Config) ->
+ repeat_for_opts(match_object_do).
+
+match_object_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar, Opts),
+ ?line fill_tab(Tab, foo),
+ ?line ets:insert(Tab, {{one, 4}, 4}),
+ ?line ets:insert(Tab,{{one,5},5}),
+ ?line ets:insert(Tab,{{two,4},4}),
+ ?line ets:insert(Tab,{{two,5},6}),
+ ?line case ets:match_object(Tab, {{one, '_'}, '$0'}) of
+ [{{one,5},5},{{one,4},4}] -> ok;
+ [{{one,4},4},{{one,5},5}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$1'}, '$0'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$9'}, '$4'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ ?line case ets:match_object(Tab, {{two, '$9'}, '$22'}) of
+ [{{two,5},6},{{two,4},4}] -> ok;
+ [{{two,4},4},{{two,5},6}] -> ok;
+ _ -> ?t:fail("ets:match_object() returned something funny.")
+ end,
+ % Check that unsucessful match returns an empty list.
+ ?line [] = ets:match_object(Tab, {{three,'$0'}, '$92'}),
+ % Check that '$0' equals '_'.
+ Len = length(ets:match_object(Tab, '$0')),
+ Len = length(ets:match_object(Tab, '_')),
+ ?line if Len > 4 -> ok end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+match_object2(suite) -> [];
+match_object2(doc) -> ["Tests that db_match_object does not generate "
+ "a `badarg' when resuming a search with no "
+ "previous matches."];
+match_object2(Config) when is_list(Config) ->
+ repeat_for_opts(match_object2_do).
+
+match_object2_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo, [bag, {keypos, 2} | Opts]),
+ ?line fill_tab2(Tab, 0, 13005), % match_db_object does 1000
+ % elements per pass, might
+ % change in the future.
+ ?line case catch ets:match_object(Tab, {hej, '$1'}) of
+ {'EXIT', _} ->
+ ets:delete(Tab),
+ ?t:fail("match_object EXIT:ed");
+ [] ->
+ io:format("Nothing matched.");
+ List ->
+ io:format("Matched:~p~n",[List])
+ end,
+ ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+misc(suite) -> [misc1, safe_fixtable, info, dups, tab2list].
+
+tab2list(doc) -> ["Tests tab2list (OTP-3319)"];
+tab2list(suite) -> [];
+tab2list(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(foo,
+ [ordered_set],
+ [{a,b}, {c,b}, {b,b}, {a,c}]),
+ ?line [{a,c},{b,b},{c,b}] = ets:tab2list(Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+misc1(doc) -> ["Simple general small test. ",
+ "If this fails, ets is in really bad shape."];
+misc1(suite) -> [];
+misc1(Config) when is_list(Config) ->
+ repeat_for_opts(misc1_do).
+
+misc1_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo,Opts),
+ ?line true = lists:member(Tab,ets:all()),
+ ?line ets:delete(Tab),
+ ?line false = lists:member(Tab,ets:all()),
+ ?line case catch ets:delete(Tab) of
+ {'EXIT',_Reason} ->
+ ?line verify_etsmem(EtsMem);
+ true ->
+ ?t:fail("Delete of nonexisting table returned `true'.")
+ end,
+ ok.
+
+safe_fixtable(doc) -> ["Check the safe_fixtable function."];
+safe_fixtable(suite) -> [];
+safe_fixtable(Config) when is_list(Config) ->
+ repeat_for_opts(safe_fixtable_do).
+
+safe_fixtable_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foo, Opts),
+ ?line fill_tab(Tab, foobar),
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line receive after 1 -> ok end,
+ ?line true = ets:safe_fixtable(Tab, false),
+ ?line false = ets:info(Tab,safe_fixed),
+ ?line true = ets:safe_fixtable(Tab, true),
+ Self = self(),
+ ?line {{_,_,_},[{Self,1}]} = ets:info(Tab,safe_fixed),
+ %% Test that an unjustified 'unfix' is a no-op.
+ {Pid,MRef} = spawn_monitor(fun() -> true = ets:safe_fixtable(Tab,false) end),
+ {'DOWN', MRef, process, Pid, normal} = receive M -> M end,
+ ?line true = ets:info(Tab,fixed),
+ ?line {{_,_,_},[{Self,1}]} = ets:info(Tab,safe_fixed),
+ %% badarg's
+ ?line {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
+ ?line true = ets:info(Tab,fixed),
+ ?line true = ets:safe_fixtable(Tab, false),
+ ?line false = ets:info(Tab,fixed),
+ ?line {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
+ ?line false = ets:info(Tab,fixed),
+ ?line ets:delete(Tab),
+ ?line case catch ets:safe_fixtable(Tab, true) of
+ {'EXIT', _Reason} ->
+ ?line verify_etsmem(EtsMem);
+ _ ->
+ ?t:fail("Fixtable on nonexisting table returned `true'")
+ end,
+ ok.
+
+info(doc) -> ["Tests ets:info result for required tuples."];
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ repeat_for_opts(info_do).
+
+info_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line MeMyselfI=self(),
+ ?line ThisNode=node(),
+ ?line Tab = ets:new(foobar, [{keypos, 2} | Opts]),
+
+ %% Note: ets:info/1 used to return a tuple, but from R11B onwards it
+ %% returns a list.
+ ?line Res = ets:info(Tab),
+ ?line {value, {memory, _Mem}} = lists:keysearch(memory, 1, Res),
+ ?line {value, {owner, MeMyselfI}} = lists:keysearch(owner, 1, Res),
+ ?line {value, {name, foobar}} = lists:keysearch(name, 1, Res),
+ ?line {value, {size, 0}} = lists:keysearch(size, 1, Res),
+ ?line {value, {node, ThisNode}} = lists:keysearch(node, 1, Res),
+ ?line {value, {named_table, false}} = lists:keysearch(named_table, 1, Res),
+ ?line {value, {type, set}} = lists:keysearch(type, 1, Res),
+ ?line {value, {keypos, 2}} = lists:keysearch(keypos, 1, Res),
+ ?line {value, {protection, protected}} =
+ lists:keysearch(protection, 1, Res),
+ ?line true = ets:delete(Tab),
+ ?line undefined = ets:info(non_existing_table_xxyy),
+ ?line undefined = ets:info(non_existing_table_xxyy,type),
+ ?line undefined = ets:info(non_existing_table_xxyy,node),
+ ?line undefined = ets:info(non_existing_table_xxyy,named_table),
+ ?line undefined = ets:info(non_existing_table_xxyy,safe_fixed),
+ ?line verify_etsmem(EtsMem).
+
+dups(doc) -> ["Test various duplicate_bags stuff"];
+dups(suite) -> [];
+dups(Config) when is_list(Config) ->
+ repeat_for_opts(dups_do).
+
+dups_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = make_table(funky,
+ [duplicate_bag | Opts],
+ [{1, 2}, {1, 2}]),
+ ?line 2 = length(ets:tab2list(T)),
+ ?line ets:delete(T, 1),
+ ?line [] = ets:lookup(T, 1),
+
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 4}),
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 2}),
+ ?line ets:insert(T, {1, 2, 4}),
+
+ ?line 5 = length(ets:tab2list(T)),
+
+ ?line 5 = length(ets:match(T, {'$1', 2, '$2'})),
+ ?line 3 = length(ets:match(T, {'_', '$1', '$1'})),
+ ?line ets:match_delete(T, {'_', '$1', '$1'}),
+ ?line 0 = length(ets:match(T, {'_', '$1', '$1'})),
+ ?line ets:delete(T),
+ ?line verify_etsmem(EtsMem).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+files(suite) -> [tab2file, tab2file2, tab2file3, tabfile_ext1, tabfile_ext2,
+ tabfile_ext3, tabfile_ext4].
+
+tab2file(doc) -> ["Check the ets:tab2file function on an empty "
+ "ets table."];
+tab2file(suite) -> [];
+tab2file(Config) when is_list(Config) ->
+ %% Write an empty ets table to a file, read back and check properties.
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, set, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file_case"]),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+ %
+ ?line EtsMem = etsmem(),
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line set = ets:info(Tab2, type),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+tab2file2(doc) -> ["Check the ets:tab2file function on a ",
+ "filled set type ets table."];
+tab2file2(suite) -> [];
+tab2file2(Config) when is_list(Config) ->
+ %% Try the same on a filled set table.
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, set, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file2_case"]),
+ ?line ok = fill_tab2(Tab, 0, 10000), % Fill up the table (grucho mucho!)
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+ %
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line set = ets:info(Tab2, type),
+ ?line Len = length(ets:tab2list(Tab2)),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+tab2file3(doc) -> ["Check the ets:tab2file function on a ",
+ "filled bag type ets table."];
+tab2file3(suite) -> [];
+tab2file3(Config) when is_list(Config) ->
+ %% Try the same on a filled bag table.
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(ets_SUITE_foo_tab, [named_table, bag, private,
+ {keypos, 2}]),
+ ?line FName = filename:join([?config(priv_dir, Config),"tab2file3_case"]),
+ ?line ok = fill_tab2(Tab, 0, 10000), % Fill up the table (grucho mucho!)
+ ?line Len = length(ets:tab2list(Tab)),
+ ?line Mem = ets:info(Tab, memory),
+ ?line ok = ets:tab2file(Tab, FName),
+ ?line true = ets:delete(Tab),
+
+ ?line {ok, Tab2} = ets:file2tab(FName),
+ ?line private = ets:info(Tab2, protection),
+ ?line true = ets:info(Tab2, named_table),
+ ?line 2 = ets:info(Tab2, keypos),
+ ?line bag = ets:info(Tab2, type),
+ ?line Len = length(ets:tab2list(Tab2)),
+ ?line Mem = ets:info(Tab2, memory),
+ ?line true = ets:delete(Tab2),
+ ?line verify_etsmem(EtsMem).
+
+-define(test_list, [8,5,4,1,58,125,255, 250, 245, 240, 235,
+ 230, Num rem 255, 255, 125, 130, 135, 140, 145,
+ 150, 134, 12, 54, Val rem 255, 12, 3, 6, 9, 126]).
+-define(big_test_list, [Num rem 256|lists:seq(1, 66)]).
+-define(test_integer, 2846287468+Num).
+-define(test_float, 187263.18236-Val).
+-define(test_atom, some_crazy_atom).
+-define(test_tuple, {just, 'Some', 'Tuple', 1, [list, item], Val+Num}).
+
+%% Insert different datatypes into a ets table.
+fill_tab2(_Tab, _Val, 0) ->
+ ok;
+fill_tab2(Tab, Val, Num) ->
+ ?line Item =
+ case Num rem 10 of
+ 0 -> "String";
+ 1 -> ?line ?test_atom;
+ 2 -> ?line ?test_tuple;
+ 3 -> ?line ?test_integer;
+ 4 -> ?line ?test_float;
+ 5 -> ?line list_to_binary(?test_list); %Heap binary
+ 6 -> ?line list_to_binary(?big_test_list); %Refc binary
+ 7 -> ?line make_sub_binary(?test_list, Num); %Sub binary
+ 8 -> ?line ?test_list;
+ 9 -> ?line fun(X) -> {Tab,Val,X*Num} end
+ end,
+ ?line true=ets:insert(Tab, {Item, Val}),
+ ?line fill_tab2(Tab, Val+1, Num-1),
+ ok.
+
+tabfile_ext1(suite) ->
+ [];
+tabfile_ext1(doc) ->
+ ["Tests verification of tables with object count extended_info"];
+tabfile_ext1(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> tabfile_ext1_do(Opts, Config) end).
+
+tabfile_ext1_do(Opts,Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"nisse.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"countflip.dat"]),
+ L = lists:seq(1,10),
+ T = ets:new(x,Opts),
+ Name = make_ref(),
+ [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
+ ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(
+ element(2,ets:file2tab(FName,[{verify,true}])))),
+ {ok,Name} = disk_log:open([{name,Name},{file,FName}]),
+ {_,[H0|T0]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ LH0=tuple_to_list(H0),
+ {value,{size,N}}=lists:keysearch(size,1,LH0),
+ NewLH0 = lists:keyreplace(size,1,LH0,{size,N-1}),
+ NewH0 = list_to_tuple(NewLH0),
+ NewT0=lists:keydelete(8,1,T0),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[NewH0|NewT0]),
+ disk_log:close(Name),
+ 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
+ {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
+ {ok, _} = ets:tabfile_info(FName2),
+ {ok, _} = ets:tabfile_info(FName),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext2(suite) ->
+ [];
+tabfile_ext2(doc) ->
+ ["Tests verification of tables with md5sum extended_info"];
+tabfile_ext2(Config) when is_list(Config) ->
+ repeat_for_opts(fun(Opts) -> tabfile_ext2_do(Opts,Config) end).
+
+tabfile_ext2_do(Opts,Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"olle.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"bitflip.dat"]),
+ L = lists:seq(1,10),
+ T = ets:new(x,Opts),
+ Name = make_ref(),
+ [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
+ ok = ets:tab2file(T,FName,[{extended_info,[md5sum]}]),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ true = lists:sort(ets:tab2list(T)) =:=
+ lists:sort(ets:tab2list(
+ element(2,ets:file2tab(FName,[{verify,true}])))),
+ {ok, Name} = disk_log:open([{name,Name},{file,FName}]),
+ {_,[H1|T1]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ NewT1=lists:keyreplace(8,1,T1,{8,"9"}),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[H1|NewT1]),
+ disk_log:close(Name),
+ {value,{8,"9"}} = lists:keysearch(8,1,
+ ets:tab2list(
+ element(2,ets:file2tab(FName2)))),
+ {error,checksum_error} = ets:file2tab(FName2,[{verify,true}]),
+ {value,{extended_info,[md5sum]}} =
+ lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName2))),
+ {value,{extended_info,[md5sum]}} =
+ lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName))),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext3(suite) ->
+ [];
+tabfile_ext3(doc) ->
+ ["Tests verification of (named) tables without extended info"];
+tabfile_ext3(Config) when is_list(Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"namn.dat"]),
+ ?line FName2 = filename:join([?config(priv_dir, Config),"ncountflip.dat"]),
+ L = lists:seq(1,10),
+ Name = make_ref(),
+ ?MODULE = ets:new(?MODULE,[named_table]),
+ [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L],
+ ets:tab2file(?MODULE,FName),
+ {error,cannot_create_table} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ {ok,?MODULE} = ets:file2tab(FName),
+ true = ets:delete(?MODULE),
+ disk_log:open([{name,Name},{file,FName}]),
+ {_,[H2|T2]} = disk_log:chunk(Name,start),
+ disk_log:close(Name),
+ NewT2=lists:keydelete(8,1,T2),
+ file:delete(FName2),
+ disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
+ disk_log:log_terms(Name,[H2|NewT2]),
+ disk_log:close(Name),
+ 9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
+ true = ets:delete(?MODULE),
+ {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
+ {'EXIT',_} = (catch ets:delete(?MODULE)),
+ {ok,_} = ets:tabfile_info(FName2),
+ {ok,_} = ets:tabfile_info(FName),
+ file:delete(FName),
+ file:delete(FName2),
+ ok.
+
+tabfile_ext4(suite) ->
+ [];
+tabfile_ext4(doc) ->
+ ["Tests verification of large table with md5 sum"];
+tabfile_ext4(Config) when is_list(Config) ->
+ ?line FName = filename:join([?config(priv_dir, Config),"bauta.dat"]),
+ LL = lists:seq(1,10000),
+ TL = ets:new(x,[]),
+ Name2 = make_ref(),
+ [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL],
+ ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {ok, Name2} = disk_log:open([{name, Name2}, {file, FName},
+ {mode, read_only}]),
+ {C,[_|_]} = disk_log:chunk(Name2,start),
+ {_,[_|_]} = disk_log:chunk(Name2,C),
+ disk_log:close(Name2),
+ true = lists:sort(ets:tab2list(TL)) =:=
+ lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
+ Res = [
+ begin
+ {ok,FD} = file:open(FName,[binary,read,write]),
+ {ok, Bin} = file:pread(FD,0,1000),
+ <<B1:N/binary,Ch:8,B2/binary>> = Bin,
+ Ch2 = (Ch + 1) rem 255,
+ Bin2 = <<B1/binary,Ch2:8,B2/binary>>,
+ ok = file:pwrite(FD,0,Bin2),
+ ok = file:close(FD),
+ X = case ets:file2tab(FName) of
+ {ok,TL2} ->
+ true = lists:sort(ets:tab2list(TL)) =/=
+ lists:sort(ets:tab2list(TL2));
+ _ ->
+ totally_broken
+ end,
+ {error,Y} = ets:file2tab(FName,[{verify,true}]),
+ ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
+ {X,Y}
+ end || N <- lists:seq(400,500) ],
+ io:format("~p~n",[Res]),
+ file:delete(FName),
+ ok.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+make_sub_binary(List, Num) when is_list(List) ->
+ N = Num rem 23,
+ Bin = list_to_binary([lists:seq(0, N)|List]),
+ {_,B} = split_binary(Bin, N+1),
+ B.
+
+heavy(suite) -> [heavy_lookup, heavy_lookup_element].
+
+%% Lookup stuff like crazy...
+heavy_lookup(doc) -> ["Performs multiple lookups for every key ",
+ "in a large table."];
+heavy_lookup(suite) -> [];
+heavy_lookup(Config) when is_list(Config) ->
+ repeat_for_opts(heavy_lookup_do).
+
+heavy_lookup_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar_table, [set, protected, {keypos, 2} | Opts]),
+ ?line ok = fill_tab2(Tab, 0, 7000),
+ ?line ?t:do_times(50, ?MODULE, do_lookup, [Tab, 6999]),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+do_lookup(_Tab, 0) -> ok;
+do_lookup(Tab, N) ->
+ case ets:lookup(Tab, N) of
+ [] -> ?t:format("Set #~p was reported as empty. Not valid.",
+ [N]),
+ exit('Invalid lookup');
+ _ -> do_lookup(Tab, N-1)
+ end.
+
+heavy_lookup_element(doc) -> ["Performs multiple lookups for ",
+ "every element in a large table."];
+heavy_lookup_element(suite) -> [];
+heavy_lookup_element(Config) when is_list(Config) ->
+ repeat_for_opts(heavy_lookup_element_do).
+
+heavy_lookup_element_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = ets:new(foobar_table, [set, protected, {keypos, 2} | Opts]),
+ ?line ok = fill_tab2(Tab, 0, 7000),
+ case os:type() of
+ vxworks ->
+ ?line ?t:do_times(5, ?MODULE, do_lookup_element,
+ [Tab, 6999, 1]);
+ % lookup ALL elements 5 times.
+ _ ->
+ ?line ?t:do_times(50, ?MODULE, do_lookup_element,
+ [Tab, 6999, 1])
+ % lookup ALL elements 50 times.
+ end,
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+do_lookup_element(_Tab, 0, _) -> ok;
+do_lookup_element(Tab, N, M) ->
+ ?line case catch ets:lookup_element(Tab, N, M) of
+ {'EXIT', {badarg, _}} ->
+ case M of
+ 1 -> ?t:fail("Set #~p reported as empty. Not valid.",
+ [N]),
+ exit('Invalid lookup_element');
+ _ -> ?line do_lookup_element(Tab, N-1, 1)
+ end;
+ _ -> ?line do_lookup_element(Tab, N, M+1)
+ end.
+
+
+fold(suite) -> [foldl_ordered, foldr_ordered,
+ foldl, foldr,
+ fold_empty].
+
+fold_empty(doc) ->
+ [];
+fold_empty(suite) -> [];
+fold_empty(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line Tab = make_table(a, [], []),
+ ?line [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab),
+ ?line [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldl(doc) ->
+ [];
+foldl(suite) -> [];
+foldl(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [bag], L),
+ ?line LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldr(doc) ->
+ [];
+foldr(suite) -> [];
+foldr(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [bag], L),
+ ?line LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldl_ordered(doc) ->
+ [];
+foldl_ordered(suite) -> [];
+foldl_ordered(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [ordered_set], L),
+ ?line LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+foldr_ordered(doc) ->
+ [];
+foldr_ordered(suite) -> [];
+foldr_ordered(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line L = [{a,1}, {c,3}, {b,2}],
+ ?line LS = lists:sort(L),
+ ?line Tab = make_table(a, [ordered_set], L),
+ ?line LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab),
+ ?line true = ets:delete(Tab),
+ ?line verify_etsmem(EtsMem).
+
+member(suite) ->
+ [];
+member(doc) ->
+ ["Tests ets:member BIF"];
+member(Config) when is_list(Config) ->
+ repeat_for_opts(member_do, [write_concurrency, all_types]).
+
+member_do(Opts) ->
+ ?line EtsMem = etsmem(),
+ ?line T = ets:new(xxx, Opts),
+ ?line false = ets:member(T,hej),
+ ?line E = fun(0,_F)->ok;
+ (N,F) ->
+ ?line ets:insert(T,{N,N rem 10}),
+ F(N-1,F)
+ end,
+ ?line E(10000,E),
+ ?line false = ets:member(T,hej),
+ ?line true = ets:member(T,1),
+ ?line false = ets:member(T,20000),
+ ?line ets:delete(T,5),
+ ?line false = ets:member(T,5),
+ ?line ets:safe_fixtable(T,true),
+ ?line ets:delete(T,6),
+ ?line false = ets:member(T,6),
+ ?line ets:safe_fixtable(T,false),
+ ?line false = ets:member(T,6),
+ ?line ets:delete(T),
+ ?line {'EXIT',{badarg,_}} = (catch ets:member(finnsinte, 23)),
+ ?line {'EXIT',{badarg,_}} = (catch ets:member(T, 23)),
+ ?line verify_etsmem(EtsMem).
+
+
+build_table(L1,L2,Num) ->
+ T = ets:new(xxx, [ordered_set]
+ ),
+ lists:foreach(
+ fun(X1) ->
+ lists:foreach(
+ fun(X2) ->
+ F = fun(FF,N) ->
+ ets:insert(T,{{X1,X2,N},
+ X1, X2, N}),
+ case N of
+ 0 ->
+ ok;
+ _ ->
+ FF(FF,N-1)
+ end
+ end,
+ F(F,Num)
+ end, L2)
+ end, L1),
+ T.
+
+build_table2(L1,L2,Num) ->
+ T = ets:new(xxx, [ordered_set]
+ ),
+ lists:foreach(
+ fun(X1) ->
+ lists:foreach(
+ fun(X2) ->
+ F = fun(FF,N) ->
+ ets:insert(T,{{N,X1,X2},
+ N, X1, X2}),
+ case N of
+ 0 ->
+ ok;
+ _ ->
+ FF(FF,N-1)
+ end
+ end,
+ F(F,Num)
+ end, L2)
+ end, L1),
+ T.
+
+time_match_object(Tab,Match, Res) ->
+ T1 = erlang:now(),
+ Res = ets:match_object(Tab,Match),
+ T2 = erlang:now(),
+ nowdiff(T1,T2).
+
+time_match(Tab,Match) ->
+ T1 = erlang:now(),
+ ets:match(Tab,Match),
+ T2 = erlang:now(),
+ nowdiff(T1,T2).
+
+seventyfive_percent_success(_,S,Fa,0) ->
+ true = (S > ((S + Fa) * 0.75));
+
+seventyfive_percent_success({M,F,A},S,Fa,N) ->
+ case (catch apply(M,F,A)) of
+ {'EXIT', _} ->
+ seventyfive_percent_success({M,F,A},S,Fa+1,N-1);
+ _ ->
+ seventyfive_percent_success({M,F,A},S+1,Fa,N-1)
+ end.
+
+fifty_percent_success(_,S,Fa,0) ->
+ true = (S > ((S + Fa) * 0.5));
+
+fifty_percent_success({M,F,A},S,Fa,N) ->
+ case (catch apply(M,F,A)) of
+ {'EXIT', _} ->
+ fifty_percent_success({M,F,A},S,Fa+1,N-1);
+ _ ->
+ fifty_percent_success({M,F,A},S+1,Fa,N-1)
+ end.
+
+
+nowtonumber({Mega, Secs, Milli}) ->
+ Milli + Secs * 1000000 + Mega * 1000000000000.
+nowdiff(T1,T2) ->
+ nowtonumber(T2) - nowtonumber(T1).
+
+create_random_string(0) ->
+ [];
+
+create_random_string(OfLength) ->
+ C = case random:uniform(2) of
+ 1 ->
+ (random:uniform($Z - $A + 1) - 1) + $A;
+ _ ->
+ (random:uniform($z - $a + 1) - 1) + $a
+ end,
+ [C | create_random_string(OfLength - 1)].
+
+
+create_random_tuple(OfLength) ->
+ list_to_tuple(lists:map(fun(X) ->
+ list_to_atom([X])
+ end,create_random_string(OfLength))).
+
+create_partly_bound_tuple(OfLength) ->
+ case random:uniform(2) of
+ 1 ->
+ create_partly_bound_tuple1(OfLength);
+ _ ->
+ create_partly_bound_tuple3(OfLength)
+ end.
+
+create_partly_bound_tuple1(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength),
+ setelement(I,T0,'$1').
+
+
+set_n_random_elements(T0,0,_,_) ->
+ T0;
+set_n_random_elements(T0,N,OfLength,GenFun) ->
+ I = random:uniform(OfLength),
+ What = GenFun(I),
+ case element(I,T0) of
+ What ->
+ set_n_random_elements(T0,N,OfLength,GenFun);
+ _Else ->
+ set_n_random_elements(setelement(I,T0,What),
+ N-1,OfLength,GenFun)
+ end.
+
+make_dollar_atom(I) ->
+ list_to_atom([$$] ++ integer_to_list(I)).
+create_partly_bound_tuple2(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength - 1),
+ set_n_random_elements(T0,I,OfLength,fun make_dollar_atom/1).
+
+create_partly_bound_tuple3(OfLength) ->
+ T0 = create_random_tuple(OfLength),
+ I = random:uniform(OfLength - 1),
+ set_n_random_elements(T0,I,OfLength,fun(_) -> '_' end).
+
+do_n_times(_,0) ->
+ ok;
+do_n_times(Fun,N) ->
+ Fun(),
+ case N rem 1000 of
+ 0 ->
+ io:format(".");
+ _ ->
+ ok
+ end,
+ do_n_times(Fun,N-1).
+
+make_table(Name, Options, Elements) ->
+ T = ets:new(Name, Options),
+ lists:foreach(fun(E) -> ets:insert(T, E) end, Elements),
+ T.
+filltabint(Tab,0) ->
+ Tab;
+filltabint(Tab,N) ->
+ ets:insert(Tab,{N,integer_to_list(N)}),
+ filltabint(Tab,N-1).
+filltabint2(Tab,0) ->
+ Tab;
+filltabint2(Tab,N) ->
+ ets:insert(Tab,{N + N rem 2,integer_to_list(N)}),
+ filltabint2(Tab,N-1).
+filltabint3(Tab,0) ->
+ Tab;
+filltabint3(Tab,N) ->
+ ets:insert(Tab,{N + N rem 2,integer_to_list(N + N rem 2)}),
+ filltabint3(Tab,N-1).
+xfilltabint(Tab,N) ->
+ case ets:info(Tab,type) of
+ bag ->
+ filltabint2(Tab,N);
+ duplicate_bag ->
+ ets:select_delete(Tab,[{'_',[],[true]}]),
+ filltabint3(Tab,N);
+ _ ->
+ filltabint(Tab,N)
+ end.
+
+
+filltabstr(Tab,N) ->
+ filltabstr(Tab,0,N).
+filltabstr(Tab,N,N) ->
+ Tab;
+filltabstr(Tab,Floor,N) when N > Floor ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ filltabstr(Tab,Floor,N-1).
+
+filltabstr2(Tab,0) ->
+ Tab;
+filltabstr2(Tab,N) ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ ets:insert(Tab,{integer_to_list(N),N+1}),
+ filltabstr2(Tab,N-1).
+filltabstr3(Tab,0) ->
+ Tab;
+filltabstr3(Tab,N) ->
+ ets:insert(Tab,{integer_to_list(N),N}),
+ ets:insert(Tab,{integer_to_list(N),N}),
+ filltabstr3(Tab,N-1).
+xfilltabstr(Tab,N) ->
+ case ets:info(Tab,type) of
+ bag ->
+ filltabstr2(Tab,N);
+ duplicate_bag ->
+ ets:select_delete(Tab,[{'_',[],[true]}]),
+ filltabstr3(Tab,N);
+ _ ->
+ filltabstr(Tab,N)
+ end.
+
+fill_sets_int(N) ->
+ fill_sets_int(N,[]).
+fill_sets_int(N,Opts) ->
+ Tab1 = ets:new(xxx, [ordered_set|Opts]),
+ filltabint(Tab1,N),
+ Tab2 = ets:new(xxx, [set|Opts]),
+ filltabint(Tab2,N),
+ Tab3 = ets:new(xxx, [bag|Opts]),
+ filltabint2(Tab3,N),
+ Tab4 = ets:new(xxx, [duplicate_bag|Opts]),
+ filltabint3(Tab4,N),
+ [Tab1,Tab2,Tab3,Tab4].
+
+check_fun(_Tab,_Fun,'$end_of_table') ->
+ ok;
+check_fun(Tab,Fun,Item) ->
+ lists:foreach(fun(Obj) ->
+ true = Fun(Obj)
+ end,
+ ets:lookup(Tab,Item)),
+ check_fun(Tab,Fun,ets:next(Tab,Item)).
+
+check(Tab,Fun,N) ->
+ N = ets:info(Tab, size),
+ check_fun(Tab,Fun,ets:first(Tab)).
+
+
+
+del_one_by_one_set(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_set(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ N = (ets:info(T,size) + 1),
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_set(T,Next,To).
+
+del_one_by_one_bag(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_bag(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From + From rem 2, integer_to_list(From)}),
+ N = (ets:info(T,size) + 1),
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_bag(T,Next,To).
+
+
+del_one_by_one_dbag_1(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_1(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 2);
+ 1 ->
+ N = ets:info(T,size)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_1(T,Next,To).
+
+del_one_by_one_dbag_2(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_2(T,From,To) ->
+ N = ets:info(T,size),
+ ets:delete_object(T,{From, integer_to_list(From)}),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 3);
+ 1 ->
+ N = (ets:info(T,size) + 1)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_2(T,Next,To).
+
+del_one_by_one_dbag_3(T,N,N) ->
+ 0 = ets:info(T,size),
+ ok;
+del_one_by_one_dbag_3(T,From,To) ->
+ N = ets:info(T,size),
+ Obj = {From + From rem 2, integer_to_list(From)},
+ ets:delete_object(T,Obj),
+ case From rem 2 of
+ 0 ->
+ N = (ets:info(T,size) + 2);
+ 1 ->
+ N = (ets:info(T,size) + 1),
+ Obj2 = {From, integer_to_list(From)},
+ ets:delete_object(T,Obj2),
+ N = (ets:info(T,size) + 2)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ del_one_by_one_dbag_3(T,Next,To).
+
+
+successive_delete(Table,From,To,Type) ->
+ successive_delete(Table,From,To,Type,ets:info(Table,type)).
+
+successive_delete(_Table,N,N,_,_) ->
+ ok;
+successive_delete(Table,From,To,Type,TType) ->
+ MS = case Type of
+ bound ->
+ [{{From,'_'},[],[true]}];
+ unbound ->
+ [{{'$1','_'},[],[{'==', '$1', From}]}]
+ end,
+ case TType of
+ X when X == bag; X == duplicate_bag ->
+ %erlang:display(From),
+ case From rem 2 of
+ 0 ->
+ 2 = ets:select_delete(Table,MS);
+ _ ->
+ 0 = ets:select_delete(Table,MS)
+ end;
+ _ ->
+ 1 = ets:select_delete(Table,MS)
+ end,
+ Next = if
+ From < To ->
+ From + 1;
+ true ->
+ From - 1
+ end,
+ successive_delete(Table, Next, To, Type,TType).
+
+gen_dets_filename(Config,N) ->
+ filename:join(?config(priv_dir,Config),
+ "testdets_" ++ integer_to_list(N) ++ ".dets").
+
+otp_6842_select_1000(Config) when is_list(Config) ->
+ ?line Tab = ets:new(xxx,[ordered_set]),
+ ?line [ets:insert(Tab,{X,X}) || X <- lists:seq(1,10000)],
+ ?line AllTrue = lists:duplicate(10,true),
+ ?line AllTrue =
+ [ length(
+ element(1,
+ ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:=
+ X*1000 || X <- lists:seq(1,10) ],
+ ?line Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000],
+ [2000,2000,2000,2000,2000],
+ [3000,3000,3000,1000],
+ [4000,4000,2000],
+ [5000,5000],
+ [6000,4000],
+ [7000,3000],
+ [8000,2000],
+ [9000,1000],
+ [10000]],
+ ?line AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) ||
+ L <- Sequences ],
+ ?line ets:delete(Tab),
+ ok.
+
+check_seq(_,'$end_of_table',[]) ->
+ true;
+check_seq(Tab,{L,C},[H|T]) when length(L) =:= H ->
+ check_seq(Tab, ets:select(C),T);
+check_seq(A,B,C) ->
+ erlang:display({A,B,C}),
+ false.
+
+otp_6338(Config) when is_list(Config) ->
+ L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112,98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53,0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120,105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100,0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100,101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99,118,106>>),
+ T = ets:new(xxx,[ordered_set]),
+ lists:foreach(fun(X) -> ets:insert(T,X) end,L),
+ [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}),
+ ets:delete(T).
+
+%% Elements could come in the wrong order in a bag if a rehash occurred.
+otp_5340(Config) when is_list(Config) ->
+ repeat_for_opts(otp_5340_do).
+
+otp_5340_do(Opts) ->
+ N = 3000,
+ T = ets:new(otp_5340, [bag,public | Opts]),
+ Ids = [1,2,3,4,5],
+ [w(T, N, Id) || Id <- Ids],
+ verify(T, Ids),
+ ets:delete(T).
+
+w(_,0, _) -> ok;
+w(T,N, Id) ->
+ ets:insert(T, {N, Id}),
+ w(T,N-1,Id).
+
+verify(T, Ids) ->
+ List = my_tab_to_list(T),
+ Errors = lists:filter(fun(Bucket) ->
+ verify2(Bucket, Ids)
+ end, List),
+ case Errors of
+ [] ->
+ ok;
+ _ ->
+ io:format("Failed:\n~p\n", [Errors]),
+ ?t:fail()
+ end.
+
+verify2([{_N,Id}|RL], [Id|R]) ->
+ verify2(RL,R);
+verify2([],[]) -> false;
+verify2(_Err, _) ->
+ true.
+
+otp_7665(doc) -> ["delete_object followed by delete on fixed bag failed to delete objects."];
+otp_7665(suite) -> [];
+otp_7665(Config) when is_list(Config) ->
+ repeat_for_opts(otp_7665_do).
+
+otp_7665_do(Opts) ->
+ Tab = ets:new(otp_7665,[bag | Opts]),
+ Min = 0,
+ Max = 10,
+ lists:foreach(fun(N)-> otp_7665_act(Tab,Min,Max,N) end,
+ lists:seq(Min,Max)),
+ ?line true = ets:delete(Tab).
+
+otp_7665_act(Tab,Min,Max,DelNr) ->
+ List1 = [{key,N} || N <- lists:seq(Min,Max)],
+ ?line true = ets:insert(Tab, List1),
+ ?line true = ets:safe_fixtable(Tab, true),
+ ?line true = ets:delete_object(Tab, {key,DelNr}),
+ List2 = lists:delete({key,DelNr}, List1),
+
+ %% Now verify that we find all remaining objects
+ ?line List2 = ets:lookup(Tab,key),
+ ?line EList2 = lists:map(fun({key,N})-> N end,
+ List2),
+ ?line EList2 = ets:lookup_element(Tab,key,2),
+ ?line true = ets:delete(Tab, key),
+ ?line [] = ets:lookup(Tab, key),
+ ?line true = ets:safe_fixtable(Tab, false),
+ ok.
+
+%% Whitebox testing of meta name table hashing.
+meta_wb(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ ?line erts_debug:set_internal_state(available_internal_state, true),
+ try
+ repeat_for_opts(meta_wb_do)
+ after
+ erts_debug:set_internal_state(available_internal_state, false)
+ end,
+ ?line verify_etsmem(EtsMem).
+
+
+meta_wb_do(Opts) ->
+ %% Do random new/delete/rename of colliding named tables
+ Names = [pioneer | colliding_names(pioneer)],
+ Len = length(Names),
+ OpFuns = {fun meta_wb_new/4, fun meta_wb_delete/4, fun meta_wb_rename/4},
+
+ io:format("Colliding names = ~p\n",[Names]),
+ F = fun(0,_,_) -> ok;
+ (N,Tabs,Me) -> Name1 = lists:nth(random:uniform(Len),Names),
+ Name2 = lists:nth(random:uniform(Len),Names),
+ Op = element(random:uniform(3),OpFuns),
+ NTabs = Op(Name1, Name2, Tabs, Opts),
+ Me(N-1,NTabs,Me)
+ end,
+ F(Len*100, [], F),
+
+ % cleanup
+ lists:foreach(fun(Name)->catch ets:delete(Name) end,
+ Names).
+
+meta_wb_new(Name, _, Tabs, Opts) ->
+ case (catch ets:new(Name,[named_table|Opts])) of
+ Name ->
+ ?line false = lists:member(Name, Tabs),
+ [Name | Tabs];
+ {'EXIT',{badarg,_}} ->
+ ?line true = lists:member(Name, Tabs),
+ Tabs
+ end.
+meta_wb_delete(Name, _, Tabs, _) ->
+ case (catch ets:delete(Name)) of
+ true ->
+ ?line true = lists:member(Name, Tabs),
+ lists:delete(Name, Tabs);
+ {'EXIT',{badarg,_}} ->
+ ?line false = lists:member(Name, Tabs),
+ Tabs
+ end.
+meta_wb_rename(Old, New, Tabs, _) ->
+ case (catch ets:rename(Old,New)) of
+ New ->
+ ?line true = lists:member(Old, Tabs)
+ andalso not lists:member(New, Tabs),
+ [New | lists:delete(Old, Tabs)];
+ {'EXIT',{badarg,_}} ->
+ ?line true = not lists:member(Old, Tabs)
+ orelse lists:member(New,Tabs),
+ Tabs
+ end.
+
+
+colliding_names(Name) ->
+ erts_debug:set_internal_state(colliding_names, {Name,5}).
+
+
+%% OTP_6913: Grow and shrink.
+
+grow_shrink(Config) when is_list(Config) ->
+ ?line EtsMem = etsmem(),
+ grow_shrink_0(lists:seq(3071, 5000), EtsMem).
+
+grow_shrink_0([N|Ns], EtsMem) ->
+ ?line grow_shrink_1(N, [set]),
+ ?line grow_shrink_1(N, [ordered_set]),
+ ?line verify_etsmem(EtsMem),
+ grow_shrink_0(Ns, EtsMem);
+grow_shrink_0([], _) -> ok.
+
+grow_shrink_1(N, Flags) ->
+ ?line T = ets:new(a, Flags),
+ ?line grow_shrink_2(N, N, T),
+ ?line ets:delete(T).
+
+grow_shrink_2(0, Orig, T) ->
+ List = [{I,a} || I <- lists:seq(1, Orig)],
+ List = lists:sort(ets:tab2list(T)),
+ grow_shrink_3(Orig, T);
+grow_shrink_2(N, Orig, T) ->
+ true = ets:insert(T, {N,a}),
+ grow_shrink_2(N-1, Orig, T).
+
+grow_shrink_3(0, T) ->
+ [] = ets:tab2list(T);
+grow_shrink_3(N, T) ->
+ true = ets:delete(T, N),
+ grow_shrink_3(N-1, T).
+
+grow_pseudo_deleted(doc) -> ["Grow a table that still contains pseudo-deleted objects"];
+grow_pseudo_deleted(suite) -> [];
+grow_pseudo_deleted(Config) when is_list(Config) ->
+ only_if_smp(fun() -> grow_pseudo_deleted_do() end).
+
+grow_pseudo_deleted_do() ->
+ lists:foreach(fun(Type) -> grow_pseudo_deleted_do(Type) end,
+ [set,bag,duplicate_bag]).
+
+grow_pseudo_deleted_do(Type) ->
+ process_flag(scheduler,1),
+ Self = self(),
+ ?line T = ets:new(kalle,[Type,public,{write_concurrency,true}]),
+ Mod = 7, Mult = 10000,
+ filltabint(T,Mod*Mult),
+ ?line true = ets:safe_fixtable(T,true),
+ ?line Mult = ets:select_delete(T,
+ [{{'$1', '_'},
+ [{'=:=', {'rem', '$1', Mod}, 0}],
+ [true]}]),
+ Left = Mult*(Mod-1),
+ ?line Left = ets:info(T,size),
+ ?line Mult = ets:info(T,kept_objects),
+ filltabstr(T,Mult),
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Self ! start,
+ io:format("Starting to filltabstr... ~p\n",[now()]),
+ filltabstr(T,Mult,Mult+10000),
+ io:format("Done with filltabstr. ~p\n",[now()]),
+ Self ! done
+ end, [link, {scheduler,2}]),
+ ?line start = receive_any(),
+ io:format("Unfixing table...~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line true = ets:safe_fixtable(T,false),
+ io:format("Unfix table done. ~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line done = receive_any(),
+ %%verify_table_load(T), % may fail if concurrency is poor (genny)
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+shrink_pseudo_deleted(doc) -> ["Shrink a table that still contains pseudo-deleted objects"];
+shrink_pseudo_deleted(suite) -> [];
+shrink_pseudo_deleted(Config) when is_list(Config) ->
+ only_if_smp(fun()->shrink_pseudo_deleted_do() end).
+
+shrink_pseudo_deleted_do() ->
+ lists:foreach(fun(Type) -> shrink_pseudo_deleted_do(Type) end,
+ [set,bag,duplicate_bag]).
+
+shrink_pseudo_deleted_do(Type) ->
+ process_flag(scheduler,1),
+ Self = self(),
+ ?line T = ets:new(kalle,[Type,public,{write_concurrency,true}]),
+ Half = 10000,
+ filltabint(T,Half*2),
+ ?line true = ets:safe_fixtable(T,true),
+ ?line Half = ets:select_delete(T,
+ [{{'$1', '_'},
+ [{'>', '$1', Half}],
+ [true]}]),
+ ?line Half = ets:info(T,size),
+ ?line Half = ets:info(T,kept_objects),
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Self ! start,
+ io:format("Starting to delete... ~p\n",[now()]),
+ del_one_by_one_set(T,1,Half+1),
+ io:format("Done with delete. ~p\n",[now()]),
+ Self ! done
+ end, [link, {scheduler,2}]),
+ ?line start = receive_any(),
+ io:format("Unfixing table...~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line true = ets:safe_fixtable(T,false),
+ io:format("Unfix table done. ~p nitems=~p\n",[now(),ets:info(T,size)]),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ ?line done = receive_any(),
+ %%verify_table_load(T), % may fail if concurrency is poor (genny)
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+
+meta_smp(suite) ->
+ [meta_lookup_unnamed_read,
+ meta_lookup_unnamed_write,
+ meta_lookup_named_read,
+ meta_lookup_named_write,
+ meta_newdel_unnamed,
+ meta_newdel_named].
+
+meta_lookup_unnamed_read(suite) -> [];
+meta_lookup_unnamed_read(Config) when is_list(Config) ->
+ InitF = fun(_) -> Tab = ets:new(unnamed,[]),
+ true = ets:insert(Tab,{key,data}),
+ Tab
+ end,
+ ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
+ Tab
+ end,
+ FiniF = fun(Tab) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_unnamed_write(suite) -> [];
+meta_lookup_unnamed_write(Config) when is_list(Config) ->
+ InitF = fun(_) -> Tab = ets:new(unnamed,[]),
+ {Tab,0}
+ end,
+ ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
+ {Tab,N+1}
+ end,
+ FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_named_read(suite) -> [];
+meta_lookup_named_read(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
+ Tab = ets:new(Name,[named_table]),
+ true = ets:insert(Tab,{key,data}),
+ Tab
+ end,
+ ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
+ Tab
+ end,
+ FiniF = fun(Tab) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_lookup_named_write(suite) -> [];
+meta_lookup_named_write(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
+ Tab = ets:new(Name,[named_table]),
+ {Tab,0}
+ end,
+ ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
+ {Tab,N+1}
+ end,
+ FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
+ end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_newdel_unnamed(suite) -> [];
+meta_newdel_unnamed(Config) when is_list(Config) ->
+ InitF = fun(_) -> ok end,
+ ExecF = fun(_) -> Tab = ets:new(unnamed,[]),
+ true = ets:delete(Tab)
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+meta_newdel_named(suite) -> [];
+meta_newdel_named(Config) when is_list(Config) ->
+ InitF = fun([ProcN|_]) -> list_to_atom(integer_to_list(ProcN))
+ end,
+ ExecF = fun(Name) -> Name = ets:new(Name,[named_table]),
+ true = ets:delete(Name),
+ Name
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,10000).
+
+smp_insert(doc) -> ["Concurrent insert's on same table"];
+smp_insert(suite) -> [];
+smp_insert(Config) when is_list(Config) ->
+ ets:new(smp_insert,[named_table,public,{write_concurrency,true}]),
+ InitF = fun(_) -> ok end,
+ ExecF = fun(_) -> true = ets:insert(smp_insert,{random:uniform(10000)})
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers(InitF,ExecF,FiniF,100000),
+ verify_table_load(smp_insert),
+ ets:delete(smp_insert).
+
+smp_fixed_delete(doc) -> ["Concurrent delete's on same fixated table"];
+smp_fixed_delete(suite) -> [];
+smp_fixed_delete(Config) when is_list(Config) ->
+ only_if_smp(fun()->smp_fixed_delete_do() end).
+
+smp_fixed_delete_do() ->
+ T = ets:new(foo,[public,{write_concurrency,true}]),
+ %%Mem = ets:info(T,memory),
+ NumOfObjs = 100000,
+ filltabint(T,NumOfObjs),
+ ets:safe_fixtable(T,true),
+ Buckets = num_of_buckets(T),
+ InitF = fun([ProcN,NumOfProcs|_]) -> {ProcN,NumOfProcs} end,
+ ExecF = fun({Key,_}) when Key > NumOfObjs ->
+ [end_of_work];
+ ({Key,Increment}) ->
+ true = ets:delete(T,Key),
+ {Key+Increment,Increment}
+ end,
+ FiniF = fun(_) -> ok end,
+ run_workers_do(InitF,ExecF,FiniF,NumOfObjs),
+ ?line 0 = ets:info(T,size),
+ ?line true = ets:info(T,fixed),
+ ?line Buckets = num_of_buckets(T),
+ ?line NumOfObjs = ets:info(T,kept_objects),
+ ets:safe_fixtable(T,false),
+ %% Will fail as unfix does not shrink the table:
+ %%?line Mem = ets:info(T,memory),
+ %%verify_table_load(T),
+ ets:delete(T).
+
+num_of_buckets(T) ->
+ ?line element(1,ets:info(T,stats)).
+
+smp_unfix_fix(doc) -> ["Fixate hash table while other process is busy doing unfix"];
+smp_unfix_fix(suite) -> [];
+smp_unfix_fix(Config) when is_list(Config) ->
+ only_if_smp(fun()-> smp_unfix_fix_do() end).
+
+smp_unfix_fix_do() ->
+ process_flag(scheduler,1),
+ Parent = self(),
+ T = ets:new(foo,[public,{write_concurrency,true}]),
+ %%Mem = ets:info(T,memory),
+ NumOfObjs = 100000,
+ Deleted = 50000,
+ filltabint(T,NumOfObjs),
+ ets:safe_fixtable(T,true),
+ Buckets = num_of_buckets(T),
+ ?line Deleted = ets:select_delete(T,[{{'$1', '_'},
+ [{'=<','$1', Deleted}],
+ [true]}]),
+ ?line Buckets = num_of_buckets(T),
+ Left = NumOfObjs - Deleted,
+ ?line Left = ets:info(T,size),
+ ?line true = ets:info(T,fixed),
+ ?line Deleted = ets:info(T,kept_objects),
+
+ {Child, Mref} =
+ spawn_opt(fun()-> ?line true = ets:info(T,fixed),
+ Parent ! start,
+ io:format("Child waiting for table to be unfixed... now=~p mem=~p\n",
+ [now(),ets:info(T,memory)]),
+ repeat_while(fun()-> ets:info(T,fixed) end),
+ io:format("Table unfixed. Child Fixating! now=~p mem=~p\n",
+ [now(),ets:info(T,memory)]),
+ ?line true = ets:safe_fixtable(T,true),
+ repeat_while(fun(Key) when Key =< NumOfObjs ->
+ ets:delete(T,Key), {true,Key+1};
+ (Key) -> {false,Key}
+ end,
+ Deleted),
+ ?line 0 = ets:info(T,size),
+ ?line true = ets:info(T,kept_objects) >= Left,
+ ?line done = receive_any()
+ end,
+ [link, monitor, {scheduler,2}]),
+
+ ?line start = receive_any(),
+ ?line true = ets:info(T,fixed),
+ io:format("Parent starting to unfix... ~p\n",[now()]),
+ ets:safe_fixtable(T,false),
+ io:format("Parent done with unfix. ~p\n",[now()]),
+ Child ! done,
+ {'DOWN', Mref, process, Child, normal} = receive_any(),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ %%verify_table_load(T),
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+otp_8166(doc) -> ["Unsafe unfix was done by trapping select/match"];
+otp_8166(suite) -> [];
+otp_8166(Config) when is_list(Config) ->
+ only_if_smp(3, fun()-> otp_8166_do(false),
+ otp_8166_do(true)
+ end).
+
+otp_8166_do(WC) ->
+ %% Bug scenario: One process segv while reading the table because another
+ %% process is doing unfix without write-lock at the end of a trapping match_object.
+ process_flag(scheduler,1),
+ T = ets:new(foo,[public, {write_concurrency,WC}]),
+ NumOfObjs = 3000, %% Need more than 1000 live objects for match_object to trap one time
+ Deleted = NumOfObjs div 2,
+ filltabint(T,NumOfObjs),
+ {ReaderPid, ReaderMref} =
+ spawn_opt(fun()-> otp_8166_reader(T,NumOfObjs) end,
+ [link, monitor, {scheduler,2}]),
+ {ZombieCrPid, ZombieCrMref} =
+ spawn_opt(fun()-> otp_8166_zombie_creator(T,Deleted) end,
+ [link, monitor, {scheduler,3}]),
+
+ repeat(fun() -> ZombieCrPid ! {loop, self()},
+ zombies_created = receive_any(),
+ otp_8166_trapper(T, 10, ZombieCrPid)
+ end,
+ 100),
+
+ ReaderPid ! quit,
+ {'DOWN', ReaderMref, process, ReaderPid, normal} = receive_any(),
+ ZombieCrPid ! quit,
+ {'DOWN', ZombieCrMref, process, ZombieCrPid, normal} = receive_any(),
+ ?line false = ets:info(T,fixed),
+ ?line 0 = ets:info(T,kept_objects),
+ %%verify_table_load(T),
+ ets:delete(T),
+ process_flag(scheduler,0).
+
+%% Keep reading the table
+otp_8166_reader(T, NumOfObjs) ->
+ repeat_while(fun(0) ->
+ receive quit -> {false,done}
+ after 0 -> {true,NumOfObjs}
+ end;
+ (Key) ->
+ ets:lookup(T,Key),
+ {true, Key-1}
+ end,
+ NumOfObjs).
+
+%% Do a match_object that will trap and thereby fixate and then unfixate the table
+otp_8166_trapper(T, Try, ZombieCrPid) ->
+ [] = ets:match_object(T,{'_',"Pink Unicorn"}),
+ case {ets:info(T,fixed),Try} of
+ {true,1} ->
+ io:format("failed to provoke unsafe unfix, give up...\n",[]),
+ ZombieCrPid ! unfix;
+ {true,_} ->
+ io:format("trapper too fast, trying again...\n",[]),
+ otp_8166_trapper(T, Try-1, ZombieCrPid);
+ {false,_} -> done
+ end.
+
+
+%% Fixate table and create some pseudo-deleted objects (zombies)
+%% Then wait for trapper to fixate before unfixing, as we want the trappers'
+%% unfix to be the one that purges the zombies.
+otp_8166_zombie_creator(T,Deleted) ->
+ case receive_any() of
+ quit -> done;
+
+ {loop,Pid} ->
+ filltabint(T,Deleted),
+ ets:safe_fixtable(T,true),
+ ?line Deleted = ets:select_delete(T,[{{'$1', '_'},
+ [{'=<','$1', Deleted}],
+ [true]}]),
+ Pid ! zombies_created,
+ repeat_while(fun() -> case ets:info(T,safe_fixed) of
+ {_,[_P1,_P2]} ->
+ false;
+ _ ->
+ receive unfix -> false
+ after 0 -> true
+ end
+ end
+ end),
+ ets:safe_fixtable(T,false),
+ otp_8166_zombie_creator(T,Deleted);
+
+ unfix ->
+ io:format("ignore unfix in outer loop?\n",[]),
+ otp_8166_zombie_creator(T,Deleted)
+ end.
+
+
+
+
+verify_table_load(T) ->
+ ?line Stats = ets:info(T,stats),
+ ?line {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen} = Stats,
+ ?line ok = if
+ AvgLen > 7 ->
+ io:format("Table overloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ Buckets>256, AvgLen < 6 ->
+ io:format("Table underloaded: Stats=~p\n~p\n",
+ [Stats, ets:info(T)]),
+ false;
+
+ StdDev > ExpSD*2 ->
+ io:format("Too large standard deviation (poor hashing?),"
+ " stats=~p\n~p\n",[Stats, ets:info(T)]),
+ false;
+
+ true ->
+ io:format("Stats = ~p\n",[Stats]),
+ ok
+ end.
+
+
+
+
+
+smp_select_delete(suite) -> [];
+smp_select_delete(doc) ->
+ ["Run concurrent select_delete (and inserts) on same table."];
+smp_select_delete(Config) when is_list(Config) ->
+ T = ets:new(smp_select_delete,[named_table,public,{write_concurrency,true}]),
+ Mod = 17,
+ Zeros = erlang:make_tuple(Mod,0),
+ InitF = fun(_) -> Zeros end,
+ ExecF = fun(Diffs0) ->
+ case random:uniform(20) of
+ 1 ->
+ Mod = 17,
+ Eq = random:uniform(Mod) - 1,
+ Deleted = ets:select_delete(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0) - Deleted),
+ Diffs1;
+ _ ->
+ Key = random:uniform(10000),
+ Eq = Key rem Mod,
+ ?line case ets:insert_new(T,{Key,Key}) of
+ true ->
+ Diffs1 = setelement(Eq+1, Diffs0,
+ element(Eq+1,Diffs0)+1),
+ Diffs1;
+ false -> Diffs0
+ end
+ end
+ end,
+ FiniF = fun(Result) -> Result end,
+ Results = run_workers_do(InitF,ExecF,FiniF,20000),
+ ?line TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end,
+ lists:duplicate(Mod, 0), Results),
+ io:format("TotCnts = ~p\n",[TotCnts]),
+ ?line LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end,
+ 0, TotCnts),
+ io:format("LeftInTab = ~p\n",[LeftInTab]),
+ ?line LeftInTab = ets:info(T,size),
+ lists:foldl(fun(Cnt,Eq) ->
+ WasCnt = ets:select_count(T,
+ [{{'_', '$1'},
+ [{'=:=', {'rem', '$1', Mod}, Eq}],
+ [true]}]),
+ io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]),
+ ?line Cnt = WasCnt,
+ Eq+1
+ end,
+ 0, TotCnts),
+ verify_table_load(T),
+ ?line LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]),
+ ?line 0 = ets:info(T,size),
+ ?line false = ets:info(T,fixed),
+ ets:delete(T).
+
+add_lists(L1,L2) ->
+ add_lists(L1,L2,[]).
+add_lists([],[],Acc) ->
+ lists:reverse(Acc);
+add_lists([E1|T1], [E2|T2], Acc) ->
+ add_lists(T1, T2, [E1+E2 | Acc]).
+
+run_workers(InitF,ExecF,FiniF,Laps) ->
+ case erlang:system_info(smp_support) of
+ true ->
+ run_workers_do(InitF,ExecF,FiniF,Laps);
+ false ->
+ {skipped,"No smp support"}
+ end.
+
+run_workers_do(InitF,ExecF,FiniF,Laps) ->
+ NumOfProcs = erlang:system_info(schedulers),
+ io:format("smp starting ~p workers\n",[NumOfProcs]),
+ Seeds = [{ProcN,random:uniform(9999)} || ProcN <- lists:seq(1,NumOfProcs)],
+ Parent = self(),
+ Pids = [spawn_link(fun()-> worker(Seed,InitF,ExecF,FiniF,Laps,Parent,NumOfProcs) end)
+ || Seed <- Seeds],
+ wait_pids(Pids).
+
+worker({ProcN,Seed}, InitF, ExecF, FiniF, Laps, Parent, NumOfProcs) ->
+ io:format("smp worker ~p, seed=~p~n",[self(),Seed]),
+ random:seed(Seed,Seed,Seed),
+ State1 = InitF([ProcN, NumOfProcs]),
+ State2 = worker_loop(Laps, ExecF, State1),
+ Result = FiniF(State2),
+ io:format("worker ~p done\n",[self()]),
+ Parent ! {self(), Result}.
+
+worker_loop(0, _, State) ->
+ State;
+worker_loop(_, _, [end_of_work|State]) ->
+ State;
+worker_loop(N, ExecF, State) ->
+ worker_loop(N-1,ExecF,ExecF(State)).
+
+wait_pids(Pids) ->
+ wait_pids(Pids,[]).
+wait_pids([],Acc) ->
+ Acc;
+wait_pids(Pids, Acc) ->
+ receive
+ {Pid,Result} ->
+ ?line true = lists:member(Pid,Pids),
+ Others = lists:delete(Pid,Pids),
+ io:format("wait_pid got ~p from ~p, still waiting for ~p\n",[Result,Pid,Others]),
+ wait_pids(Others,[Result | Acc])
+ end.
+
+
+
+
+my_tab_to_list(Ts) ->
+ Key = ets:first(Ts),
+ my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)]).
+
+my_tab_to_list(_Ts,'$end_of_table', Acc) -> lists:reverse(Acc);
+my_tab_to_list(Ts,Key, Acc) ->
+ my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)| Acc]).
+
+etsmem() ->
+ {try erlang:memory(ets) catch error:notsup -> notsup end,
+ case erlang:system_info({allocator,ets_alloc}) of
+ false -> undefined;
+ MemInfo ->
+ MSBCS = lists:foldl(
+ fun ({instance, _, L}, Acc) ->
+ {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L),
+ {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L),
+ [MBCS,SBCS | Acc]
+ end,
+ [],
+ MemInfo),
+ lists:foldl(
+ fun(L, {Bl0,BlSz0}) ->
+ {value,{_,Bl,_,_}} = lists:keysearch(blocks, 1, L),
+ {value,{_,BlSz,_,_}} = lists:keysearch(blocks_size, 1, L),
+ {Bl0+Bl,BlSz0+BlSz}
+ end, {0,0}, MSBCS)
+ end}.
+
+verify_etsmem(MemInfo) ->
+ wait_for_test_procs(),
+ case etsmem() of
+ MemInfo ->
+ io:format("Ets mem info: ~p", [MemInfo]),
+ case MemInfo of
+ {ErlMem,EtsAlloc} when ErlMem == notsup; EtsAlloc == undefined ->
+ %% Use 'erl +Mea max' to do more complete memory leak testing.
+ {comment,"Incomplete or no mem leak testing"};
+ _ ->
+ ok
+ end;
+ Other ->
+ io:format("Expected: ~p", [MemInfo]),
+ io:format("Actual: ~p", [Other]),
+ ?t:fail()
+ end.
+
+start_loopers(N, Prio, Fun, State) ->
+ lists:map(fun (_) ->
+ my_spawn_opt(fun () -> looper(Fun, State) end,
+ [{priority, Prio}, link])
+ end,
+ lists:seq(1, N)).
+
+stop_loopers(Loopers) ->
+ lists:foreach(fun (P) ->
+ unlink(P),
+ exit(P, bang)
+ end,
+ Loopers),
+ ok.
+
+looper(Fun, State) ->
+ looper(Fun, Fun(State)).
+
+spawn_logger(Procs) ->
+ receive
+ {new_test_proc, Proc} ->
+ spawn_logger([Proc|Procs]);
+ {sync_test_procs, Kill, From} ->
+ lists:foreach(fun (Proc) when From == Proc ->
+ ok;
+ (Proc) ->
+ Mon = erlang:monitor(process, Proc),
+ receive
+ {'DOWN', Mon, _, _, _} ->
+ ok
+ after 0 ->
+ case Kill of
+ true -> exit(Proc, kill);
+ _ -> ok
+ end,
+ receive
+ {'DOWN', Mon, _, _, _} ->
+ ok
+ end
+ end
+ end, Procs),
+ From ! test_procs_synced,
+ spawn_logger([From])
+ end.
+
+start_spawn_logger() ->
+ case whereis(ets_test_spawn_logger) of
+ Pid when is_pid(Pid) -> true;
+ _ -> register(ets_test_spawn_logger,
+ spawn_opt(fun () -> spawn_logger([]) end,
+ [{priority, max}]))
+ end.
+
+%% restart_spawn_logger() ->
+%% stop_spawn_logger(),
+%% start_spawn_logger().
+
+stop_spawn_logger() ->
+ Mon = erlang:monitor(process, ets_test_spawn_logger),
+ (catch exit(whereis(ets_test_spawn_logger), kill)),
+ receive {'DOWN', Mon, _, _, _} -> ok end,
+ ok.
+
+wait_for_test_procs() ->
+ wait_for_test_procs(false).
+
+wait_for_test_procs(Kill) ->
+ ets_test_spawn_logger ! {sync_test_procs, Kill, self()},
+ receive test_procs_synced -> ok end.
+
+log_test_proc(Proc) ->
+ ets_test_spawn_logger ! {new_test_proc, Proc},
+ Proc.
+
+my_spawn(Fun) -> log_test_proc(spawn(Fun)).
+%%my_spawn(M,F,A) -> log_test_proc(spawn(M,F,A)).
+%%my_spawn(N,M,F,A) -> log_test_proc(spawn(N,M,F,A)).
+
+my_spawn_link(Fun) -> log_test_proc(spawn_link(Fun)).
+my_spawn_link(M,F,A) -> log_test_proc(spawn_link(M,F,A)).
+%%my_spawn_link(N,M,F,A) -> log_test_proc(spawn_link(N,M,F,A)).
+
+my_spawn_opt(Fun,Opts) -> log_test_proc(spawn_opt(Fun,Opts)).
+%%my_spawn_opt(M,F,A,Opts) -> log_test_proc(spawn_opt(M,F,A,Opts)).
+%%my_spawn_opt(N,M,F,A,Opts) -> log_test_proc(spawn_opt(N,M,F,A,Opts)).
+
+repeat(_Fun, 0) ->
+ ok;
+repeat(Fun, N) ->
+ Fun(),
+ repeat(Fun, N-1).
+
+repeat_while(Fun) ->
+ case Fun() of
+ true -> repeat_while(Fun);
+ false -> false
+ end.
+
+repeat_while(Fun, Arg0) ->
+ case Fun(Arg0) of
+ {true,Arg1} -> repeat_while(Fun,Arg1);
+ {false,Ret} -> Ret
+ end.
+
+receive_any() ->
+ receive M ->
+ io:format("Process ~p got msg ~p\n", [self(),M]),
+ M
+ end.
+
+receive_any_spinning() ->
+ receive_any_spinning(1000000).
+receive_any_spinning(Loops) ->
+ receive_any_spinning(Loops,Loops,1).
+receive_any_spinning(Loops,0,Tries) ->
+ receive M ->
+ io:format("Spinning process ~p got msg ~p after ~p tries\n", [self(),M,Tries]),
+ M
+ after 0 ->
+ receive_any_spinning(Loops, Loops, Tries+1)
+ end;
+receive_any_spinning(Loops, N, Tries) when N>0 ->
+ receive_any_spinning(Loops, N-1, Tries).
+
+
+
+spawn_monitor_with_pid(Pid, Fun) when is_pid(Pid) ->
+ spawn_monitor_with_pid(Pid, Fun, 1, 10).
+
+spawn_monitor_with_pid(Pid, Fun, N, M) when N > M*10 ->
+ spawn_monitor_with_pid(Pid, Fun, N, M*10);
+spawn_monitor_with_pid(Pid, Fun, N, M) ->
+ ?line false = is_process_alive(Pid),
+ case spawn(fun()-> case self() of
+ Pid -> Fun();
+ _ -> die
+ end
+ end) of
+ Pid ->
+ {Pid, erlang:monitor(process, Pid)};
+ Other ->
+ case N rem M of
+ 0 -> io:format("Failed ~p times to get pid ~p (current = ~p)\n",[N,Pid,Other]);
+ _ -> ok
+ end,
+ spawn_monitor_with_pid(Pid,Fun,N+1,M)
+ end.
+
+
+only_if_smp(Func) ->
+ only_if_smp(2, Func).
+only_if_smp(Schedulers, Func) ->
+ case {erlang:system_info(smp_support),
+ erlang:system_info(schedulers_online)} of
+ {false,_} -> {skip,"No smp support"};
+ {true,N} when N < Schedulers -> {skip,"Too few schedulers online"};
+ {true,_} -> Func()
+ end.
+
+
+%% Repeat test function with different combination of table options
+%%
+repeat_for_opts(F) ->
+ repeat_for_opts(F, [write_concurrency]).
+
+repeat_for_opts(F, OptGenList) when is_atom(F) ->
+ repeat_for_opts(fun(Opts) -> ?MODULE:F(Opts) end, OptGenList);
+repeat_for_opts(F, OptGenList) ->
+ repeat_for_opts(F, OptGenList, []).
+
+repeat_for_opts(F, [], Acc) ->
+ lists:map(fun(Opts) ->
+ io:format("Calling with options ~p\n",[Opts]),
+ F(Opts)
+ end, Acc);
+repeat_for_opts(F, [OptList | Tail], []) when is_list(OptList) ->
+ repeat_for_opts(F, Tail, [[Opt] || Opt <- OptList]);
+repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) ->
+ repeat_for_opts(F, Tail, [[Opt|Acc] || Opt <- OptList, Acc <- AccList]);
+repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
+ repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
+
+repeat_for_opts_atom2list(all_types) -> [set,ordered_set,bag,duplicate_bag];
+repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}].
+
+