1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(ets_SUITE).
21
22-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
23	 init_per_group/2,end_per_group/2]).
24-export([default/1,setbag/1,badnew/1,verybadnew/1,named/1,keypos2/1,
25	 privacy/1]).
26-export([empty/1,badinsert/1]).
27-export([badlookup/1,lookup_order/1]).
28-export([delete_elem/1,delete_tab/1,delete_large_tab/1,
29	 delete_large_named_table/1,
30	 evil_delete/1,baddelete/1,match_delete/1,table_leak/1]).
31-export([match_delete3/1]).
32-export([firstnext/1,firstnext_concurrent/1]).
33-export([slot/1]).
34-export([hash_clash/1]).
35-export([match1/1, match2/1, match_object/1, match_object2/1]).
36-export([dups/1, misc1/1, safe_fixtable/1, info/1, tab2list/1]).
37-export([info_binary_stress/1]).
38-export([tab2file/1, tab2file2/1, tabfile_ext1/1,
39	 tabfile_ext2/1, tabfile_ext3/1, tabfile_ext4/1, badfile/1]).
40-export([heavy_lookup/1, heavy_lookup_element/1, heavy_concurrent/1]).
41-export([lookup_element_mult/1]).
42-export([foldl_ordered/1, foldr_ordered/1, foldl/1, foldr/1, fold_empty/1]).
43-export([t_delete_object/1, t_init_table/1, t_whitebox/1,
44         select_bound_chunk/1, t_delete_all_objects/1, t_test_ms/1,
45	 t_select_delete/1,t_select_replace/1,t_select_replace_next_bug/1,
46         t_select_pam_stack_overflow_bug/1,
47         t_ets_dets/1]).
48-export([t_insert_list/1, t_insert_list_bag/1, t_insert_list_duplicate_bag/1,
49         t_insert_list_set/1, t_insert_list_delete_set/1,
50         t_insert_list_parallel/1, t_insert_list_delete_parallel/1,
51         t_insert_list_kill_process/1]).
52-export([test_table_size_concurrency/1,test_table_memory_concurrency/1,
53         test_delete_table_while_size_snapshot/1, test_delete_table_while_size_snapshot_helper/1,
54         test_decentralized_counters_setting/1]).
55
56-export([ordered/1, ordered_match/1, interface_equality/1,
57	 fixtable_next/1, fixtable_iter_bag/1,
58         fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1,
59	 update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]).
60-export([update_counter_with_default/1]).
61-export([update_counter_with_default_bad_pos/1]).
62-export([update_counter_table_growth/1]).
63-export([member/1]).
64-export([memory/1]).
65-export([select_fail/1]).
66-export([t_insert_new/1]).
67-export([t_repair_continuation/1]).
68-export([t_match_spec_run/1]).
69-export([t_bucket_disappears/1]).
70-export([t_named_select/1]).
71-export([select_fixtab_owner_change/1]).
72-export([otp_5340/1]).
73-export([otp_6338/1]).
74-export([otp_6842_select_1000/1]).
75-export([select_mbuf_trapping/1]).
76-export([otp_7665/1]).
77-export([meta_wb/1]).
78-export([grow_shrink/1, grow_pseudo_deleted/1, shrink_pseudo_deleted/1]).
79-export([meta_lookup_unnamed_read/1, meta_lookup_unnamed_write/1,
80	 meta_lookup_named_read/1, meta_lookup_named_write/1,
81	 meta_newdel_unnamed/1, meta_newdel_named/1]).
82-export([smp_insert/1, smp_fixed_delete/1, smp_unfix_fix/1, smp_select_delete/1,
83         smp_ordered_iteration/1,
84         smp_select_replace/1, otp_8166/1, otp_8732/1, delete_unfix_race/1]).
85-export([throughput_benchmark/0,
86         throughput_benchmark/1,
87         test_throughput_benchmark/1,
88         long_throughput_benchmark/1,
89         lookup_catree_par_vs_seq_init_benchmark/0]).
90-export([exit_large_table_owner/1,
91	 exit_many_large_table_owner/1,
92	 exit_many_tables_owner/1,
93	 exit_many_many_tables_owner/1]).
94-export([write_concurrency/1, heir/1, give_away/1, setopts/1]).
95-export([bad_table/1, types/1]).
96-export([otp_9932/1]).
97-export([otp_9423/1]).
98-export([otp_10182/1]).
99-export([ets_all/1]).
100-export([massive_ets_all/1]).
101-export([take/1]).
102-export([whereis_table/1]).
103-export([ms_excessive_nesting/1]).
104-export([error_info/1]).
105
106-export([init_per_testcase/2, end_per_testcase/2]).
107%% Convenience for manual testing
108-export([random_test/0]).
109
110-export([t_select_reverse/1]).
111
112-include_lib("stdlib/include/ms_transform.hrl"). % ets:fun2ms
113-include_lib("common_test/include/ct.hrl").
114-include_lib("common_test/include/ct_event.hrl").
115
116-define(m(A,B), assert_eq(A,B)).
117-define(heap_binary_size, 64).
118
119init_per_testcase(Case, Config) ->
120    rand:seed(default),
121    io:format("*** SEED: ~p ***\n", [rand:export_seed()]),
122    start_spawn_logger(),
123    wait_for_test_procs(), %% Ensure previous case cleaned up
124    [{test_case, Case} | Config].
125
126end_per_testcase(_Func, _Config) ->
127    wait_for_test_procs(true).
128
129
130%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
131
132suite() ->
133    [{ct_hooks,[ts_install_cth]},
134     {timetrap,{minutes,5}}].
135
136all() ->
137    [{group, new}, {group, insert}, {group, lookup},
138     {group, delete}, firstnext, firstnext_concurrent, slot, hash_clash,
139     {group, match}, t_match_spec_run,
140     {group, lookup_element}, {group, misc}, {group, files},
141     {group, heavy}, {group, insert_list}, ordered, ordered_match,
142     interface_equality, fixtable_next, fixtable_iter_bag, fixtable_insert,
143     rename, rename_unnamed, evil_rename, update_element,
144     update_counter, evil_update_counter,
145     update_counter_with_default,
146     update_counter_with_default_bad_pos,
147     partly_bound,
148     update_counter_table_growth,
149     match_heavy, {group, fold}, member, t_delete_object,
150     select_bound_chunk,
151     t_init_table, t_whitebox, t_delete_all_objects,
152     t_test_ms, t_select_delete, t_select_replace,
153     t_select_replace_next_bug,
154     t_select_pam_stack_overflow_bug,
155     t_ets_dets, memory, t_select_reverse, t_bucket_disappears,
156     t_named_select, select_fixtab_owner_change,
157     select_fail, t_insert_new, t_repair_continuation,
158     otp_5340, otp_6338, otp_6842_select_1000, otp_7665,
159     select_mbuf_trapping,
160     otp_8732, meta_wb, grow_shrink, grow_pseudo_deleted,
161     shrink_pseudo_deleted, {group, meta_smp}, smp_insert,
162     smp_fixed_delete, smp_unfix_fix, smp_select_replace,
163     smp_ordered_iteration,
164     smp_select_delete, otp_8166, exit_large_table_owner,
165     exit_many_large_table_owner, exit_many_tables_owner,
166     exit_many_many_tables_owner, write_concurrency, heir,
167     give_away, setopts, bad_table, types,
168     otp_10182,
169     otp_9932,
170     otp_9423,
171     ets_all,
172     massive_ets_all,
173     take,
174     whereis_table,
175     delete_unfix_race,
176     %test_throughput_benchmark,
177     %{group, benchmark},
178     test_table_size_concurrency,
179     test_table_memory_concurrency,
180     test_delete_table_while_size_snapshot,
181     test_decentralized_counters_setting,
182     ms_excessive_nesting,
183     error_info].
184
185
186groups() ->
187    [{new, [],
188      [default, setbag, badnew, verybadnew, named, keypos2,
189       privacy]},
190     {insert, [], [empty, badinsert]},
191     {lookup, [], [badlookup, lookup_order]},
192     {lookup_element, [], [lookup_element_mult]},
193     {delete, [],
194      [delete_elem, delete_tab, delete_large_tab,
195       delete_large_named_table, evil_delete, table_leak,
196       baddelete, match_delete, match_delete3]},
197     {match, [],
198      [match1, match2, match_object, match_object2]},
199     {misc, [],
200      [misc1, safe_fixtable, info, info_binary_stress, dups, tab2list]},
201     {files, [],
202      [tab2file, tab2file2, tabfile_ext1,
203       tabfile_ext2, tabfile_ext3, tabfile_ext4, badfile]},
204     {heavy, [],
205      [heavy_lookup, heavy_lookup_element, heavy_concurrent]},
206     {fold, [],
207      [foldl_ordered, foldr_ordered, foldl, foldr,
208       fold_empty]},
209     {meta_smp, [],
210      [meta_lookup_unnamed_read, meta_lookup_unnamed_write,
211       meta_lookup_named_read, meta_lookup_named_write,
212       meta_newdel_unnamed, meta_newdel_named]},
213     {benchmark, [],
214      [long_throughput_benchmark]},
215     {insert_list, [],
216      [t_insert_list, t_insert_list_set, t_insert_list_bag,
217       t_insert_list_duplicate_bag, t_insert_list_delete_set,
218       t_insert_list_parallel, t_insert_list_delete_parallel,
219       t_insert_list_kill_process]}].
220
221init_per_suite(Config) ->
222    erts_debug:set_internal_state(available_internal_state, true),
223    case erts_debug:set_internal_state(ets_force_trap, true) of
224        ok ->
225            [{ets_force_trap, true} | Config];
226        notsup ->
227            Config
228    end.
229
230end_per_suite(_Config) ->
231    stop_spawn_logger(),
232    erts_debug:set_internal_state(ets_force_trap, false),
233    catch erts_debug:set_internal_state(available_internal_state, false),
234    ok.
235
236init_per_group(benchmark, Config) ->
237    P = self(),
238    %% Spawn owner of ETS table that is alive until end_per_group is run
239    EtsProcess =
240        spawn(
241          fun()->
242                  Tab = ets:new(ets_benchmark_result_summary_tab, [public]),
243                  P ! {the_table, Tab},
244                  receive
245                      kill -> ok
246                  end
247          end),
248    Tab = receive {the_table, T} -> T end,
249    CounterNames = [nr_of_benchmarks,
250                    total_throughput,
251                    nr_of_set_benchmarks,
252                    total_throughput_set,
253                    nr_of_ordered_set_benchmarks,
254                    total_throughput_ordered_set],
255    lists:foreach(fun(CtrName) ->
256                          ets:insert(Tab, {CtrName, 0.0})
257                  end,
258                  CounterNames),
259    [{ets_benchmark_result_summary_tab, Tab},
260     {ets_benchmark_result_summary_tab_process, EtsProcess} | Config];
261init_per_group(_GroupName, Config) ->
262    Config.
263
264end_per_group(benchmark, Config) ->
265    T = proplists:get_value(ets_benchmark_result_summary_tab, Config),
266    EtsProcess = proplists:get_value(ets_benchmark_result_summary_tab_process, Config),
267    Report =
268        fun(NOfBenchmarksCtr, TotThroughoutCtr, Name) ->
269                Average =
270                    ets:lookup_element(T, TotThroughoutCtr, 2) /
271                    ets:lookup_element(T, NOfBenchmarksCtr, 2),
272                io:format("~p ~p~n", [Name, Average]),
273                ct_event:notify(
274                  #event{name = benchmark_data,
275                         data = [{suite,"ets_bench"},
276                                 {name, Name},
277                                 {value, Average}]})
278        end,
279    Report(nr_of_benchmarks,
280           total_throughput,
281           "Average Throughput"),
282    Report(nr_of_set_benchmarks,
283           total_throughput_set,
284           "Average Throughput Set"),
285    Report(nr_of_ordered_set_benchmarks,
286           total_throughput_ordered_set,
287           "Average Throughput Ordered Set"),
288    ets:delete(T),
289    EtsProcess ! kill,
290    Config;
291end_per_group(_GroupName, Config) ->
292    Config.
293
294
295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
296
297%% Test that a disappearing bucket during select of a non-fixed table works.
298t_bucket_disappears(Config) when is_list(Config) ->
299    repeat_for_opts(fun t_bucket_disappears_do/1).
300
301t_bucket_disappears_do(Opts) ->
302    EtsMem = etsmem(),
303    ets_new(abcd, [named_table, public, {keypos, 2} | Opts]),
304    ets:insert(abcd, {abcd,1,2}),
305    ets:insert(abcd, {abcd,2,2}),
306    ets:insert(abcd, {abcd,3,2}),
307    {_, Cont} = ets:select(abcd, [{{'_', '$1', '_'},
308				   [{'<', '$1', {const, 10}}],
309				   ['$1']}], 1),
310    ets:delete(abcd, 2),
311    ets:select(Cont),
312    true = ets:delete(abcd),
313    verify_etsmem(EtsMem).
314
315%% OTP-21: Test that select/1 fails if named table was deleted and recreated
316%%         and succeeds if table was renamed.
317t_named_select(_Config) ->
318    repeat_for_opts(fun t_named_select_do/1).
319
320t_named_select_do(Opts) ->
321    EtsMem = etsmem(),
322    T = t_name_tid_select,
323    ets_new(T, [named_table | Opts]),
324    ets:insert(T, {1,11}),
325    ets:insert(T, {2,22}),
326    ets:insert(T, {3,33}),
327    MS = [{{'$1', 22}, [], ['$1']}],
328    {[2], Cont1} = ets:select(T, MS, 1),
329    ets:delete(T),
330    {'EXIT',{badarg,_}} = (catch ets:select(Cont1)),
331    ets_new(T, [named_table | Opts]),
332    {'EXIT',{badarg,_}} = (catch ets:select(Cont1)),
333
334    true = ets:insert_new(T, {1,22}),
335    true = ets:insert_new(T, {2,22}),
336    true = ets:insert_new(T, {4,22}),
337    {[A,B], Cont2} = ets:select(T, MS, 2),
338    ets:rename(T, abcd),
339    {[C], '$end_of_table'} = ets:select(Cont2),
340    7 = A + B + C,
341
342    true = ets:delete(abcd),
343    verify_etsmem(EtsMem).
344
345
346%% Verify select and friends release fixtab as they should
347%% even when owneship is changed between traps.
348select_fixtab_owner_change(_Config) ->
349    T = ets:new(xxx, [protected]),
350    NKeys = 2000,
351    [ets:insert(T,{K,K band 7}) || K <- lists:seq(1,NKeys)],
352
353    %% Buddy and Papa will ping-pong table ownership between them
354    %% and the aim is to give Buddy the table when he is
355    %% in the middle of a yielding select* call.
356    {Buddy,_} = spawn_opt(fun() -> sfoc_buddy_loop(T, 1, undefined) end,
357                          [link,monitor]),
358
359    sfoc_papa_loop(T, Buddy),
360
361    receive {'DOWN', _, process, Buddy, _} -> ok end,
362    ets:delete(T),
363    ok.
364
365sfoc_buddy_loop(T, I, State0) ->
366    receive
367        {'ETS-TRANSFER', T, Papa, _} ->
368            ets:give_away(T, Papa, State0),
369            case State0 of
370                done ->
371                    ok;
372                _ ->
373                    State1 = sfoc_traverse(T, I, State0),
374                    %% Verify no fixation left
375                    {I, false} = {I, ets:info(T, safe_fixed_monotonic_time)},
376                    sfoc_buddy_loop(T, I+1, State1)
377            end
378    end.
379
380sfoc_papa_loop(T, Buddy) ->
381    ets:give_away(T, Buddy, "Catch!"),
382    receive
383        {'ETS-TRANSFER', T, Buddy, State} ->
384            case State of
385                done ->
386                    ok;
387                _ ->
388                    sfoc_papa_loop(T, Buddy)
389            end
390    end.
391
392sfoc_traverse(T, 1, S) ->
393    ets:select(T, [{{'$1',7}, [], ['$1']}]), S;
394sfoc_traverse(T, 2, S) ->
395    0 = ets:select_count(T, [{{'$1',7}, [], [false]}]), S;
396sfoc_traverse(T, 3, _) ->
397    Limit = ets:info(T, size) div 2,
398    {_, Continuation} = ets:select(T, [{{'$1',7}, [], ['$1']}],
399                                   Limit),
400    Continuation;
401sfoc_traverse(_T, 4, Continuation) ->
402    _ = ets:select(Continuation),
403    done.
404
405%% Check ets:match_spec_run/2.
406t_match_spec_run(Config) when is_list(Config) ->
407    ct:timetrap({minutes,30}), %% valgrind needs a lot
408    init_externals(),
409    EtsMem = etsmem(),
410
411    t_match_spec_run_test([{1},{2},{3}],
412			  [{{'$1'},[{'>','$1',1}],['$1']}],
413			  [2,3]),
414
415    Huge = [{X} || X <- lists:seq(1,2500)],
416    L = lists:seq(2476,2500),
417    t_match_spec_run_test(Huge, [{{'$1'},[{'>','$1',2475}],['$1']}], L),
418
419    L2 = [{X*16#FFFFFFF} || X <- L],
420    t_match_spec_run_test(Huge,
421			  [{{'$1'}, [{'>','$1',2475}], [{{{'*','$1',16#FFFFFFF}}}]}],
422			  L2),
423
424    t_match_spec_run_test(Huge, [{{'$1'}, [{'=:=',{'rem','$1',500},0}], ['$1']}],
425			  [500,1000,1500,2000,2500]),
426
427    %% More matching fun with several match clauses and guards,
428    %% applied to a variety of terms.
429    Fun = fun(Term) ->
430		  CTerm = {const, Term},
431
432		  N_List = [{Term, "0", "v-element"},
433			    {"=hidden_node", "0", Term},
434			    {"0", Term, Term},
435			    {"something", Term, "something else"},
436			    {"guard and res", Term, 872346},
437			    {Term, {'and',Term,'again'}, 3.14},
438			    {Term, {'and',Term,'again'}, "m&g"},
439			    {Term, {'and',Term,'again'}, "m&g&r"},
440			    {[{second,Term}, 'and', "tail"], Term, ['and',"tail"]}],
441
442		  N_MS = [{{'$1','$2','$3'},
443			   [{'=:=','$1',CTerm}, {'=:=','$2',{const,"0"}}],
444			   [{{"Guard only for $1",'$3'}}]},
445
446			  {{'$3','$1','$4'},
447			   [{'=:=','$3',"=hidden_node"}, {'=:=','$1',{const,"0"}}],
448			   [{{"Result only for $4",'$4'}}]},
449
450			  {{'$2','$1','$1'},
451			   [{'=:=','$2',{const,"0"}}],
452			   [{{"Match only for $1",'$2'}}]},
453
454			  {{'$2',Term,['$3'|'_']},
455			   [{is_list,'$2'},{'=:=','$3',$s}],
456			   [{{"Matching term",'$2'}}]},
457
458			  {{'$1','$2',872346},
459			   [{'=:=','$2',CTerm}, {is_list,'$1'}],
460			   [{{"Guard and result",'$2'}}]},
461
462			  {{'$1', {'and','$1','again'}, '$2'},
463			   [{is_float,'$2'}],
464			   [{{"Match and result",'$1'}}]},
465
466			  {{'$1', {'and','$1','again'}, '$2'},
467			   [{'=:=','$1',CTerm}, {'=:=', '$2', "m&g"}],
468			   [{{"Match and guard",'$2'}}]},
469
470			  {{'$1', {'and','$1','again'}, "m&g&r"},
471			   [{'=:=','$1',CTerm}],
472			   [{{"Match, guard and result",'$1'}}]},
473
474			  {{'$1', '$2', '$3'},
475			   [{'=:=','$1',[{{second,'$2'}} | '$3']}],
476			   [{{"Building guard"}}]}
477			 ],
478
479		  N_Result = [{"Guard only for $1", "v-element"},
480			      {"Result only for $4", Term},
481			      {"Match only for $1", "0"},
482			      {"Matching term","something"},
483			      {"Guard and result",Term},
484			      {"Match and result",Term},
485			      {"Match and guard","m&g"},
486			      {"Match, guard and result",Term},
487			      {"Building guard"}],
488
489		  F = fun(N_MS_Perm) ->
490			      t_match_spec_run_test(N_List, N_MS_Perm, N_Result)
491		      end,
492		  repeat_for_permutations(F, N_MS)
493	  end,
494    test_terms(Fun, skip_refc_check),
495
496    verify_etsmem(EtsMem).
497
498t_match_spec_run_test(List, MS, Result) ->
499
500    %%io:format("ms = ~p\n",[MS]),
501
502    ?m(Result, ets:match_spec_run(List, ets:match_spec_compile(MS))),
503
504    %% Check that ets:select agree
505    Tab = ets:new(xxx, [bag]),
506    ets:insert(Tab, List),
507    SRes = lists:sort(Result),
508    ?m(SRes, lists:sort(ets:select(Tab, MS))),
509    ets:delete(Tab),
510
511    %% Check that tracing agree
512    Self = self(),
513    {Tracee, MonRef} = my_spawn_monitor(fun() -> ms_tracee(Self, List) end),
514    receive {Tracee, ready} -> ok end,
515
516    MST = lists:map(fun(Clause) -> ms_clause_ets_to_trace(Clause) end, MS),
517
518    %%io:format("MS = ~p\nMST= ~p\n",[MS,MST]),
519
520    erlang:trace_pattern({?MODULE,ms_tracee_dummy,'_'}, MST , [local]),
521    erlang:trace(Tracee, true, [call]),
522    Tracee ! start,
523    TRes = ms_tracer_collect(Tracee, MonRef, []),
524    case TRes of
525	SRes -> ok;
526	_ ->
527	    io:format("TRACE MATCH FAILED\n"),
528	    io:format("Input = ~p\nMST = ~p\nExpected = ~p\nGot = ~p\n", [List, MST, SRes, TRes]),
529	    ct:fail("TRACE MATCH FAILED")
530    end,
531    ok.
532
533
534
535ms_tracer_collect(Tracee, Ref, Acc) ->
536    receive
537	{trace, Tracee, call, _Args, [Msg]} ->
538	    ms_tracer_collect(Tracee, Ref, [Msg | Acc]);
539
540	{'DOWN', Ref, process, Tracee, _} ->
541	    TDRef = erlang:trace_delivered(Tracee),
542	    ms_tracer_collect(Tracee, TDRef, Acc);
543
544	{trace_delivered, Tracee, Ref} ->
545	    lists:sort(Acc);
546
547	Other ->
548	    io:format("Unexpected message = ~p\n", [Other]),
549	    ct:fail("Unexpected tracer msg")
550    end.
551
552
553ms_tracee(Parent, CallArgList) ->
554    Parent ! {self(), ready},
555    receive start -> ok end,
556    F = fun({A1}) ->
557                ms_tracee_dummy(A1);
558           ({A1,A2}) ->
559                   ms_tracee_dummy(A1, A2);
560           ({A1,A2,A3}) ->
561                ms_tracee_dummy(A1, A2, A3);
562           ({A1,A2,A3,A4}) ->
563                ms_tracee_dummy(A1, A2, A3, A4)
564        end,
565    lists:foreach(F, CallArgList).
566
567ms_tracee_dummy(_) -> ok.
568ms_tracee_dummy(_,_) -> ok.
569ms_tracee_dummy(_,_,_) -> ok.
570ms_tracee_dummy(_,_,_,_) -> ok.
571
572ms_clause_ets_to_trace({Head, Guard, Body}) ->
573    {tuple_to_list(Head), Guard, [{message, Body}]}.
574
575assert_eq(A,A) -> ok;
576assert_eq(A,B) ->
577    io:format("FAILED MATCH:\n~p\n =/=\n~p\n",[A,B]),
578    ct:fail("assert_eq failed").
579
580
581%% Test ets:repair_continuation/2.
582t_repair_continuation(Config) when is_list(Config) ->
583    repeat_for_opts(fun t_repair_continuation_do/1).
584
585
586t_repair_continuation_do(Opts) ->
587    EtsMem = etsmem(),
588    MS = [{'_',[],[true]}],
589    MS2 = [{{{'$1','_'},'_'},[],['$1']}],
590    (fun() ->
591	     T = ets_new(x,[ordered_set|Opts]),
592	     F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
593	     F(1000,F),
594	     {_,C} = ets:select(T,MS,5),
595	     C2 = erlang:setelement(5,C,<<>>),
596	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
597	     C3 = ets:repair_continuation(C2,MS),
598	     {[true,true,true,true,true],_} = ets:select(C3),
599	     {[true,true,true,true,true],_} = ets:select(C),
600	     true = ets:delete(T)
601     end)(),
602    (fun() ->
603	     T = ets_new(x,[ordered_set|Opts]),
604	     F = fun(0,_)->ok;(N,F) -> ets:insert(T,{N,N}), F(N-1,F) end,
605	     F(1000,F),
606	     {_,C} = ets:select(T,MS,1001),
607	     C = '$end_of_table',
608	     C3 = ets:repair_continuation(C,MS),
609	     '$end_of_table' = ets:select(C3),
610	     '$end_of_table' = ets:select(C),
611	     true = ets:delete(T)
612     end)(),
613
614    (fun() ->
615	     T = ets_new(x,[ordered_set|Opts]),
616	     F = fun(0,_)->ok;(N,F) ->
617			 ets:insert(T,{integer_to_list(N),N}),
618			 F(N-1,F)
619		 end,
620	     F(1000,F),
621	     {_,C} = ets:select(T,MS,5),
622	     C2 = erlang:setelement(5,C,<<>>),
623	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
624	     C3 = ets:repair_continuation(C2,MS),
625	     {[true,true,true,true,true],_} = ets:select(C3),
626	     {[true,true,true,true,true],_} = ets:select(C),
627	     true = ets:delete(T)
628     end)(),
629    (fun() ->
630	     T = ets_new(x,[ordered_set|Opts]),
631	     F = fun(0,_)->ok;(N,F) ->
632			 ets:insert(T,{{integer_to_list(N),N},N}),
633			 F(N-1,F)
634		 end,
635	     F(1000,F),
636	     {_,C} = ets:select(T,MS2,5),
637	     C2 = erlang:setelement(5,C,<<>>),
638	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
639	     C3 = ets:repair_continuation(C2,MS2),
640	     {[_,_,_,_,_],_} = ets:select(C3),
641	     {[_,_,_,_,_],_} = ets:select(C),
642	     true = ets:delete(T)
643     end)(),
644
645    (fun() ->
646	     T = ets_new(x,[set|Opts]),
647	     F = fun(0,_)->ok;(N,F) ->
648			 ets:insert(T,{N,N}),
649			 F(N-1,F)
650		 end,
651	     F(1000,F),
652	     {_,C} = ets:select(T,MS,5),
653	     C2 = erlang:setelement(4,C,<<>>),
654	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
655	     C3 = ets:repair_continuation(C2,MS),
656	     {[true,true,true,true,true],_} = ets:select(C3),
657	     {[true,true,true,true,true],_} = ets:select(C),
658	     true = ets:delete(T)
659     end)(),
660    (fun() ->
661	     T = ets_new(x,[set|Opts]),
662	     F = fun(0,_)->ok;(N,F) ->
663			 ets:insert(T,{integer_to_list(N),N}),
664			 F(N-1,F)
665		 end,
666	     F(1000,F),
667	     {_,C} = ets:select(T,MS,5),
668	     C2 = erlang:setelement(4,C,<<>>),
669	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
670	     C3 = ets:repair_continuation(C2,MS),
671	     {[true,true,true,true,true],_} = ets:select(C3),
672	     {[true,true,true,true,true],_} = ets:select(C),
673	     true = ets:delete(T)
674     end)(),
675    (fun() ->
676	     T = ets_new(x,[bag|Opts]),
677	     F = fun(0,_)->ok;(N,F) ->
678			 ets:insert(T,{integer_to_list(N),N}),
679			 F(N-1,F)
680		 end,
681	     F(1000,F),
682	     {_,C} = ets:select(T,MS,5),
683	     C2 = erlang:setelement(4,C,<<>>),
684	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
685	     C3 = ets:repair_continuation(C2,MS),
686	     {[true,true,true,true,true],_} = ets:select(C3),
687	     {[true,true,true,true,true],_} = ets:select(C),
688	     true = ets:delete(T)
689     end)(),
690    (fun() ->
691	     T = ets_new(x,[duplicate_bag|Opts]),
692	     F = fun(0,_)->ok;(N,F) ->
693			 ets:insert(T,{integer_to_list(N),N}),
694			 F(N-1,F)
695		 end,
696	     F(1000,F),
697	     {_,C} = ets:select(T,MS,5),
698	     C2 = erlang:setelement(4,C,<<>>),
699	     {'EXIT',{badarg,_}} = (catch ets:select(C2)),
700	     C3 = ets:repair_continuation(C2,MS),
701	     {[true,true,true,true,true],_} = ets:select(C3),
702	     {[true,true,true,true,true],_} = ets:select(C),
703	     true = ets:delete(T)
704     end)(),
705    false = ets:is_compiled_ms(<<>>),
706    true = ets:is_compiled_ms(ets:match_spec_compile(MS)),
707    verify_etsmem(EtsMem).
708
709
710%% Test correct default vaules of a new ets table.
711default(Config) when is_list(Config) ->
712    %% Default should be set,protected
713    EtsMem = etsmem(),
714    Def = ets_new(def,[]),
715    set = ets:info(Def,type),
716    protected = ets:info(Def,protection),
717    Compressed = erlang:system_info(ets_always_compress),
718    Compressed = ets:info(Def,compressed),
719    Self = self(),
720    Self = ets:info(Def,owner),
721    none = ets:info(Def, heir),
722    false = ets:info(Def,named_table),
723    ets:delete(Def),
724    verify_etsmem(EtsMem).
725
726%% Test that select fails even if nothing can match.
727select_fail(Config) when is_list(Config) ->
728    EtsMem = etsmem(),
729    repeat_for_opts(fun select_fail_do/1,
730                    [all_types,write_concurrency]),
731    verify_etsmem(EtsMem).
732
733select_fail_do(Opts) ->
734    T = ets_new(x,Opts),
735    ets:insert(T,{a,a}),
736    case (catch
737	      ets:select(T,[{{a,'_'},[],[{snuffla}]}])) of
738	{'EXIT',{badarg,_}} ->
739	    ok;
740	Else0 ->
741	    exit({type,ets:info(T,type),
742		  expected,'EXIT',got,Else0})
743    end,
744    case (catch
745	      ets:select(T,[{{b,'_'},[],[{snuffla}]}])) of
746	{'EXIT',{badarg,_}} ->
747	    ok;
748	Else1 ->
749	    exit({type,ets:info(T,type),
750		  expected,'EXIT',got,Else1})
751    end,
752    ets:delete(T).
753
754
755-define(S(T),ets:info(T,memory)).
756
757%% Whitebox test of ets:info(X, memory).
758memory(Config) when is_list(Config) ->
759    ok = chk_normal_tab_struct_size(),
760    repeat_for_opts(fun memory_do/1, [compressed]),
761    catch erts_debug:set_internal_state(available_internal_state, false).
762
763memory_do(Opts) ->
764    L = [T1,T2,T3,T4] = fill_sets_int(1000,Opts),
765    XR1 = case mem_mode(T1) of
766	      {normal,_} ->     {13836, 15346, 15346, 15346+6};
767	      {compressed,4} -> {11041, 12551, 12551, 12551+1};
768	      {compressed,8} -> {10050, 11560, 11560, 11560}
769	  end,
770    XRes1 = adjust_xmem(L, XR1, 1),
771    Res1 = {?S(T1),?S(T2),?S(T3),?S(T4)},
772    lists:foreach(fun(T) ->
773			  Before = ets:info(T,size),
774			  Key = 2, %894, %%ets:first(T),
775			  Objs = ets:lookup(T,Key),
776			  ets:delete(T,Key),
777			  io:format("deleted key ~p from ~p changed size ~p to ~p: ~p\n",
778				    [Key, ets:info(T,type), Before, ets:info(T,size), Objs])
779		  end,
780		  L),
781    XR2 = case mem_mode(T1) of
782	      {normal,_} ->     {13826, 15337, 15337-9, 15337-3};
783	      {compressed,4} -> {11031, 12542, 12542-9, 12542-8};
784	      {compressed,8} -> {10040, 11551, 11551-9, 11551-9}
785	  end,
786    XRes2 = adjust_xmem(L, XR2, 1),
787    Res2 = {?S(T1),?S(T2),?S(T3),?S(T4)},
788    lists:foreach(fun(T) ->
789			  Before = ets:info(T,size),
790			  Key = 4, %802, %ets:first(T),
791			  Objs = ets:lookup(T,Key),
792			  ets:match_delete(T,{Key,'_'}),
793			  io:format("match_deleted key ~p from ~p changed size ~p to ~p: ~p\n",
794				    [Key, ets:info(T,type), Before, ets:info(T,size), Objs])
795		  end,
796		  L),
797    XR3 = case mem_mode(T1) of
798	      {normal,_} ->     {13816, 15328, 15328-18, 15328-12};
799	      {compressed,4} -> {11021, 12533, 12533-18, 12533-17};
800	      {compressed,8} -> {10030, 11542, 11542-18, 11542-18}
801	  end,
802    XRes3 = adjust_xmem(L, XR3, 1),
803    Res3 = {?S(T1),?S(T2),?S(T3),?S(T4)},
804    lists:foreach(fun(T) ->
805			  ets:delete_all_objects(T)
806		  end,
807		  L),
808    XRes4 = adjust_xmem(L, {50, 256, 256, 256}, 0),
809    Res4 = {?S(T1),?S(T2),?S(T3),?S(T4)},
810    lists:foreach(fun(T) ->
811			  ets:delete(T)
812		  end,
813		  L),
814    L2 =  [T11,T12,T13,T14] = fill_sets_int(1000),
815    lists:foreach(fun(T) ->
816			  ets:select_delete(T,[{'_',[],[true]}])
817		  end,
818		  L2),
819    XRes5 = adjust_xmem(L2, {50, 256, 256, 256}, 0),
820    Res5 = {?S(T11),?S(T12),?S(T13),?S(T14)},
821    io:format("XRes1 = ~p~n"
822	      " Res1 = ~p~n~n"
823	      "XRes2 = ~p~n"
824	      " Res2 = ~p~n~n"
825	      "XRes3 = ~p~n"
826	      " Res3 = ~p~n~n"
827	      "XRes4 = ~p~n"
828	      " Res4 = ~p~n~n"
829	      "XRes5 = ~p~n"
830	      " Res5 = ~p~n~n",
831	      [XRes1, Res1,
832	       XRes2, Res2,
833	       XRes3, Res3,
834	       XRes4, Res4,
835	       XRes5, Res5]),
836    XRes1 = Res1,
837    XRes2 = Res2,
838    XRes3 = Res3,
839    XRes4 = Res4,
840    XRes5 = Res5,
841    ok.
842
843mem_mode(T) ->
844    {case ets:info(T,compressed) of
845	 true -> compressed;
846	 false -> normal
847     end,
848     erlang:system_info(wordsize)}.
849
850chk_normal_tab_struct_size() ->
851    System = {os:type(),
852	      os:version(),
853	      erlang:system_info(wordsize),
854	      erlang:system_info(smp_support),
855	      erlang:system_info(heap_type)},
856    io:format("System = ~p~n", [System]),
857    ok.
858
859adjust_xmem([_T1,_T2,_T3,_T4], {A0,B0,C0,D0} = _Mem0, EstCnt) ->
860    %% Adjust for 64-bit, smp, and os:
861    %%   Table struct size may differ.
862
863    {TabSz, EstSz} = erts_debug:get_internal_state('DbTable_words'),
864    HTabSz = TabSz + EstCnt*EstSz,
865    OrdSetExtra = case erlang:system_info(wordsize) of
866                      8 -> 40; % larger stack on 64 bit architectures
867                      _ -> 0
868                  end,
869    {A0+TabSz+OrdSetExtra, B0+HTabSz, C0+HTabSz, D0+HTabSz}.
870
871%% Misc. whitebox tests
872t_whitebox(Config) when is_list(Config) ->
873    EtsMem = etsmem(),
874    repeat_for_opts(fun whitebox_1/1),
875    repeat_for_opts(fun whitebox_1/1),
876    repeat_for_opts(fun whitebox_1/1),
877    repeat_for_opts(fun whitebox_2/1),
878    repeat_for_opts(fun whitebox_2/1),
879    repeat_for_opts(fun whitebox_2/1),
880    verify_etsmem(EtsMem).
881
882whitebox_1(Opts) ->
883    T=ets_new(x,[bag | Opts]),
884    ets:insert(T,[{du,glade},{ta,en}]),
885    ets:insert(T,[{hej,hopp2},{du,glade2},{ta,en2}]),
886    {_,C}=ets:match(T,{ta,'$1'},1),
887    ets:select(C),
888    ets:match(C),
889    ets:delete(T),
890    ok.
891
892whitebox_2(Opts) ->
893    T=ets_new(x,[ordered_set, {keypos,2} | Opts]),
894    T2=ets_new(x,[set, {keypos,2}| Opts]),
895    0 = ets:select_delete(T,[{{hej},[],[true]}]),
896    0 = ets:select_delete(T,[{{hej,hopp},[],[true]}]),
897    0 = ets:select_delete(T2,[{{hej},[],[true]}]),
898    0 = ets:select_delete(T2,[{{hej,hopp},[],[true]}]),
899    ets:delete(T),
900    ets:delete(T2),
901    ok.
902
903select_bound_chunk(_Config) ->
904    repeat_for_opts(fun select_bound_chunk_do/1, [all_types]).
905
906select_bound_chunk_do(Opts) ->
907    T = ets_new(x, Opts),
908    ets:insert(T, [{key, 1}]),
909    {[{key, 1}], '$end_of_table'} = ets:select(T, [{{key,1},[],['$_']}], 100000),
910    ok.
911
912
913%% Test ets:to/from_dets.
914t_ets_dets(Config) when is_list(Config) ->
915    repeat_for_opts(fun(Opts) -> t_ets_dets(Config,Opts) end).
916
917t_ets_dets(Config, Opts) ->
918    Fname = gen_dets_filename(Config,1),
919    (catch file:delete(Fname)),
920    {ok,DTab} = dets:open_file(testdets_1,
921			       [{file, Fname}]),
922    ETab = ets_new(x,Opts),
923    filltabint(ETab,3000),
924    DTab = ets:to_dets(ETab,DTab),
925    ets:delete_all_objects(ETab),
926    0 = ets:info(ETab,size),
927    true = ets:from_dets(ETab,DTab),
928    3000 = ets:info(ETab,size),
929    ets:delete(ETab),
930    check_badarg(catch ets:to_dets(ETab,DTab),
931		 ets, to_dets, [ETab,DTab]),
932    check_badarg(catch ets:from_dets(ETab,DTab),
933		 ets, from_dets, [ETab,DTab]),
934    ETab2 = ets_new(x,Opts),
935    filltabint(ETab2,3000),
936    dets:close(DTab),
937    check_badarg(catch ets:to_dets(ETab2,DTab),
938		 ets, to_dets, [ETab2,DTab]),
939    check_badarg(catch ets:from_dets(ETab2,DTab),
940		 ets, from_dets, [ETab2,DTab]),
941    ets:delete(ETab2),
942    (catch file:delete(Fname)),
943    ok.
944
945check_badarg({'EXIT', {badarg, [{M,F,Args,_} | _]}}, M, F, Args) ->
946    true.
947
948%% Test ets:delete_all_objects/1.
949t_delete_all_objects(Config) when is_list(Config) ->
950    EtsMem = etsmem(),
951    repeat_for_opts_all_set_table_types(fun t_delete_all_objects_do/1),
952    verify_etsmem(EtsMem).
953
954get_kept_objects(T) ->
955    case ets:info(T,stats) of
956	{_,_,_,_,_,_,KO}  ->
957	    KO;
958        _ ->
959            0
960    end.
961
962t_delete_all_objects_do(Opts) ->
963    KeyRange = 4000,
964    T=ets_new(x, Opts, KeyRange),
965    filltabint(T,KeyRange),
966    O=ets:first(T),
967    ets:next(T,O),
968    ets:safe_fixtable(T,true),
969    true = ets:delete_all_objects(T),
970    '$end_of_table' = ets:next(T,O),
971    0 = ets:info(T,size),
972    case ets:info(T,type) of
973        ordered_set -> ok;
974        _ -> KeyRange = get_kept_objects(T)
975    end,
976    ets:safe_fixtable(T,false),
977    0 = ets:info(T,size),
978    0 = get_kept_objects(T),
979    filltabint(T, KeyRange),
980    KeyRange = ets:info(T,size),
981    true = ets:delete_all_objects(T),
982    0 = ets:info(T,size),
983    ets:delete(T),
984
985    %% Test delete_all_objects is atomic
986    T2 = ets_new(t_delete_all_objects, [public | Opts]),
987    Self = self(),
988    Inserters = [spawn_link(fun() -> inserter(T2, 1, Self) end) || _ <- [1,2,3,4]],
989    [receive {Ipid, running} -> ok end || Ipid <- Inserters],
990
991    ets:delete_all_objects(T2),
992    erlang:yield(),
993    [Ipid ! stop || Ipid <- Inserters],
994    Result = [receive {Ipid, stopped, Highest} -> {Ipid,Highest} end || Ipid <- Inserters],
995
996    %% Verify unbroken sequences of objects inserted _after_ ets:delete_all_objects.
997    Sum = lists:foldl(fun({Ipid, Highest}, AccSum) ->
998                              %% ets:fun2ms(fun({{K,Ipid}}) when K =< Highest -> true end),
999                              AliveMS = [{{{'$1',Ipid}},[{'=<','$1',{const,Highest}}],[true]}],
1000                              Alive = ets:select_count(T2, AliveMS),
1001                              Lowest = Highest - (Alive-1),
1002
1003                              %% ets:fun2ms(fun({{K,Ipid}}) when K < Lowest -> true end)
1004                              DeletedMS = [{{{'$1',Ipid}},[{'<','$1',{const,Lowest}}],[true]}],
1005                              0 = ets:select_count(T2, DeletedMS),
1006                              AccSum + Alive
1007                      end,
1008                      0,
1009                      Result),
1010    ok = case ets:info(T2, size) of
1011             Sum -> ok;
1012             Size ->
1013                 io:format("Sum = ~p\nSize = ~p\n", [Sum, Size]),
1014                 {Sum,Size}
1015         end,
1016
1017    ets:delete(T2).
1018
1019inserter(T, Next, Papa) ->
1020    Wait = case Next of
1021               10*1000 ->
1022                   Papa ! {self(), running},
1023                   0;
1024               100*1000 -> %% We most often don't reach this far
1025                   io:format("Inserter ~p reached ~p objects\n",
1026                             [self(), Next]),
1027                   infinity;
1028               _ ->
1029                   0
1030           end,
1031
1032    ets:insert(T, {{Next, self()}}),
1033    receive
1034        stop ->
1035            Papa ! {self(), stopped, Next},
1036            ok
1037    after Wait ->
1038            inserter(T, Next+1, Papa)
1039    end.
1040
1041
1042%% Test ets:delete_object/2.
1043t_delete_object(Config) when is_list(Config) ->
1044    EtsMem = etsmem(),
1045    repeat_for_opts(fun t_delete_object_do/1),
1046    verify_etsmem(EtsMem).
1047
1048t_delete_object_do(Opts) ->
1049    T = ets_new(x,Opts),
1050    filltabint(T,4000),
1051    del_one_by_one_set(T,1,4001),
1052    filltabint(T,4000),
1053    del_one_by_one_set(T,4000,0),
1054    filltabint(T,4000),
1055    First = ets:first(T),
1056    Next = ets:next(T,First),
1057    ets:safe_fixtable(T,true),
1058    ets:delete_object(T,{First, integer_to_list(First)}),
1059    Next = ets:next(T,First),
1060    3999 = ets:info(T,size),
1061    1 = get_kept_objects(T),
1062    ets:safe_fixtable(T,false),
1063    3999 = ets:info(T,size),
1064    0 = get_kept_objects(T),
1065    ets:delete(T),
1066    T1 = ets_new(x,[ordered_set | Opts]),
1067    filltabint(T1,4000),
1068    del_one_by_one_set(T1,1,4001),
1069    filltabint(T1,4000),
1070    del_one_by_one_set(T1,4000,0),
1071    ets:delete(T1),
1072    T2 = ets_new(x,[bag | Opts]),
1073    filltabint2(T2,4000),
1074    del_one_by_one_bag(T2,1,4001),
1075    filltabint2(T2,4000),
1076    del_one_by_one_bag(T2,4000,0),
1077    ets:delete(T2),
1078    T3 = ets_new(x,[duplicate_bag | Opts]),
1079    filltabint3(T3,4000),
1080    del_one_by_one_dbag_1(T3,1,4001),
1081    filltabint3(T3,4000),
1082    del_one_by_one_dbag_1(T3,4000,0),
1083    filltabint(T3,4000),
1084    filltabint3(T3,4000),
1085    del_one_by_one_dbag_2(T3,1,4001),
1086    filltabint(T3,4000),
1087    filltabint3(T3,4000),
1088    del_one_by_one_dbag_2(T3,4000,0),
1089
1090    filltabint2(T3,4000),
1091    filltabint(T3,4000),
1092    del_one_by_one_dbag_3(T3,4000,0),
1093    ets:delete(T3),
1094    ok.
1095
1096make_init_fun(N) when N > 4000->
1097    fun(read) ->
1098	    end_of_input;
1099       (close) ->
1100	    exit(close_not_expected)
1101    end;
1102make_init_fun(N) ->
1103    fun(read) ->
1104	    case N rem 2 of
1105		0 ->
1106		    {[{N, integer_to_list(N)}, {N, integer_to_list(N)}],
1107		     make_init_fun(N + 1)};
1108		1 ->
1109		    {[], make_init_fun(N + 1)}
1110	    end;
1111       (close) ->
1112	    exit(close_not_expected)
1113    end.
1114
1115%% Test ets:init_table/2.
1116t_init_table(Config) when is_list(Config)->
1117    EtsMem = etsmem(),
1118    repeat_for_opts(fun t_init_table_do/1),
1119    verify_etsmem(EtsMem).
1120
1121t_init_table_do(Opts) ->
1122    T = ets_new(x,[duplicate_bag | Opts]),
1123    filltabint(T,4000),
1124    ets:init_table(T, make_init_fun(1)),
1125    del_one_by_one_dbag_1(T,4000,0),
1126    ets:delete(T),
1127    ok.
1128
1129do_fill_dbag_using_lists(T,0) ->
1130    T;
1131do_fill_dbag_using_lists(T,N) ->
1132    ets:insert(T,[{N,integer_to_list(N)},
1133		  {N + N rem 2,integer_to_list(N + N rem 2)}]),
1134    do_fill_dbag_using_lists(T,N - 1).
1135
1136
1137%% Test the insert_new function.
1138t_insert_new(Config) when is_list(Config) ->
1139    EtsMem = etsmem(),
1140    L = fill_sets_int(1000) ++ fill_sets_int(1000,[{write_concurrency,true}]),
1141    lists:foreach(fun(Tab) ->
1142			  false = ets:insert_new(Tab,{2,"2"}),
1143			  true = ets:insert_new(Tab,{2002,"2002"}),
1144			  false = ets:insert_new(Tab,{2002,"2002"}),
1145			  true = ets:insert(Tab,{2002,"2002"}),
1146			  false =  ets:insert_new(Tab,[{2002,"2002"}]),
1147			  false =  ets:insert_new(Tab,[{2002,"2002"},
1148						       {2003,"2003"}]),
1149			  false =  ets:insert_new(Tab,[{2001,"2001"},
1150						       {2002,"2002"},
1151						       {2003,"2003"}]),
1152			  false =  ets:insert_new(Tab,[{2001,"2001"},
1153						       {2002,"2002"}]),
1154			  true =  ets:insert_new(Tab,[{2001,"2001"},
1155						      {2003,"2003"}]),
1156			  false = ets:insert_new(Tab,{2001,"2001"}),
1157			  false = ets:insert_new(Tab,{2002,"2002"}),
1158			  false = ets:insert_new(Tab,{2003,"2003"}),
1159			  true = ets:insert_new(Tab,{2004,"2004"}),
1160			  true = ets:insert_new(Tab,{2000,"2000"}),
1161			  true = ets:insert_new(Tab,[{2005,"2005"},
1162						     {2006,"2006"},
1163						     {2007,"2007"}]),
1164			  Num =
1165			      case ets:info(Tab,type) of
1166				  bag ->
1167				      true =
1168					  ets:insert(Tab,{2004,"2004-2"}),
1169				      false =
1170					  ets:insert_new(Tab,{2004,"2004-3"}),
1171				      1009;
1172				  duplicate_bag ->
1173				      true =
1174					  ets:insert(Tab,{2004,"2004"}),
1175				      false =
1176					  ets:insert_new(Tab,{2004,"2004"}),
1177				      1010;
1178				  _ ->
1179				      1008
1180			      end,
1181			  Num = ets:info(Tab,size),
1182			  List = ets:tab2list(Tab),
1183			  ets:delete_all_objects(Tab),
1184			  true = ets:insert_new(Tab,List),
1185			  false = ets:insert_new(Tab,List),
1186			  ets:delete(Tab)
1187		  end,
1188		  L),
1189    verify_etsmem(EtsMem).
1190
1191%% Test ets:insert/2 with list of objects into duplicate bag table.
1192t_insert_list(Config) when is_list(Config) ->
1193    EtsMem = etsmem(),
1194    repeat_for_opts(fun t_insert_list_do/1),
1195    verify_etsmem(EtsMem).
1196
1197t_insert_list_do(Opts) ->
1198    T = ets_new(x,[duplicate_bag | Opts]),
1199    do_fill_dbag_using_lists(T,4000),
1200    del_one_by_one_dbag_2(T,4000,0),
1201    ets:delete(T).
1202
1203% Insert a long list twice in a bag
1204t_insert_list_bag(Config) when is_list(Config) ->
1205    EtsMem = etsmem(),
1206    repeat_for_opts(fun t_insert_list_bag_do/1,
1207                    [write_concurrency, compressed]),
1208    verify_etsmem(EtsMem).
1209
1210t_insert_list_bag_do(Opts) ->
1211    T = ets:new(t, [bag | Opts]),
1212    ListSize = 25000,
1213    List = [ {N} || N <- lists:seq(1, ListSize)],
1214    ets:insert(T, List),
1215    ets:insert(T, List),
1216    ListSize = ets:info(T, size),
1217
1218    %% Insert different sized objects to better test (compressed) object comparison
1219    List2 = [begin Bits=(N rem 71), {N div 7, <<N:Bits>>} end || {N} <- List],
1220    ets:insert(T, List2),
1221    List2Sz = ListSize * 2,
1222    List2Sz = ets:info(T, size),
1223    ets:delete(T),
1224    ok.
1225
1226% Insert a long list twice in a duplicate_bag
1227t_insert_list_duplicate_bag(Config) when is_list(Config) ->
1228    EtsMem = etsmem(),
1229    T = ets:new(t, [duplicate_bag]),
1230    ListSize = 25000,
1231    List = [ {N} || N <- lists:seq(1, ListSize)],
1232    ets:insert(T, List),
1233    ets:insert(T, List),
1234    DoubleListSize = ListSize * 2,
1235    DoubleListSize = ets:info(T, size),
1236    ets:delete(T),
1237    verify_etsmem(EtsMem).
1238
1239%% Test ets:insert/2 with list of objects into set tables.
1240t_insert_list_set(Config) when is_list(Config) ->
1241    EtsMem = etsmem(),
1242    repeat_for_opts(fun t_insert_list_set_do/1, [set_types]),
1243    verify_etsmem(EtsMem).
1244
1245t_insert_list_set_do(Opts) ->
1246    Nr = 2,
1247    t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr, 1, Nr+1),
1248    t_insert_list_set_do(Opts, fun ets_insert_with_check/2, Nr*2, 2, Nr*2),
1249    InsertNewWithCheck =
1250        fun(T,E) ->
1251                Res = ets:insert_new(T,E),
1252                Seq = element(1, lists:nth(1, E)),
1253                case Seq rem 2 =:= 0 of
1254                    true -> Res = false;
1255                    false -> Res = true
1256                end
1257        end,
1258    t_insert_list_set_do(Opts, InsertNewWithCheck, Nr, 1, Nr),
1259    t_insert_list_set_do(Opts, fun ets:insert_new/2, Nr*2, 2, Nr*2),
1260    ok.
1261
1262t_insert_list_set_do(Opts, InsertFun, Nr, Step, ExpectedSize) ->
1263    T = ets_new(x,Opts),
1264    [InsertFun(T,[{X,X}, {X+1,X}]) || X <- lists:seq(1,Nr,Step)],
1265    ExpectedSize = ets:info(T,size),
1266    ets:delete(T).
1267
1268%% Test ets:insert/2 with list of objects into set tables in parallel.
1269t_insert_list_parallel(Config) when is_list(Config) ->
1270    EtsMem = etsmem(),
1271    repeat_for_opts(fun t_insert_list_parallel_do/1, [[public], set_types]),
1272    verify_etsmem(EtsMem).
1273
1274ets_insert_with_check(Table, ToInsert) ->
1275    true = ets:insert(Table, ToInsert),
1276    true.
1277
1278ets_insert_new_with_check(Table, ToInsert) ->
1279    ExpectedRes =
1280        case put(is_first_insert_for_list, true) of
1281            undefined -> true;
1282            true -> false
1283        end,
1284    ExpectedRes = ets:insert_new(Table, ToInsert),
1285    ExpectedRes.
1286
1287t_insert_list_parallel_do(Opts) ->
1288    [(fun(I) ->
1289             t_insert_list_parallel_do(Opts, I, 2, 100, 500),
1290             t_insert_list_parallel_do(Opts, I, 10, 100, 100),
1291             t_insert_list_parallel_do(Opts, I, 1000, 100, 10),
1292             t_insert_list_parallel_do(Opts, I, 50000, 3, 1)
1293      end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
1294                                       fun ets_insert_new_with_check/2]].
1295
1296t_insert_list_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
1297    T = ets_new(x,Opts),
1298    t_insert_list_parallel_do_helper(self(), T, 0, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess),
1299    receive done -> ok end,
1300    ExpectedSize = ListLength * NrOfProcesses,
1301    ExpectedSize = length(ets:match_object(T, {'$0', '$1'})),
1302    ExpectedSize = ets:info(T, size),
1303    ets:delete(T),
1304    ok.
1305
1306t_insert_list_delete_parallel(Config) when is_list(Config) ->
1307    EtsMem = etsmem(),
1308    repeat_for_opts(fun t_insert_list_delete_parallel_do/1, [[public], set_types]),
1309    verify_etsmem(EtsMem).
1310
1311t_insert_list_delete_parallel_do(Opts) ->
1312    [(fun(I) ->
1313              t_insert_list_delete_parallel_do(Opts, I, 30, 32, 1000000),
1314              t_insert_list_delete_parallel_do(Opts, I, 300, 8, 1000000),
1315              t_insert_list_delete_parallel_do(Opts, I, 3000, 4, 1000000),
1316              t_insert_list_delete_parallel_do(Opts, I, 9000, 4, 1000000)
1317      end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
1318                                       fun ets_insert_new_with_check/2]],
1319    ok.
1320
1321t_insert_list_delete_parallel_do(Opts, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
1322    T = ets_new(x,Opts),
1323    CompletedInsertsCtr = counters:new(1,[]),
1324    NewInsertFun =
1325        fun(Table, ToInsert) ->
1326                try
1327                    InsertFun(Table, ToInsert),
1328                    counters:add(CompletedInsertsCtr, 1, 1)
1329                catch
1330                    error:badarg -> put(stop,yes)
1331                end
1332        end,
1333    Self = self(),
1334    spawn(fun()->
1335                  t_insert_list_parallel_do_helper(self(), T, 0, NewInsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess),
1336                  receive done -> Self ! done_parallel_insert end
1337          end),
1338    receive after 3 -> ok end,
1339    spawn(fun()->
1340                  spawn(fun()->
1341                                receive after 7 -> ok end,
1342                                ets:delete(T),
1343                                Self ! done_delete
1344                        end)
1345          end),
1346    receive done_delete -> ok end,
1347    receive done_parallel_insert -> ok end,
1348    io:format("~p/~p completed",
1349              [counters:get(CompletedInsertsCtr, 1),
1350               NrOfProcesses * NrOfInsertsPerProcess]).
1351
1352
1353t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, 1, NrOfInsertsPerProcess) ->
1354    try
1355        repeat(fun()->
1356                       case get(stop) of
1357                           yes -> throw(end_repeat);
1358                           _ -> ok
1359                       end,
1360                       InsertFun(T,[{X,X} || X <- lists:seq(StartKey,StartKey+ListLength-1,1)])
1361               end, NrOfInsertsPerProcess)
1362    catch
1363        throw:end_repeat -> ok
1364    end,
1365    Parent ! done;
1366t_insert_list_parallel_do_helper(Parent, T, StartKey, InsertFun, ListLength, NrOfProcesses, NrOfInsertsPerProcess) ->
1367    Self = self(),
1368    spawn(fun() ->
1369                  t_insert_list_parallel_do_helper(Self,
1370                                                   T,
1371                                                   StartKey,
1372                                                   InsertFun,
1373                                                   ListLength,
1374                                                   NrOfProcesses div 2,
1375                                                   NrOfInsertsPerProcess) end),
1376    spawn(fun() ->
1377                  t_insert_list_parallel_do_helper(Self,
1378                                                   T,
1379                                                   StartKey + ListLength*(NrOfProcesses div 2),
1380                                                   InsertFun,
1381                                                   ListLength,
1382                                                   (NrOfProcesses div 2) + (NrOfProcesses rem 2),
1383                                                   NrOfInsertsPerProcess)
1384          end),
1385    receive done -> ok end,
1386    receive done -> ok end,
1387    Parent ! done.
1388
1389t_insert_list_delete_set(Config) when is_list(Config) ->
1390    EtsMem = etsmem(),
1391    repeat_for_opts(fun t_insert_list_delete_set_do/1, [[public],set_types]),
1392    verify_etsmem(EtsMem).
1393
1394t_insert_list_delete_set_do(Opts) ->
1395    [(fun(I) ->
1396              t_insert_list_delete_set_do(Opts, I, 1000000, 1, 1),
1397              t_insert_list_delete_set_do(Opts, I, 100000, 10, 5),
1398              t_insert_list_delete_set_do(Opts, I, 10000, 100, 50),
1399              t_insert_list_delete_set_do(Opts, I, 1000, 1000, 500)
1400      end)(InsertFun) || InsertFun <- [fun ets_insert_with_check/2,
1401                                       fun ets_insert_new_with_check/2]],
1402    ok.
1403
1404
1405t_insert_list_delete_set_do(Opts, InsertFun, ListLength, NrOfTables, NrOfInserts) ->
1406    CompletedInsertsCtr = counters:new(1,[]),
1407    Parent = self(),
1408    [(fun() ->
1409              T = ets_new(x,Opts),
1410              spawn(
1411                fun() ->
1412                        try
1413                            repeat(
1414                              fun() ->
1415                                      InsertFun(T,[{Z,Z} ||
1416                                                      Z <- lists:seq(1,ListLength)]),
1417                                      counters:add(CompletedInsertsCtr, 1, 1)%,
1418                              end, NrOfInserts)
1419                        catch
1420                            error:badarg -> ok
1421                        end,
1422                        Parent ! done
1423                end),
1424              receive after 1 -> ok end,
1425              ets:delete(T)
1426      end)() || _ <- lists:seq(1,NrOfTables)],
1427    [receive done -> ok end || _ <- lists:seq(1,NrOfTables)],
1428    io:format("~p/~p completed",
1429              [counters:get(CompletedInsertsCtr, 1),
1430               NrOfTables * NrOfInserts]).
1431
1432
1433t_insert_list_kill_process(Config) when is_list(Config) ->
1434    EtsMem = etsmem(),
1435    repeat_for_opts(fun t_insert_list_kill_process_do/1, [[public], set_types]),
1436    verify_etsmem(EtsMem).
1437
1438
1439t_insert_list_kill_process_do(Opts) ->
1440    [(fun(I) ->
1441              [(fun(Time) ->
1442                        T = ets_new(x,Opts),
1443                        List = lists:seq(1,600000),
1444                        TupleList = [{E,E} || E <- List],
1445                        Pid = spawn(fun() -> I(T, TupleList) end),
1446                        receive after Time -> ok end,
1447                        exit(Pid, kill),
1448                        ets:delete(T)
1449                end)(TheTime) || TheTime <- [1,3,5] ++ lists:seq(7,29,7)]
1450      end)(InsertFun) || InsertFun <- [fun ets:insert/2,
1451                                       fun ets:insert_new/2]],
1452    ok.
1453
1454%% Test interface of ets:test_ms/2.
1455t_test_ms(Config) when is_list(Config) ->
1456    EtsMem = etsmem(),
1457    {ok,[a,b]} = ets:test_ms({a,b},
1458			     [{{'$1','$2'},[{'<','$1','$2'}],['$$']}]),
1459    {ok,false} = ets:test_ms({a,b},
1460			     [{{'$1','$2'},[{'>','$1','$2'}],['$$']}]),
1461    Tpl = {a,gb_sets:new()},
1462    {ok,Tpl} = ets:test_ms(Tpl, [{{'_','_'},  [], ['$_']}]), % OTP-10190
1463    {error,[{error,String}]} = ets:test_ms({a,b},
1464					   [{{'$1','$2'},
1465					     [{'flurp','$1','$2'}],
1466					     ['$$']}]),
1467    true = (if is_list(String) -> true; true -> false end),
1468    verify_etsmem(EtsMem).
1469
1470%% Test the select reverse BIFs.
1471t_select_reverse(Config) when is_list(Config) ->
1472    Table = ets_new(xxx, [ordered_set]),
1473    filltabint(Table,1000),
1474    A = lists:reverse(ets:select(Table,[{{'$1', '_'},
1475					 [{'>',
1476					   {'rem',
1477					    '$1', 5},
1478					   2}],
1479					 ['$_']}])),
1480    A = ets:select_reverse(Table,[{{'$1', '_'},
1481				   [{'>',
1482				     {'rem',
1483				      '$1', 5},
1484				     2}],
1485				   ['$_']}]),
1486    A = reverse_chunked(Table,[{{'$1', '_'},
1487				[{'>',
1488				  {'rem',
1489				   '$1', 5},
1490				  2}],
1491				['$_']}],3),
1492    %% A set/bag/duplicate_bag should get the same result regardless
1493    %% of select or select_reverse
1494    Table2 = ets_new(xxx, [set]),
1495    filltabint(Table2,1000),
1496    Table3 = ets_new(xxx, [bag]),
1497    filltabint(Table3,1000),
1498    Table4 = ets_new(xxx, [duplicate_bag]),
1499    filltabint(Table4,1000),
1500    lists:map(fun(Tab) ->
1501		      B = ets:select(Tab,[{{'$1', '_'},
1502					   [{'>',
1503					     {'rem',
1504					      '$1', 5},
1505					     2}],
1506					   ['$_']}]),
1507		      B = ets:select_reverse(Tab,[{{'$1', '_'},
1508						   [{'>',
1509						     {'rem',
1510						      '$1', 5},
1511						     2}],
1512						   ['$_']}])
1513	      end,[Table2, Table3, Table4]),
1514    ok.
1515
1516
1517
1518reverse_chunked(T,MS,N) ->
1519    do_reverse_chunked(ets:select_reverse(T,MS,N),[]).
1520
1521do_reverse_chunked('$end_of_table',Acc) ->
1522    lists:reverse(Acc);
1523do_reverse_chunked({L,C},Acc) ->
1524    NewAcc = lists:reverse(L)++Acc,
1525    do_reverse_chunked(ets:select_reverse(C), NewAcc).
1526
1527
1528%% Test the ets:select_delete/2 and ets:select_count/2 BIFs.
1529t_select_delete(Config) when is_list(Config) ->
1530    ct:timetrap({minutes,30}), %% valgrind needs a lot
1531    EtsMem = etsmem(),
1532    Tables = fill_sets_int(10000) ++ fill_sets_int(10000,[{write_concurrency,true}]),
1533    lists:foreach
1534      (fun(Table) ->
1535	       4000 = ets:select_count(Table,[{{'$1', '_'},
1536					       [{'>',
1537						 {'rem',
1538						  '$1', 5},
1539						 2}],
1540					       [true]}]),
1541	       4000 = ets:select_delete(Table,[{{'$1', '_'},
1542						[{'>',
1543						  {'rem',
1544						   '$1', 5},
1545						  2}],
1546						[true]}]),
1547	       check(Table,
1548		     fun({N,_}) when (N rem 5) =< 2 ->
1549			     true;
1550			(_) ->
1551			     false
1552		     end,
1553		     6000)
1554
1555       end,
1556       Tables),
1557    lists:foreach
1558      (fun(Table) ->
1559	       ets:select_delete(Table,[{'_',[],[true]}]),
1560	       xfilltabint(Table,4000),
1561	       successive_delete(Table,1,4001,bound),
1562	       0 = ets:info(Table,size),
1563	       xfilltabint(Table,4000),
1564	       successive_delete(Table,4000,0, bound),
1565	       0 = ets:info(Table,size),
1566	       xfilltabint(Table,4000),
1567	       successive_delete(Table,1,4001,unbound),
1568	       0 = ets:info(Table,size),
1569	       xfilltabint(Table,4000),
1570	       successive_delete(Table,4000,0, unbound),
1571	       0 = ets:info(Table,size)
1572
1573       end,
1574       Tables),
1575    lists:foreach
1576      (fun(Table) ->
1577	       F = case ets:info(Table,type) of
1578		       X when X == bag; X == duplicate_bag ->
1579			   2;
1580		       _ ->
1581			   1
1582		   end,
1583	       xfilltabstr(Table, 4000),
1584	       1000 = ets:select_count(Table,
1585				       [{{[$3 | '$1'], '_'},
1586					 [{'==',
1587					   {'length', '$1'},
1588					   3}],[true]}]) div F,
1589	       1000 = ets:select_delete(Table,
1590					[{{[$3 | '$1'], '_'},
1591					  [{'==',
1592					    {'length', '$1'},
1593					    3}],[true]}]) div F,
1594	       check(Table, fun({[3,_,_,_],_}) -> false;
1595			       (_) -> true
1596			    end, 3000*F),
1597	       8 = ets:select_count(Table,
1598				    [{{"7",'_'},[],[false]},
1599				     {{['_'], '_'},
1600				      [],[true]}]) div F,
1601	       8 = ets:select_delete(Table,
1602				     [{{"7",'_'},[],[false]},
1603				      {{['_'], '_'},
1604				       [],[true]}]) div F,
1605	       check(Table, fun({"7",_}) -> true;
1606			       ({[_],_}) -> false;
1607			       (_) -> true
1608			    end, 2992*F),
1609	       xfilltabstr(Table, 4000),
1610	       %% This happens to be interesting for other select types too
1611	       200 = length(ets:select(Table,
1612				       [{{[$3,'_','_'],'_'},
1613					 [],[true]},
1614					{{[$1,'_','_'],'_'},
1615					 [],[true]}])) div F,
1616	       200 = ets:select_count(Table,
1617				      [{{[$3,'_','_'],'_'},
1618					[],[true]},
1619				       {{[$1,'_','_'],'_'},
1620					[],[true]}]) div F,
1621	       200 = length(element(1,ets:select(Table,
1622						 [{{[$3,'_','_'],'_'},
1623						   [],[true]},
1624						  {{[$1,'_','_'],'_'},
1625						   [],[true]}],
1626						 1000))) div F,
1627	       200 = length(
1628		       ets:select_reverse(Table,
1629					  [{{[$3,'_','_'],'_'},
1630					    [],[true]},
1631					   {{[$1,'_','_'],'_'},
1632					    [],[true]}])) div F,
1633	       200 = length(
1634		       element(1,
1635			       ets:select_reverse
1636				 (Table,
1637				  [{{[$3,'_','_'],'_'},
1638				    [],[true]},
1639				   {{[$1,'_','_'],'_'},
1640				    [],[true]}],
1641				  1000))) div F,
1642	       200 = ets:select_delete(Table,
1643				       [{{[$3,'_','_'],'_'},
1644					 [],[true]},
1645					{{[$1,'_','_'],'_'},
1646					 [],[true]}]) div F,
1647	       0 = ets:select_count(Table,
1648				    [{{[$3,'_','_'],'_'},
1649				      [],[true]},
1650				     {{[$1,'_','_'],'_'},
1651				      [],[true]}]) div F,
1652	       check(Table, fun({[$3,_,_],_}) -> false;
1653			       ({[$1,_,_],_}) -> false;
1654			       (_) -> true
1655			    end, 3800*F)
1656       end,
1657       Tables),
1658    lists:foreach(fun(Tab) -> ets:delete(Tab) end,Tables),
1659    verify_etsmem(EtsMem).
1660
1661%% Tests the ets:select_replace/2 BIF
1662t_select_replace(Config) when is_list(Config) ->
1663    EtsMem = etsmem(),
1664    repeat_for_opts(fun do_select_replace/1),
1665    verify_etsmem(EtsMem).
1666
1667do_select_replace(Opts) ->
1668    Tables = fill_sets_intup(10000, Opts),
1669
1670    TestFun = fun (Table, TableType) when TableType =:= bag ->
1671                      % Operation not supported; bag implementation
1672                      % presented both semantic consistency and performance issues.
1673                      10000 = ets:select_delete(Table, [{'_',[],[true]}]);
1674
1675                  (Table, TableType) ->
1676                      % Invalid replacement doesn't keep the key
1677                      MatchSpec1 = [{{{'$1','$3'}, '$2'},
1678                                     [{'=:=', {'band', '$1', 2#11}, 2#11},
1679                                      {'=/=', {'hd', '$2'}, $x}],
1680                                     [{{{{'$2','$3'}}, '$1'}}]}],
1681                      {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec1)),
1682
1683                      % Invalid replacement doesn't keep the key (even though it would be the same value)
1684                      MatchSpec2 = [{{{'$1','$3'}, '$2'},
1685                                     [{'=:=', {'band', '$1', 2#11}, 2#11}],
1686                                     [{{{{{'+', '$1', 0},'$3'}}, '$2'}}]},
1687                                    {{{'$1','$3'}, '$2'},
1688                                     [{'=/=', {'band', '$1', 2#11}, 2#11}],
1689                                     [{{{{{'-', '$1', 0},'$3'}}, '$2'}}]}],
1690                      {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec2)),
1691
1692                      % Invalid replacement changes key to float equivalent
1693                      MatchSpec3 = [{{{'$1','$3'}, '$2'},
1694                                     [{'=:=', {'band', '$1', 2#11}, 2#11},
1695                                      {'=/=', {'hd', '$2'}, $x}],
1696                                     [{{{{{'*', '$1', 1.0},'$3'}}, '$2'}}]}],
1697                      {'EXIT',{badarg,_}} = (catch ets:select_replace(Table, MatchSpec3)),
1698
1699                      % Replacements are differently-sized tuples
1700                      MatchSpec4_A = [{{{'$1','$3'},'$2'},
1701                                       [{'<', {'rem', '$1', 5}, 2}],
1702                                       [{{{{'$1','$3'}}, [$x | '$2'], stuff}}]}],
1703                      MatchSpec4_B = [{{{'$1','$3'},'$2','_'},
1704                                       [],
1705                                       [{{{{'$1','$3'}},'$2'}}]}],
1706                      4000 = ets:select_replace(Table, MatchSpec4_A),
1707                      4000 = ets:select_replace(Table, MatchSpec4_B),
1708
1709                      % Replacement is the same tuple
1710                      MatchSpec5 = [{{{'$1','$3'}, '$2'},
1711                                     [{'>', {'rem', '$1', 5}, 3}],
1712                                     ['$_']}],
1713                      2000 = ets:select_replace(Table, MatchSpec5),
1714
1715                      % Replacement reconstructs an equal tuple
1716                      MatchSpec6 = [{{{'$1','$3'}, '$2'},
1717                                     [{'>', {'rem', '$1', 5}, 3}],
1718                                     [{{{{'$1','$3'}}, '$2'}}]}],
1719                      2000 = ets:select_replace(Table, MatchSpec6),
1720
1721                      % Replacement uses {element,KeyPos,T} for key
1722                      2000 = ets:select_replace(Table,
1723                                                [{{{'$1','$3'}, '$2'},
1724                                                  [{'>', {'rem', '$1', 5}, 3}],
1725                                                  [{{{element, 1, '$_'}, '$2'}}]}]),
1726
1727                      % Replacement uses wrong {element,KeyPos,T} for key
1728                      {'EXIT',{badarg,_}} = (catch ets:select_replace(Table,
1729                                                                     [{{{'$1','$3'}, '$2'},
1730                                                                       [],
1731                                                                       [{{{element, 2, '$_'}, '$2'}}]}])),
1732
1733                      check(Table,
1734                            fun ({{N,_}, [$x, C | _]}) when ((N rem 5) < 2) -> (C >= $0) andalso (C =< $9);
1735                                ({{N,_}, [C | _]}) when is_float(N) -> (C >= $0) andalso (C =< $9);
1736                                ({{N,_}, [C | _]}) when ((N rem 5) > 3) -> (C >= $0) andalso (C =< $9);
1737                                ({_, [C | _]}) -> (C >= $0) andalso (C =< $9)
1738                            end,
1739                            10000),
1740
1741                      % Replace unbound range (>)
1742                      MatchSpec7 = [{{{'$1','$3'}, '$2'},
1743                                     [{'>', '$1', 7000}],
1744                                     [{{{{'$1','$3'}}, {{gt_range, '$2'}}}}]}],
1745                      3000 = ets:select_replace(Table, MatchSpec7),
1746
1747                      % Replace unbound range (<)
1748                      MatchSpec8 = [{{{'$1','$3'}, '$2'},
1749                                     [{'<', '$1', 3000}],
1750                                     [{{{{'$1','$3'}}, {{le_range, '$2'}}}}]}],
1751                      case TableType of
1752                          ordered_set ->   2999 = ets:select_replace(Table, MatchSpec8);
1753                          set ->           2999 = ets:select_replace(Table, MatchSpec8);
1754                          duplicate_bag -> 2998 = ets:select_replace(Table, MatchSpec8)
1755                      end,
1756
1757                      % Replace bound range
1758                      MatchSpec9 = [{{{'$1','$3'}, '$2'},
1759                                     [{'>=', '$1', 3001},
1760                                      {'<', '$1', 7000}],
1761                                     [{{{{'$1','$3'}}, {{range, '$2'}}}}]}],
1762                      case TableType of
1763                          ordered_set ->   3999 = ets:select_replace(Table, MatchSpec9);
1764                          set ->           3999 = ets:select_replace(Table, MatchSpec9);
1765                          duplicate_bag -> 3998 = ets:select_replace(Table, MatchSpec9)
1766                      end,
1767
1768                      % Replace particular keys
1769                      MatchSpec10 = [{{{'$1','$3'}, '$2'},
1770                                     [{'==', '$1', 3000}],
1771                                     [{{{{'$1','$3'}}, {{specific1, '$2'}}}}]},
1772                                    {{{'$1','$3'}, '$2'},
1773                                     [{'==', '$1', 7000}],
1774                                     [{{{{'$1','$3'}}, {{specific2, '$2'}}}}]}],
1775                      case TableType of
1776                          ordered_set ->   2 = ets:select_replace(Table, MatchSpec10);
1777                          set ->           2 = ets:select_replace(Table, MatchSpec10);
1778                          duplicate_bag -> 4 = ets:select_replace(Table, MatchSpec10)
1779                      end,
1780
1781                      check(Table,
1782                            fun ({{N,_}, {gt_range, _}}) -> N > 7000;
1783                                ({{N,_}, {le_range, _}}) -> N < 3000;
1784                                ({{N,_}, {range, _}}) -> (N >= 3001) andalso (N < 7000);
1785                                ({{N,_}, {specific1, _}}) -> N == 3000;
1786                                ({{N,_}, {specific2, _}}) -> N == 7000
1787                            end,
1788                            10000),
1789
1790                      10000 = ets:select_delete(Table, [{'_',[],[true]}]),
1791                      check(Table, fun (_) -> false end, 0)
1792              end,
1793
1794    lists:foreach(
1795      fun(Table) ->
1796              TestFun(Table, ets:info(Table, type)),
1797              ets:delete(Table)
1798      end,
1799      Tables),
1800
1801    %% Test key-safe match-specs are accepted
1802    BigNum = (123 bsl 123),
1803    RefcBin = list_to_binary(lists:seq(1,?heap_binary_size+1)),
1804    Terms = [a, "hej", 123, 1.23, BigNum , <<"123">>, RefcBin, TestFun, self()],
1805    EqPairs = fun(X,Y) ->
1806                      [{ '$1', '$1'},
1807                       { {X, Y}, {{X, Y}}},
1808                       { {'$1', Y}, {{'$1', Y}}},
1809                       { {{X, Y}}, {{{{X, Y}}}}},
1810                       { {X}, {{X}}},
1811                       { X, {const, X}},
1812                       { {X,Y}, {const, {X,Y}}},
1813                       { {X}, {const, {X}}},
1814                       { {X, Y}, {{X, {const, Y}}}},
1815                       { {X, {Y,'$1'}}, {{{const, X}, {{Y,'$1'}}}}},
1816                       { [X, Y | '$1'], [X, Y | '$1']},
1817                       { [{X, '$1'}, Y], [{{X, '$1'}}, Y]},
1818                       { [{X, Y} | '$1'], [{const, {X, Y}} | '$1']},
1819                       { [$p,$r,$e,$f,$i,$x | '$1'], [$p,$r,$e,$f,$i,$x | '$1']},
1820                       { {[{X,Y}]}, {{[{{X,Y}}]}}},
1821                       { {[{X,Y}]}, {{{const, [{X,Y}]}}}},
1822                       { {[{X,Y}]}, {{[{const,{X,Y}}]}}}
1823                      ]
1824              end,
1825
1826    T2 = ets:new(x, Opts),
1827    [lists:foreach(fun({A, B}) ->
1828                           %% just check that matchspec is accepted
1829                           0 = ets:select_replace(T2, [{{A, '$2', '$3'}, [], [{{B, '$3', '$2'}}]}])
1830                   end,
1831                   EqPairs(X,Y)) || X <- Terms, Y <- Terms],
1832
1833    %% Test key-unsafe matchspecs are rejected
1834    NeqPairs = fun(X, Y) ->
1835                      [{'$1', '$2'},
1836                       {{X, Y}, {X, Y}},
1837                       {{{X, Y}}, {{{X, Y}}}},
1838                       {{X}, {{{X}}}},
1839                       {{const, X}, {const, X}},
1840                       {{const, {X,Y}}, {const, {X,Y}}},
1841                       {'$1', {const, '$1'}},
1842                       {{X}, {const, {{X}}}},
1843                       {{X, {Y,'$1'}}, {{{const, X}, {Y,'$1'}}}},
1844                       {[X, Y | '$1'], [X, Y]},
1845                       {[X, Y], [X, Y | '$1']},
1846                       {[{X, '$1'}, Y], [{X, '$1'}, Y]},
1847                       {[$p,$r,$e,$f,$i,$x | '$1'], [$p,$r,$e,$f,$I,$x | '$1']},
1848                       { {[{X,Y}]}, {{[{X,Y}]}}},
1849                       { {[{X,Y}]}, {{{const, [{{X,Y}}]}}}},
1850                       { {[{X,Y}]}, {{[{const,{{X,Y}}}]}}},
1851                       {'_', '_'},
1852                       {'$_', '$_'},
1853                       {'$$', '$$'},
1854                       {#{}, #{}},
1855                       {#{X => '$1'}, #{X => '$1'}}
1856                      ]
1857              end,
1858
1859    [lists:foreach(fun({A, B}) ->
1860                           %% just check that matchspec is rejected
1861                           {'EXIT',{badarg,_}} = (catch ets:select_replace(T2, [{{A, '$2', '$3'}, [], [{{B, '$3', '$2'}}]}]))
1862                   end,
1863                   NeqPairs(X,Y)) || X <- Terms, Y <- Terms],
1864
1865
1866    %% Wrap entire tuple with 'const'
1867    [[begin
1868          Old = {Key, 1, 2},
1869          ets:insert(T2, Old),
1870          1 = ets:select_replace(T2, [{Old, [], [{const, New}]}]),
1871          [New] = ets:lookup(T2, Key),
1872          ets:delete(T2, Key)
1873      end || New <- [{Key, 1, 2}, {Key, 3, 4}, {Key, 1}, {Key, 1, 2, 3}, {Key}]
1874     ]
1875     || Key <- [{1, tuple}, {nested, {tuple, {a,b}}} | Terms]],
1876
1877    %% 'const' wrap does not work with maps or variables in keys
1878    [[begin
1879          Old = {Key, 1, 2},
1880          {'EXIT',{badarg,_}} = (catch ets:select_replace(T2, [{Old, [], [{const, New}]}]))
1881      end || New <- [{Key, 1, 2}, {Key, 3, 4}, {Key, 1}, {Key, 1, 2, 3}, {Key}]
1882     ]
1883     || Key <- [#{a => 1}, {nested, #{a => 1}}, '$1']],
1884
1885
1886    ets:delete(T2),
1887    ok.
1888
1889%% OTP-15346: Bug caused select_replace of bound key to corrupt static stack
1890%% used by ets:next and ets:prev.
1891t_select_replace_next_bug(Config) when is_list(Config) ->
1892    T = ets:new(k, [ordered_set]),
1893    [ets:insert(T, {I, value}) || I <- lists:seq(1,10)],
1894    1 = ets:first(T),
1895
1896    %% Make sure select_replace does not leave pointer
1897    %% to deallocated {2,value} in static stack.
1898    MS = [{{2,value}, [], [{{2,"new_value"}}]}],
1899    1 = ets:select_replace(T, MS),
1900
1901    %% This would crash or give wrong result at least on DEBUG emulator
1902    %% where deallocated memory is overwritten.
1903    2 = ets:next(T, 1),
1904
1905    ets:delete(T).
1906
1907
1908%% OTP-17379
1909t_select_pam_stack_overflow_bug(Config) ->
1910    T = ets:new(k, []),
1911    ets:insert(T,[{x,17}]),
1912    [{x,18}] = ets:select(T,[{{x,17}, [], [{{{element,1,'$_'},{const,18}}}]}]),
1913    ets:delete(T),
1914    ok.
1915
1916
1917%% Test that partly bound keys gives faster matches.
1918partly_bound(Config) when is_list(Config) ->
1919    case os:type() of
1920	{win32,_} ->
1921	    {skip,"Inaccurate measurements on Windows"};
1922	_ ->
1923	    EtsMem = etsmem(),
1924	    dont_make_worse(),
1925	    make_better(),
1926	    verify_etsmem(EtsMem)
1927    end.
1928
1929dont_make_worse() ->
1930    seventyfive_percent_success(fun dont_make_worse_sub/0, 0, 0, 10).
1931
1932dont_make_worse_sub() ->
1933    T = build_table([a,b],[a,b],15000),
1934    T1 = time_match_object(T,{'_',a,a,1500}, [{{a,a,1500},a,a,1500}]),
1935    T2 = time_match_object(T,{{a,a,'_'},a,a,1500},
1936			   [{{a,a,1500},a,a,1500}]),
1937    ets:delete(T),
1938    true = (T1 > T2),
1939    ok.
1940
1941make_better() ->
1942    fifty_percent_success(fun make_better_sub2/0, 0, 0, 10),
1943    fifty_percent_success(fun make_better_sub1/0, 0, 0, 10).
1944
1945make_better_sub1() ->
1946    T = build_table2([a,b],[a,b],15000),
1947    T1 = time_match_object(T,{'_',1500,a,a}, [{{1500,a,a},1500,a,a}]),
1948    T2 = time_match_object(T,{{1500,a,'_'},1500,a,a},
1949			   [{{1500,a,a},1500,a,a}]),
1950    ets:delete(T),
1951    io:format("~p>~p~n",[(T1 / 100),T2]),
1952    true = ((T1 / 100) > T2), % More marginal than needed.
1953    ok.
1954
1955make_better_sub2() ->
1956    T = build_table2([a,b],[a,b],15000),
1957    T1 = time_match(T,{'$1',1500,a,a}),
1958    T2 = time_match(T,{{1500,a,'$1'},1500,a,a}),
1959    ets:delete(T),
1960    io:format("~p>~p~n",[(T1 / 100),T2]),
1961    true = ((T1 / 100) > T2), % More marginal than needed.
1962    ok.
1963
1964
1965%% Heavy random matching, comparing set with ordered_set.
1966match_heavy(Config) when is_list(Config) ->
1967    PrivDir = proplists:get_value(priv_dir,Config),
1968    DataDir = proplists:get_value(data_dir, Config),
1969    %% Easier to have in process dictionary when manually
1970    %% running the test function.
1971    put(where_to_read,DataDir),
1972    put(where_to_write,PrivDir),
1973    random_test(),
1974    drop_match(),
1975    ok.
1976
1977%%% Extra safety for the very low probability that this is not
1978%%% caught by the random test (Statistically impossible???)
1979drop_match() ->
1980    EtsMem = etsmem(),
1981    T = build_table([a,b],[a],1500),
1982    [{{a,a,1},a,a,1},{{b,a,1},b,a,1}] =
1983	ets:match_object(T, {'_','_','_',1}),
1984    true = ets:delete(T),
1985    verify_etsmem(EtsMem).
1986
1987
1988
1989ets_match(Tab,Expr) ->
1990    case rand:uniform(2) of
1991	1 ->
1992	    ets:match(Tab,Expr);
1993	_ ->
1994	    match_chunked(Tab,Expr)
1995    end.
1996
1997match_chunked(Tab,Expr) ->
1998    match_chunked_collect(ets:match(Tab,Expr,
1999				    rand:uniform(1999) + 1)).
2000match_chunked_collect('$end_of_table') ->
2001    [];
2002match_chunked_collect({Results, Continuation}) ->
2003    Results ++ match_chunked_collect(ets:match(Continuation)).
2004
2005ets_match_object(Tab,Expr) ->
2006    case rand:uniform(2) of
2007	1 ->
2008	    ets:match_object(Tab,Expr);
2009	_ ->
2010	    match_object_chunked(Tab,Expr)
2011    end.
2012
2013match_object_chunked(Tab,Expr) ->
2014    match_object_chunked_collect(ets:match_object(Tab,Expr,
2015						  rand:uniform(1999) + 1)).
2016match_object_chunked_collect('$end_of_table') ->
2017    [];
2018match_object_chunked_collect({Results, Continuation}) ->
2019    Results ++ match_object_chunked_collect(ets:match_object(Continuation)).
2020
2021
2022
2023random_test() ->
2024    ReadDir = get(where_to_read),
2025    WriteDir = get(where_to_write),
2026    (catch file:make_dir(WriteDir)),
2027    case file:consult(filename:join([ReadDir,"preset_random_seed.txt"])) of
2028	{ok,[X]} ->
2029	    rand:seed(X);
2030	_ ->
2031	    rand:seed(default)
2032    end,
2033    Seed = rand:export_seed(),
2034    {ok,F} = file:open(filename:join([WriteDir,"last_random_seed.txt"]),
2035		       [write]),
2036    io:format(F,"~p. ~n",[Seed]),
2037    file:close(F),
2038    io:format("Random seed ~p written to ~s, copy to ~s to rerun with "
2039	      "same seed.",[Seed,
2040			    filename:join([WriteDir, "last_random_seed.txt"]),
2041			    filename:join([ReadDir,
2042					   "preset_random_seed.txt"])]),
2043    do_random_test().
2044
2045do_random_test() ->
2046    EtsMem = etsmem(),
2047    OrdSet = ets_new(xxx,[ordered_set]),
2048    Set = ets_new(xxx,[]),
2049    do_n_times(fun() ->
2050		       Key = create_random_string(25),
2051		       Value = create_random_tuple(25),
2052		       ets:insert(OrdSet,{Key,Value}),
2053		       ets:insert(Set,{Key,Value})
2054	       end, 5000),
2055    io:format("~nData inserted~n"),
2056    do_n_times(fun() ->
2057		       I = rand:uniform(25),
2058		       Key = create_random_string(I) ++ '_',
2059		       L1 = ets_match_object(OrdSet,{Key,'_'}),
2060		       L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
2061		       case L1 == L2 of
2062			   false ->
2063			       io:format("~p != ~p~n",
2064					 [L1,L2]),
2065			       exit({not_eq, L1, L2});
2066			   true ->
2067			       ok
2068		       end
2069	       end,
2070	       2000),
2071    io:format("~nData matched~n"),
2072    ets:match_delete(OrdSet,'_'),
2073    ets:match_delete(Set,'_'),
2074    do_n_times(fun() ->
2075		       Value = create_random_string(25),
2076		       Key = create_random_tuple(25),
2077		       ets:insert(OrdSet,{Key,Value}),
2078		       ets:insert(Set,{Key,Value})
2079	       end, 2000),
2080    io:format("~nData inserted~n"),
2081    (fun() ->
2082	     Key = list_to_tuple(lists:duplicate(25,'_')),
2083	     L1 = ets_match_object(OrdSet,{Key,'_'}),
2084	     L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
2085	     2000 = length(L1),
2086	     case L1 == L2 of
2087		 false ->
2088		     io:format("~p != ~p~n",
2089			       [L1,L2]),
2090		     exit({not_eq, L1, L2});
2091		 true ->
2092		     ok
2093	     end
2094     end)(),
2095    (fun() ->
2096	     Key = {'$1','$2','$3','$4',
2097		    '$5','$6','$7','$8',
2098		    '$9','$10','$11','$12',
2099		    '$13','$14','$15','$16',
2100		    '$17','$18','$19','$20',
2101		    '$21','$22','$23','$24',
2102		    '$25'},
2103	     L1 = ets_match_object(OrdSet,{Key,'_'}),
2104	     L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
2105	     2000 = length(L1),
2106	     case L1 == L2 of
2107		 false ->
2108		     io:format("~p != ~p~n",
2109			       [L1,L2]),
2110		     exit({not_eq, L1, L2});
2111		 true ->
2112		     ok
2113	     end
2114     end)(),
2115    (fun() ->
2116	     Key = {'$1','$2','$3','$4',
2117		    '$5','$6','$7','$8',
2118		    '$9','$10','$11','$12',
2119		    '$13','$14','$15','$16',
2120		    '$17','$18','$19','$20',
2121		    '$21','$22','$23','$24',
2122		    '$25'},
2123	     L1 = ets_match(OrdSet,{Key,'_'}),
2124	     L2 = lists:sort(ets_match(Set,{Key,'_'})),
2125	     2000 = length(L1),
2126	     case L1 == L2 of
2127		 false ->
2128		     io:format("~p != ~p~n",
2129			       [L1,L2]),
2130		     exit({not_eq, L1, L2});
2131		 true ->
2132		     ok
2133	     end
2134     end)(),
2135    ets:match_delete(OrdSet,'_'),
2136    ets:match_delete(Set,'_'),
2137    do_n_times(fun() ->
2138		       Value = create_random_string(25),
2139		       Key = create_random_tuple(25),
2140		       ets:insert(OrdSet,{Key,Value}),
2141		       ets:insert(Set,{Key,Value})
2142	       end, 2000),
2143    io:format("~nData inserted~n"),
2144    do_n_times(fun() ->
2145		       Key = create_partly_bound_tuple(25),
2146		       L1 = ets_match_object(OrdSet,{Key,'_'}),
2147		       L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
2148		       case L1 == L2 of
2149			   false ->
2150			       io:format("~p != ~p~n",
2151					 [L1,L2]),
2152			       exit({not_eq, L1, L2});
2153			   true ->
2154			       ok
2155		       end
2156	       end,
2157	       2000),
2158    do_n_times(fun() ->
2159		       Key = create_partly_bound_tuple2(25),
2160		       L1 = ets_match_object(OrdSet,{Key,'_'}),
2161		       L2 = lists:sort(ets_match_object(Set,{Key,'_'})),
2162		       case L1 == L2 of
2163			   false ->
2164			       io:format("~p != ~p~n",
2165					 [L1,L2]),
2166			       exit({not_eq, L1, L2});
2167			   true ->
2168			       ok
2169		       end
2170	       end,
2171	       2000),
2172    do_n_times(fun() ->
2173		       Key = create_partly_bound_tuple2(25),
2174		       L1 = ets_match(OrdSet,{Key,'_'}),
2175		       L2 = lists:sort(ets_match(Set,{Key,'_'})),
2176		       case L1 == L2 of
2177			   false ->
2178			       io:format("~p != ~p~n",
2179					 [L1,L2]),
2180			       exit({not_eq, L1, L2});
2181			   true ->
2182			       ok
2183		       end
2184	       end,
2185	       2000),
2186    io:format("~nData matched~n"),
2187    ets:match_delete(OrdSet,'_'),
2188    ets:match_delete(Set,'_'),
2189    do_n_times(fun() ->
2190		       do_n_times(fun() ->
2191					  Value =
2192					      create_random_string(25),
2193					  Key = create_random_tuple(25),
2194					  ets:insert(OrdSet,{Key,Value}),
2195					  ets:insert(Set,{Key,Value})
2196				  end, 500),
2197		       io:format("~nData inserted~n"),
2198		       do_n_times(fun() ->
2199					  Key =
2200					      create_partly_bound_tuple(25),
2201					  ets:match_delete(OrdSet,{Key,'_'}),
2202					  ets:match_delete(Set,{Key,'_'}),
2203					  L1 = ets:info(OrdSet,size),
2204					  L2 = ets:info(Set,size),
2205					  [] = ets_match_object(OrdSet,
2206								{Key,'_'}),
2207					  case L1 == L2 of
2208					      false ->
2209						  io:format("~p != ~p "
2210							    "(deleted ~p)~n",
2211							    [L1,L2,Key]),
2212						  exit({not_eq, L1, L2,
2213							{deleted,Key}});
2214					      true ->
2215						  ok
2216					  end
2217				  end,
2218				  50),
2219		       io:format("~nData deleted~n")
2220	       end,
2221	       10),
2222    ets:delete(OrdSet),
2223    ets:delete(Set),
2224    verify_etsmem(EtsMem).
2225
2226%% Test various variants of update_element.
2227update_element(Config) when is_list(Config) ->
2228    EtsMem = etsmem(),
2229    repeat_for_opts(fun update_element_opts/1),
2230    verify_etsmem(EtsMem).
2231
2232update_element_opts(Opts) ->
2233    TupleCases = [{{key,val}, 1 ,2},
2234		  {{val,key}, 2, 1},
2235		  {{key,val}, 1 ,[2]},
2236		  {{key,val,val}, 1, [2,3]},
2237		  {{val,key,val,val}, 2, [3,4,1]},
2238		  {{val,val,key,val}, 3, [1,4,1,2]}, % update pos1 twice
2239		  {{val,val,val,key}, 4, [2,1,2,3]}],% update pos2 twice
2240
2241    lists:foreach(fun({Tuple,KeyPos,UpdPos}) -> update_element_opts(Tuple,KeyPos,UpdPos,Opts) end,
2242		  TupleCases),
2243
2244    update_element_neg(Opts).
2245
2246
2247
2248update_element_opts(Tuple,KeyPos,UpdPos,Opts) ->
2249    Set = ets_new(set,[{keypos,KeyPos} | Opts]),
2250    OrdSet = ets_new(ordered_set,[ordered_set,{keypos,KeyPos} | Opts]),
2251    update_element(Set,Tuple,KeyPos,UpdPos),
2252    update_element(OrdSet,Tuple,KeyPos,UpdPos),
2253    true = ets:delete(Set),
2254    true = ets:delete(OrdSet),
2255    ok.
2256
2257update_element(T,Tuple,KeyPos,UpdPos) ->
2258    KeyList = [17,"seventeen",<<"seventeen">>,{17},list_to_binary(lists:seq(1,100)),make_ref(), self()],
2259    lists:foreach(fun(Key) ->
2260			  TupleWithKey = setelement(KeyPos,Tuple,Key),
2261			  update_element_do(T,TupleWithKey,Key,UpdPos)
2262		  end,
2263		  KeyList).
2264
2265update_element_do(Tab,Tuple,Key,UpdPos) ->
2266
2267    %% Strategy: Step around in Values array and call ets:update_element for the values.
2268    %% Take Length number of steps of size 1, then of size 2, ..., Length-1.
2269    %% This will try all combinations of {fromValue,toValue}
2270    %%
2271    %% IMPORTANT: size(Values) must be a prime number for this to work!!!
2272
2273    Big32 = 16#12345678,
2274    Big64 = 16#123456789abcdef0,
2275    Values = { 623, -27, 0, Big32, -Big32, Big64, -Big64, Big32*Big32,
2276	       -Big32*Big32, Big32*Big64, -Big32*Big64, Big64*Big64, -Big64*Big64,
2277	       "A", "Sverker", [], {12,-132}, {},
2278	       <<45,232,0,12,133>>, <<234,12,23>>, list_to_binary(lists:seq(1,100)),
2279	       (fun(X) -> X*Big32 end),
2280	       make_ref(), make_ref(), self(), ok, update_element, 28, 29 },
2281    Length = size(Values),
2282
2283    PosValArgF = fun(ToIx, ResList, [Pos | PosTail], Rand, MeF) ->
2284			 NextIx = (ToIx+Rand) rem Length,
2285			 MeF(NextIx, [{Pos,element(ToIx+1,Values)} | ResList], PosTail, Rand, MeF);
2286
2287		    (_ToIx, ResList, [], _Rand, _MeF) ->
2288			 ResList;
2289
2290		    (ToIx, [], Pos, _Rand, _MeF) ->
2291			 {Pos, element(ToIx+1,Values)}   % single {pos,value} arg
2292		 end,
2293
2294    UpdateF = fun(ToIx,Rand) ->
2295                      PosValArg = PosValArgF(ToIx,[],UpdPos,Rand,PosValArgF),
2296                      %%io:format("update_element(~p)~n",[PosValArg]),
2297                      ArgHash = erlang:phash2({Tab,Key,PosValArg}),
2298                      true = ets:update_element(Tab, Key, PosValArg),
2299                      ArgHash = erlang:phash2({Tab,Key,PosValArg}),
2300                      NewTuple = update_tuple(PosValArg,Tuple),
2301                      [NewTuple] = ets:lookup(Tab,Key)
2302	      end,
2303
2304    LoopF = fun(_FromIx, Incr, _Times, Checksum, _MeF) when Incr >= Length ->
2305		    Checksum; % done
2306
2307	       (FromIx, Incr, 0, Checksum, MeF) ->
2308		    MeF(FromIx, Incr+1, Length, Checksum, MeF);
2309
2310	       (FromIx, Incr, Times, Checksum, MeF) ->
2311		    ToIx = (FromIx + Incr) rem Length,
2312		    UpdateF(ToIx,Checksum),
2313		    if
2314			Incr =:= 0 -> UpdateF(ToIx,Checksum);  % extra update to same value
2315			true -> true
2316		    end,
2317		    MeF(ToIx, Incr, Times-1, Checksum+ToIx+1, MeF)
2318	    end,
2319
2320    FirstTuple = Tuple,
2321    true = ets:insert(Tab,FirstTuple),
2322    [FirstTuple] = ets:lookup(Tab,Key),
2323
2324    Checksum = LoopF(0, 1, Length, 0, LoopF),
2325    Checksum = (Length-1)*Length*(Length+1) div 2,  % if Length is a prime
2326    ok.
2327
2328update_tuple({Pos,Val}, Tpl) ->
2329    setelement(Pos, Tpl, Val);
2330update_tuple([{Pos,Val} | Tail], Tpl) ->
2331    update_tuple(Tail,setelement(Pos, Tpl, Val));
2332update_tuple([], Tpl) ->
2333    Tpl.
2334
2335
2336
2337update_element_neg(Opts) ->
2338    Set = ets_new(set,Opts),
2339    OrdSet = ets_new(ordered_set,[ordered_set | Opts]),
2340    update_element_neg_do(Set),
2341    update_element_neg_do(OrdSet),
2342    ets:delete(Set),
2343    {'EXIT',{badarg,_}} = (catch ets:update_element(Set,key,{2,1})),
2344    ets:delete(OrdSet),
2345    {'EXIT',{badarg,_}} = (catch ets:update_element(OrdSet,key,{2,1})),
2346
2347    Bag = ets_new(bag,[bag | Opts]),
2348    DBag = ets_new(duplicate_bag,[duplicate_bag | Opts]),
2349    {'EXIT',{badarg,_}} = (catch ets:update_element(Bag,key,{2,1})),
2350    {'EXIT',{badarg,_}} = (catch ets:update_element(DBag,key,{2,1})),
2351    true = ets:delete(Bag),
2352    true = ets:delete(DBag),
2353    ok.
2354
2355
2356update_element_neg_do(T) ->
2357    Object = {key, 0, "Hej"},
2358    true = ets:insert(T,Object),
2359
2360    UpdateF = fun(Arg3) ->
2361		      ArgHash = erlang:phash2({T,key,Arg3}),
2362		      {'EXIT',{badarg,_}} = (catch ets:update_element(T,key,Arg3)),
2363		      ArgHash = erlang:phash2({T,key,Arg3}),
2364		      [Object] = ets:lookup(T,key)
2365	      end,
2366
2367    %% List of invalid {Pos,Value} tuples
2368    InvList = [false, {2}, {2,1,false}, {false,1}, {0,1}, {1,1}, {-1,1}, {4,1}],
2369
2370    lists:foreach(UpdateF, InvList),
2371    lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl]) end, InvList),
2372    lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{2,1}]) end, InvList),
2373    lists:foreach(fun(InvTpl) -> UpdateF([{2,1},{3,"Hello"},InvTpl]) end, InvList),
2374    lists:foreach(fun(InvTpl) -> UpdateF([{3,"Hello"},{2,1},InvTpl]) end, InvList),
2375    lists:foreach(fun(InvTpl) -> UpdateF([{2,1},InvTpl,{3,"Hello"}]) end, InvList),
2376    lists:foreach(fun(InvTpl) -> UpdateF([InvTpl,{3,"Hello"},{2,1}]) end, InvList),
2377    UpdateF([{2,1} | {3,1}]),
2378    lists:foreach(fun(InvTpl) -> UpdateF([{2,1} | InvTpl]) end, InvList),
2379
2380    true = ets:update_element(T,key,[]),
2381    false = ets:update_element(T,false,[]),
2382    false = ets:update_element(T,false,{2,1}),
2383    ets:delete(T,key),
2384    false = ets:update_element(T,key,{2,1}),
2385    ok.
2386
2387
2388%% test various variants of update_counter.
2389update_counter(Config) when is_list(Config) ->
2390    EtsMem = etsmem(),
2391    repeat_for_opts(fun update_counter_do/1),
2392    verify_etsmem(EtsMem).
2393
2394update_counter_do(Opts) ->
2395    Set = ets_new(set,Opts),
2396    OrdSet = ets_new(ordered_set,[ordered_set | Opts]),
2397    update_counter_for(Set),
2398    update_counter_for(OrdSet),
2399    ets:delete_all_objects(Set),
2400    ets:delete_all_objects(OrdSet),
2401    ets:safe_fixtable(Set, true),
2402    ets:safe_fixtable(OrdSet, true),
2403    update_counter_for(Set),
2404    update_counter_for(OrdSet),
2405    ets:safe_fixtable(Set, false),
2406    ets:safe_fixtable(OrdSet, false),
2407    ets:delete(Set),
2408    ets:delete(OrdSet),
2409    update_counter_neg(Opts).
2410
2411update_counter_for(T) ->
2412    ets:insert(T,{a,1,1}),
2413    101 = ets:update_counter(T,a,100),
2414    [{a,101,1}] = ets:lookup(T,a),
2415    101 = ets:update_counter(T,a,{3,100}),
2416    [{a,101,101}] = ets:lookup(T,a),
2417
2418
2419    LooperF = fun(Obj, 0, _, _) ->
2420		      Obj;
2421
2422		 (Obj, Times, Arg3, Myself) ->
2423		      {NewObj, Ret} = uc_mimic(Obj,Arg3),
2424		      ArgHash = erlang:phash2({T,a,Arg3}),
2425		      %%io:format("update_counter(~p, ~p, ~p) expecting ~p\n",[T,a,Arg3,Ret]),
2426                      [DefaultObj] = ets:lookup(T, a),
2427		      Ret = ets:update_counter(T,a,Arg3),
2428                      Ret = ets:update_counter(T, b, Arg3, DefaultObj),   % Use other key
2429		      ArgHash = erlang:phash2({T,a,Arg3}),
2430		      %%io:format("NewObj=~p~n ",[NewObj]),
2431		      [NewObj] = ets:lookup(T,a),
2432                      true = ets:lookup(T, b) =:= [setelement(1, NewObj, b)],
2433                      ets:delete(T, b),
2434		      Myself(NewObj,Times-1,Arg3,Myself)
2435	      end,
2436
2437    LoopF = fun(Obj, Times, Arg3) ->
2438		    %%io:format("Loop start:\nObj = ~p\nArg3=~p\n",[Obj,Arg3]),
2439		    LooperF(Obj,Times,Arg3,LooperF)
2440	    end,
2441
2442    SmallMax32 = (1 bsl 27) - 1,
2443    SmallMax64 = (1 bsl (27+32)) - 1,
2444    Big1Max32 = (1 bsl 32) - 1,
2445    Big1Max64 = (1 bsl 64) - 1,
2446
2447    Steps = 100,
2448    Obj0 = {a,0,0,0,0},
2449    ets:insert(T,Obj0),
2450    Obj1 = LoopF(Obj0, Steps, {2,(SmallMax32 div Steps)*2}),
2451    Obj2 = LoopF(Obj1, Steps, {3,(SmallMax64 div Steps)*2}),
2452    Obj3 = LoopF(Obj2, Steps, {4,(Big1Max32 div Steps)*2}),
2453    Obj4 = LoopF(Obj3, Steps, {5,(Big1Max64 div Steps)*2}),
2454
2455    Obj5 = LoopF(Obj4, Steps, {2,-(SmallMax32 div Steps)*4}),
2456    Obj6 = LoopF(Obj5, Steps, {3,-(SmallMax64 div Steps)*4}),
2457    Obj7 = LoopF(Obj6, Steps, {4,-(Big1Max32 div Steps)*4}),
2458    Obj8 = LoopF(Obj7, Steps, {5,-(Big1Max64 div Steps)*4}),
2459
2460    Obj9 = LoopF(Obj8, Steps, {2,(SmallMax32 div Steps)*2}),
2461    ObjA = LoopF(Obj9, Steps, {3,(SmallMax64 div Steps)*2}),
2462    ObjB = LoopF(ObjA, Steps, {4,(Big1Max32 div Steps)*2}),
2463    Obj0 = LoopF(ObjB, Steps, {5,(Big1Max64 div Steps)*2}),
2464
2465    %% back at zero, same trip again with lists
2466
2467    Obj4 = LoopF(Obj0,Steps,[{2, (SmallMax32 div Steps)*2},
2468			     {3, (SmallMax64 div Steps)*2},
2469			     {4, (Big1Max32 div Steps)*2},
2470			     {5, (Big1Max64 div Steps)*2}]),
2471
2472    Obj8 = LoopF(Obj4,Steps,[{4, -(Big1Max32 div Steps)*4},
2473			     {2, -(SmallMax32 div Steps)*4},
2474			     {5, -(Big1Max64 div Steps)*4},
2475			     {3, -(SmallMax64 div Steps)*4}]),
2476
2477    Obj0 = LoopF(Obj8,Steps,[{5, (Big1Max64 div Steps)*2},
2478			     {2, (SmallMax32 div Steps)*2},
2479			     {4, (Big1Max32 div Steps)*2},
2480			     {3, (SmallMax64 div Steps)*2}]),
2481
2482    %% make them shift size at the same time
2483    ObjC = LoopF(Obj0,Steps,[{5, (Big1Max64 div Steps)*2},
2484			     {3, (Big1Max64 div Steps)*2 + 1},
2485			     {2, -(Big1Max64 div Steps)*2},
2486			     {4, -(Big1Max64 div Steps)*2 + 1}]),
2487
2488    %% update twice in same list
2489    ObjD = LoopF(ObjC,Steps,[{5, -(Big1Max64 div Steps) + 1},
2490			     {3, -(Big1Max64 div Steps)*2 - 1},
2491			     {5, -(Big1Max64 div Steps) - 1},
2492			     {4, (Big1Max64 div Steps)*2 - 1}]),
2493
2494    Obj0 = LoopF(ObjD,Steps,[{2, (Big1Max64 div Steps) - 1},
2495			     {4, Big1Max64*2},
2496			     {2, (Big1Max64 div Steps) + 1},
2497			     {4, -Big1Max64*2}]),
2498
2499    %% warping with list
2500    ObjE = LoopF(Obj0,1000,
2501		 [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
2502		  {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
2503		  {4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2},
2504		  {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
2505
2506    %% warping without list
2507    ObjF = LoopF(ObjE,1000,{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2}),
2508    ObjG = LoopF(ObjF,1000,{5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2}),
2509    ObjH = LoopF(ObjG,1000,{4,-Big1Max32*4 div 11,-Big1Max32*2,Big1Max32*2}),
2510    ObjI = LoopF(ObjH,1000,{2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}),
2511
2512    %% mixing it up
2513    LoopF(ObjI,1000,
2514	  [{3,SmallMax32*4 div 5,SmallMax32*2,-SmallMax32*2},
2515	   {5,-SmallMax64*4 div 3},
2516	   {3,-SmallMax32*4 div 11},
2517	   {5,0},
2518	   {4,1},
2519	   {5,-SmallMax64*4 div 7,-SmallMax64*2,SmallMax64*2},
2520	   {2,Big1Max64*4 div 13,Big1Max64*2,-Big1Max64*2}]),
2521    ok.
2522
2523%% uc_mimic works kind of like the real ets:update_counter
2524%% Obj = Tuple in ets
2525%% Pits = {Pos,Incr} | {Pos,Incr,Thres,Warp}
2526%% Returns {Updated tuple in ets, Return value from update_counter}
2527uc_mimic(Obj, Pits) when is_tuple(Pits) ->
2528    Pos = element(1,Pits),
2529    NewObj = setelement(Pos, Obj, uc_adder(element(Pos,Obj),Pits)),
2530    {NewObj, element(Pos,NewObj)};
2531
2532uc_mimic(Obj, PitsList) when is_list(PitsList) ->
2533    {NewObj,ValList} = uc_mimic(Obj,PitsList,[]),
2534    {NewObj,lists:reverse(ValList)}.
2535
2536uc_mimic(Obj, [], Acc) ->
2537    {Obj,Acc};
2538uc_mimic(Obj, [Pits|Tail], Acc) ->
2539    {NewObj,NewVal} = uc_mimic(Obj,Pits),
2540    uc_mimic(NewObj,Tail,[NewVal|Acc]).
2541
2542uc_adder(Init, {_Pos, Add}) ->
2543    Init + Add;
2544uc_adder(Init, {_Pos, Add, Thres, Warp}) ->
2545    case Init + Add of
2546	X when X > Thres, Add > 0 ->
2547	    Warp;
2548	Y when Y < Thres, Add < 0 ->
2549	    Warp;
2550	Z ->
2551	    Z
2552    end.
2553
2554update_counter_neg(Opts) ->
2555    Set = ets_new(set,Opts),
2556    OrdSet = ets_new(ordered_set,[ordered_set | Opts]),
2557    update_counter_neg_for(Set),
2558    update_counter_neg_for(OrdSet),
2559    ets:delete(Set),
2560    {'EXIT',{badarg,_}} = (catch ets:update_counter(Set,key,1)),
2561    ets:delete(OrdSet),
2562    {'EXIT',{badarg,_}} = (catch ets:update_counter(OrdSet,key,1)),
2563
2564    Bag = ets_new(bag,[bag | Opts]),
2565    DBag = ets_new(duplicate_bag,[duplicate_bag | Opts]),
2566    {'EXIT',{badarg,_}} = (catch ets:update_counter(Bag,key,1)),
2567    {'EXIT',{badarg,_}} = (catch ets:update_counter(DBag,key,1)),
2568    true = ets:delete(Bag),
2569    true = ets:delete(DBag),
2570    ok.
2571
2572update_counter_neg_for(T) ->
2573    Object = {key,0,false,1},
2574    true = ets:insert(T,Object),
2575
2576    UpdateF = fun(Arg3) ->
2577		      ArgHash = erlang:phash2({T,key,Arg3}),
2578		      {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,Arg3)),
2579		      ArgHash = erlang:phash2({T,key,Arg3}),
2580		      [Object] = ets:lookup(T,key)
2581	      end,
2582
2583    %% List of invalid arg3-tuples
2584    InvList = [false, {2}, {2,false}, {false,1},
2585	       {0,1}, {-1,1}, % BUG < R12B-2
2586	       {1,1}, {3,1}, {5,1}, {2,1,100}, {2,1,100,0,false}, {2,1,false,0}, {2,1,0,false}],
2587
2588    lists:foreach(UpdateF, InvList),
2589    lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv]) end, InvList),
2590    lists:foreach(fun(Inv) -> UpdateF([Inv,{2,1}]) end, InvList),
2591    lists:foreach(fun(Inv) -> UpdateF([{2,1},{4,-100},Inv]) end, InvList),
2592    lists:foreach(fun(Inv) -> UpdateF([{4,100,50,0},{2,1},Inv]) end, InvList),
2593    lists:foreach(fun(Inv) -> UpdateF([{2,1},Inv,{4,100,50,0}]) end, InvList),
2594    lists:foreach(fun(Inv) -> UpdateF([Inv,{4,100,50,0},{2,1}]) end, InvList),
2595    UpdateF([{2,1} | {4,1}]),
2596    lists:foreach(fun(Inv) -> UpdateF([{2,1} | Inv]) end, InvList),
2597
2598    {'EXIT',{badarg,_}} = (catch ets:update_counter(T,false,1)),
2599    ets:delete(T,key),
2600    {'EXIT',{badarg,_}} = (catch ets:update_counter(T,key,1)),
2601    ok.
2602
2603
2604evil_update_counter(Config) when is_list(Config) ->
2605    %% The code server uses ets table. Pre-load modules that might not be
2606    %% already loaded.
2607    gb_sets:module_info(),
2608    math:module_info(),
2609    ordsets:module_info(),
2610    rand:module_info(),
2611
2612    repeat_for_opts(fun evil_update_counter_do/1).
2613
2614evil_update_counter_do(Opts) ->
2615    EtsMem = etsmem(),
2616    process_flag(trap_exit, true),
2617    Pids = [my_spawn_link(fun() -> evil_counter(I,Opts) end)  || I <- lists:seq(1, 40)],
2618    wait_for_all(gb_sets:from_list(Pids)),
2619    verify_etsmem(EtsMem),
2620    ok.
2621
2622wait_for_all(Pids0) ->
2623    case gb_sets:is_empty(Pids0) of
2624	true ->
2625	    ok;
2626	false ->
2627	    receive
2628		{'EXIT',Pid,normal} ->
2629		    Pids = gb_sets:delete(Pid, Pids0),
2630		    wait_for_all(Pids);
2631		Other ->
2632		    io:format("unexpected: ~p\n", [Other]),
2633		    ct:fail(failed)
2634	    end
2635    end.
2636
2637evil_counter(I,Opts) ->
2638    T = ets_new(a, Opts),
2639    Start0 = case I rem 3 of
2640		 0 -> 16#12345678;
2641		 1 -> 16#12345678FFFFFFFF;
2642		 2 -> 16#7777777777FFFFFFFF863648726743
2643	     end,
2644    Start = Start0 + rand:uniform(100000),
2645    ets:insert(T, {dracula,Start}),
2646    Iter = 40000 div syrup_factor(),
2647    End = Start + Iter,
2648    End = evil_counter_1(Iter, T),
2649    ets:delete(T).
2650
2651evil_counter_1(0, T) ->
2652    [{dracula,Count}] = ets:lookup(T, dracula),
2653    Count;
2654evil_counter_1(Iter, T) ->
2655    ets:update_counter(T, dracula, 1),
2656    evil_counter_1(Iter-1, T).
2657
2658update_counter_with_default(Config) when is_list(Config) ->
2659    repeat_for_opts(fun update_counter_with_default_do/1).
2660
2661update_counter_with_default_do(Opts) ->
2662    T1 = ets_new(a, [set | Opts]),
2663    %% Insert default object.
2664    3 = ets:update_counter(T1, foo, 2, {beaufort,1}),
2665    1 = ets:info(T1, size),
2666    %% Increment.
2667    5 = ets:update_counter(T1, foo, 2, {cabecou,1}),
2668    1 = ets:info(T1, size),
2669    %% Increment with list.
2670    [9] = ets:update_counter(T1, foo, [{2,4}], {camembert,1}),
2671    1 = ets:info(T1, size),
2672    %% Same with non-immediate key.
2673    3 = ets:update_counter(T1, {foo,bar}, 2, {{chaource,chevrotin},1}),
2674    2 = ets:info(T1, size),
2675    5 = ets:update_counter(T1, {foo,bar}, 2, {{cantal,comté},1}),
2676    2 = ets:info(T1, size),
2677    [9] = ets:update_counter(T1, {foo,bar}, [{2,4}], {{emmental,de,savoie},1}),
2678    2 = ets:info(T1, size),
2679    %% default counter is not an integer.
2680    {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, 3, {saint,félicien})),
2681    2 = ets:info(T1, size),
2682    %% No third element in default value.
2683    {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, [{3,1}], {roquefort,1})),
2684    2 = ets:info(T1, size),
2685
2686    %% Same with ordered set.
2687    T2 = ets_new(b, [ordered_set | Opts]),
2688    3 = ets:update_counter(T2, foo, 2, {maroilles,1}),
2689    1 = ets:info(T2, size),
2690    5 = ets:update_counter(T2, foo, 2, {mimolette,1}),
2691    1 = ets:info(T2, size),
2692    [9] = ets:update_counter(T2, foo, [{2,4}], {morbier,1}),
2693    1 = ets:info(T2, size),
2694    3 = ets:update_counter(T2, {foo,bar}, 2, {{laguiole},1}),
2695    2 = ets:info(T2, size),
2696    5 = ets:update_counter(T2, {foo,bar}, 2, {{saint,nectaire},1}),
2697    2 = ets:info(T2, size),
2698    [9] = ets:update_counter(T2, {foo,bar}, [{2,4}], {{rocamadour},1}),
2699    2 = ets:info(T2, size),
2700    %% Arithmetically-equal keys.
2701    3 = ets:update_counter(T2, 1.0, 2, {1,1}),
2702    3 = ets:info(T2, size),
2703    5 = ets:update_counter(T2, 1, 2, {1,1}),
2704    3 = ets:info(T2, size),
2705    7 = ets:update_counter(T2, 1, 2, {1.0,1}),
2706    3 = ets:info(T2, size),
2707    %% Same with reversed type difference.
2708    3 = ets:update_counter(T2, 2, 2, {2.0,1}),
2709    4 = ets:info(T2, size),
2710    5 = ets:update_counter(T2, 2.0, 2, {2.0,1}),
2711    4 = ets:info(T2, size),
2712    7 = ets:update_counter(T2, 2.0, 2, {2,1}),
2713    4 = ets:info(T2, size),
2714    %% default counter is not an integer.
2715    {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, 3, {saint,félicien})),
2716    4 = ets:info(T2, size),
2717    %% No third element in default value.
2718    {'EXIT',{badarg,_}} = (catch ets:update_counter(T1, qux, [{3,1}], {roquefort,1})),
2719    4 = ets:info(T2, size),
2720    ok.
2721
2722%% ERL-1125
2723update_counter_with_default_bad_pos(Config) when is_list(Config) ->
2724    repeat_for_all_ord_set_table_types(fun update_counter_with_default_bad_pos_do/1).
2725
2726update_counter_with_default_bad_pos_do(Opts) ->
2727    T = ets_new(a, Opts),
2728    0 = ets:info(T, size),
2729    ok = try ets:update_counter(T, 101065, {1, 1}, {101065, 0})
2730         catch
2731             error:badarg -> ok;
2732             Class:Reason -> {Class, Reason}
2733         end,
2734    0 = ets:info(T, size),
2735    ok.
2736
2737update_counter_table_growth(_Config) ->
2738    repeat_for_opts(fun update_counter_table_growth_do/1).
2739
2740update_counter_table_growth_do(Opts) ->
2741    Set = ets_new(b, [set | Opts]),
2742    [ets:update_counter(Set, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)],
2743    OrderedSet = ets_new(b, [ordered_set | Opts]),
2744    [ets:update_counter(OrderedSet, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)],
2745    ok.
2746
2747%% Check that a first-next sequence always works on a fixed table.
2748fixtable_next(Config) when is_list(Config) ->
2749    repeat_for_opts(fun fixtable_next_do/1,
2750                    [write_concurrency,all_types]).
2751
2752fixtable_next_do(Opts) ->
2753    EtsMem = etsmem(),
2754    do_fixtable_next(ets_new(set,[public | Opts])),
2755    verify_etsmem(EtsMem).
2756
2757do_fixtable_next(Tab) ->
2758    F = fun(X,T,FF) ->
2759                case X of
2760                    0 -> true;
2761                    _ ->
2762                        ets:insert(T, {X,
2763                                       integer_to_list(X),
2764                                       X rem 10}),
2765                        FF(X-1,T,FF)
2766                end
2767        end,
2768    F(100,Tab,F),
2769    ets:safe_fixtable(Tab,true),
2770    First = ets:first(Tab),
2771    ets:delete(Tab, First),
2772    ets:next(Tab, First),
2773    ets:match_delete(Tab,{'_','_','_'}),
2774    '$end_of_table' = ets:next(Tab, First),
2775    true = ets:info(Tab, fixed),
2776    ets:safe_fixtable(Tab, false),
2777    false = ets:info(Tab, fixed),
2778    ets:delete(Tab).
2779
2780%% Check that iteration of bags find all live objects and nothing else.
2781fixtable_iter_bag(Config) when is_list(Config) ->
2782    repeat_for_opts(fun fixtable_iter_do/1,
2783                    [write_concurrency,[bag,duplicate_bag]]).
2784
2785fixtable_iter_do(Opts) ->
2786    EtsMem = etsmem(),
2787    do_fixtable_iter_bag(ets_new(fixtable_iter_bag,Opts)),
2788    verify_etsmem(EtsMem).
2789
2790do_fixtable_iter_bag(T) ->
2791    MaxValues = 4,
2792    %% Create 1 to MaxValues objects for each key
2793    %% and then delete every possible combination of those objects
2794    %% in every possible order.
2795    %% Then test iteration returns all live objects and nothing else.
2796
2797    CrDelOps = [begin
2798                    Values = lists:seq(1,N),
2799                    %% All ways of deleting any number of the Values in any order
2800                    Combos = combs(Values),
2801                    DeleteOps = concat_lists([perms(C) || C <- Combos]),
2802                    {N, DeleteOps}
2803                end
2804                || N <- lists:seq(1,MaxValues)],
2805
2806    %%io:format("~p\n", [CrDelOps]),
2807
2808    NKeys = lists:foldl(fun({_, DeleteOps}, Cnt) ->
2809                               Cnt + length(DeleteOps)
2810                       end,
2811                       0,
2812                       CrDelOps),
2813
2814    io:format("Create ~p keys\n", [NKeys]),
2815
2816    %% Fixate even before inserts just to maintain small table size
2817    %% and increase likelyhood of different keys in same bucket.
2818    ets:safe_fixtable(T,true),
2819    InsRes = [begin
2820                  [begin
2821                       Key = {NValues,ValueList},
2822                       [begin
2823                            Tpl = {Key, V},
2824                            %%io:format("Insert object ~p", [Tpl]),
2825                            ets:insert(T, Tpl),
2826                            Tpl
2827                        end
2828                        || V <- lists:seq(1,NValues)]
2829                   end
2830                   || ValueList <- DeleteOps]
2831              end
2832              || {NValues, DeleteOps} <- CrDelOps],
2833
2834    Inserted = lists:flatten(InsRes),
2835    InSorted = lists:sort(Inserted),
2836    InSorted = lists:usort(Inserted),  %% No duplicates
2837    NObjs = length(Inserted),
2838
2839    DelRes = [begin
2840                  [begin
2841                       Key = {NValues,ValueList},
2842                       [begin
2843                            Tpl = {Key, V},
2844                            %%io:format("Delete object ~p", [Tpl]),
2845                            ets:delete_object(T, Tpl),
2846                            Tpl
2847                        end
2848                        || V <- ValueList]
2849                   end
2850                   || ValueList <- DeleteOps]
2851              end
2852              || {NValues, DeleteOps} <- CrDelOps],
2853
2854    Deleted = lists:flatten(DelRes),
2855    DelSorted = lists:sort(Deleted),
2856    DelSorted = lists:usort(Deleted),  %% No duplicates
2857    NDels = length(Deleted),
2858
2859    %% Nr of keys where all values were deleted.
2860    NDeletedKeys = lists:sum([factorial(N) || N <- lists:seq(1,MaxValues)]),
2861
2862    CountKeysFun = fun Me(K1, Cnt) ->
2863                           case ets:next(T, K1) of
2864                               '$end_of_table' ->
2865                                   Cnt;
2866                               K2 ->
2867                                   Objs = ets:lookup(T, K2),
2868                                   [{{NValues, ValueList}, _V} | _] = Objs,
2869                                   ExpectedLive = NValues - length(ValueList),
2870                                   ExpectedLive = length(Objs),
2871                                   Me(K2, Cnt+1)
2872                           end
2873                   end,
2874
2875    ExpectedKeys = NKeys - NDeletedKeys,
2876    io:format("Expected keys: ~p\n", [ExpectedKeys]),
2877    FoundKeys = CountKeysFun(ets:first(T), 1),
2878    io:format("Found keys: ~p\n", [FoundKeys]),
2879    ExpectedKeys = FoundKeys,
2880
2881    ExpectedObjs = NObjs - NDels,
2882    io:format("Expected objects: ~p\n", [ExpectedObjs]),
2883    FoundObjs = ets:select_count(T, [{{'_','_'}, [], [true]}]),
2884    io:format("Found objects: ~p\n", [FoundObjs]),
2885    ExpectedObjs = FoundObjs,
2886
2887    ets:delete(T).
2888
2889%% All permutations of list
2890perms([]) -> [[]];
2891perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].
2892
2893%% All combinations of picking the element (or not) from list
2894combs([]) -> [[]];
2895combs([H|T]) ->
2896    Tcombs = combs(T),
2897    Tcombs ++ [[H | C] || C <- Tcombs].
2898
2899factorial(0) -> 1;
2900factorial(N) when N > 0 ->
2901    N * factorial(N - 1).
2902
2903concat_lists([]) ->
2904    [];
2905concat_lists([H|T]) ->
2906    H ++ concat_lists(T).
2907
2908
2909%% Check inserts of deleted keys in fixed bags.
2910fixtable_insert(Config) when is_list(Config) ->
2911    Combos = [[Type,{write_concurrency,WC}] || Type<- [bag,duplicate_bag],
2912					       WC <- [false,true]],
2913    lists:foreach(fun(Opts) -> fixtable_insert_do(Opts) end,
2914		  Combos),
2915    ok.
2916
2917fixtable_insert_do(Opts) ->
2918    io:format("Opts = ~p\n",[Opts]),
2919    Ets = make_table(ets, Opts, [{a,1}, {a,2}, {b,1}, {b,2}]),
2920    ets:safe_fixtable(Ets,true),
2921    ets:match_delete(Ets,{b,1}),
2922    First = ets:first(Ets),
2923    Next = case First of
2924	       a -> b;
2925	       b -> a
2926	   end,
2927    Next = ets:next(Ets,First),
2928    ets:delete(Ets,Next),
2929    '$end_of_table' = ets:next(Ets,First),
2930    ets:insert(Ets, {Next,1}),
2931    false = ets:insert_new(Ets, {Next,1}),
2932    Next = ets:next(Ets,First),
2933    '$end_of_table' = ets:next(Ets,Next),
2934    ets:delete(Ets,Next),
2935    '$end_of_table' = ets:next(Ets,First),
2936    ets:insert(Ets, {Next,2}),
2937    false = ets:insert_new(Ets, {Next,1}),
2938    Next = ets:next(Ets,First),
2939    '$end_of_table' = ets:next(Ets,Next),
2940    ets:delete(Ets,First),
2941    Next = ets:first(Ets),
2942    '$end_of_table' = ets:next(Ets,Next),
2943    ets:delete(Ets,Next),
2944    '$end_of_table' = ets:next(Ets,First),
2945    true = ets:insert_new(Ets,{Next,1}),
2946    false = ets:insert_new(Ets,{Next,2}),
2947    Next = ets:next(Ets,First),
2948    ets:delete_object(Ets,{Next,1}),
2949    '$end_of_table' = ets:next(Ets,First),
2950    true = ets:insert_new(Ets,{Next,2}),
2951    false = ets:insert_new(Ets,{Next,1}),
2952    Next = ets:next(Ets,First),
2953    ets:delete(Ets,First),
2954    ets:safe_fixtable(Ets,false),
2955    {'EXIT',{badarg,_}} = (catch ets:next(Ets,First)),
2956    ok.
2957
2958%% Test the 'write_concurrency' option.
2959write_concurrency(Config) when is_list(Config) ->
2960    EtsMem = etsmem(),
2961    Yes1 = ets_new(foo,[public,{write_concurrency,true}]),
2962    Yes2 = ets_new(foo,[protected,{write_concurrency,true}]),
2963    No1 = ets_new(foo,[private,{write_concurrency,true}]),
2964
2965    Yes3 = ets_new(foo,[bag,public,{write_concurrency,true}]),
2966    Yes4 = ets_new(foo,[bag,protected,{write_concurrency,true}]),
2967    No2 = ets_new(foo,[bag,private,{write_concurrency,true}]),
2968
2969    Yes5 = ets_new(foo,[duplicate_bag,public,{write_concurrency,true}]),
2970    Yes6 = ets_new(foo,[duplicate_bag,protected,{write_concurrency,true}]),
2971    No3 = ets_new(foo,[duplicate_bag,private,{write_concurrency,true}]),
2972
2973    NoCentCtrs = {decentralized_counters,false},
2974    Yes7 = ets_new(foo,[ordered_set,public,{write_concurrency,true},NoCentCtrs]),
2975    Yes8 = ets_new(foo,[ordered_set,protected,{write_concurrency,true},NoCentCtrs]),
2976    Yes9 = ets_new(foo,[ordered_set,{write_concurrency,true},NoCentCtrs]),
2977    Yes10 = ets_new(foo,[{write_concurrency,true},ordered_set,public,NoCentCtrs]),
2978    Yes11 = ets_new(foo,[{write_concurrency,true},ordered_set,protected,NoCentCtrs]),
2979    Yes12 = ets_new(foo,[set,{write_concurrency,false},
2980                         {write_concurrency,true},ordered_set,public,NoCentCtrs]),
2981    Yes13 = ets_new(foo,[private,public,set,{write_concurrency,false},
2982                         {write_concurrency,true},ordered_set,NoCentCtrs]),
2983    Yes14 = ets_new(foo,[ordered_set,public,{write_concurrency,true}]),
2984    No4 = ets_new(foo,[ordered_set,private,{write_concurrency,true}]),
2985    No5 = ets_new(foo,[ordered_set,public,{write_concurrency,false}]),
2986    No6 = ets_new(foo,[ordered_set,protected,{write_concurrency,false}]),
2987    No7 = ets_new(foo,[ordered_set,private,{write_concurrency,false}]),
2988
2989    No8 = ets_new(foo,[public,{write_concurrency,false}]),
2990    No9 = ets_new(foo,[protected,{write_concurrency,false}]),
2991
2992    YesMem = ets:info(Yes1,memory),
2993    NoHashMem = ets:info(No1,memory),
2994    YesTreeMem = ets:info(Yes7,memory),
2995    YesYesTreeMem = ets:info(Yes14,memory),
2996    NoTreeMem = ets:info(No4,memory),
2997    io:format("YesMem=~p NoHashMem=~p NoTreeMem=~p YesTreeMem=~p\n",[YesMem,NoHashMem,
2998                                                                     NoTreeMem,YesTreeMem]),
2999
3000    YesMem = ets:info(Yes2,memory),
3001    YesMem = ets:info(Yes3,memory),
3002    YesMem = ets:info(Yes4,memory),
3003    YesMem = ets:info(Yes5,memory),
3004    YesMem = ets:info(Yes6,memory),
3005    NoHashMem = ets:info(No2,memory),
3006    NoHashMem = ets:info(No3,memory),
3007    YesTreeMem = ets:info(Yes7,memory),
3008    YesTreeMem = ets:info(Yes8,memory),
3009    YesTreeMem = ets:info(Yes9,memory),
3010    YesTreeMem = ets:info(Yes10,memory),
3011    YesTreeMem = ets:info(Yes11,memory),
3012    YesTreeMem = ets:info(Yes12,memory),
3013    YesTreeMem = ets:info(Yes13,memory),
3014    NoTreeMem = ets:info(No4,memory),
3015    NoTreeMem = ets:info(No5,memory),
3016    NoTreeMem = ets:info(No6,memory),
3017    NoTreeMem = ets:info(No7,memory),
3018    NoHashMem = ets:info(No8,memory),
3019    NoHashMem = ets:info(No9,memory),
3020
3021    true = YesMem > YesTreeMem,
3022
3023    case erlang:system_info(schedulers) > 1 of
3024        true ->
3025            true = YesMem > NoHashMem,
3026            true = YesMem > NoTreeMem,
3027            true = YesTreeMem < NoTreeMem,
3028            true = YesYesTreeMem > YesTreeMem;
3029        _ ->
3030            one_scheduler_only
3031    end,
3032
3033    {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,foo}])),
3034    {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency}])),
3035    {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,{write_concurrency,true,foo}])),
3036    {'EXIT',{badarg,_}} = (catch ets_new(foo,[public,write_concurrency])),
3037
3038    lists:foreach(fun(T) -> ets:delete(T) end,
3039        	  [Yes1,Yes2,Yes3,Yes4,Yes5,Yes6,Yes7,Yes8,Yes9,Yes10,Yes11,Yes12,Yes13,Yes14,
3040        	   No1,No2,No3,No4,No5,No6,No7,No8,No9]),
3041    verify_etsmem(EtsMem),
3042    ok.
3043
3044
3045%% The 'heir' option.
3046heir(Config) when is_list(Config) ->
3047    repeat_for_opts(fun heir_do/1).
3048
3049heir_do(Opts) ->
3050    EtsMem = etsmem(),
3051    Master = self(),
3052
3053    %% Different types of heir data and link/monitor relations
3054    TestFun = fun(Arg) -> {EtsMem,Arg} end,
3055    Combos = [{Data,Mode} || Data<-[foo_data, <<"binary">>,
3056				    lists:seq(1,10), {17,TestFun,self()},
3057				    "The busy heir"],
3058			     Mode<-[none,link,monitor]],
3059    lists:foreach(fun({Data,Mode})-> heir_1(Data,Mode,Opts) end,
3060		  Combos),
3061
3062    %% No heir
3063    {Founder1,MrefF1} = my_spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
3064    Founder1 ! {go, none},
3065    {"No heir",Founder1} = receive_any(),
3066    {'DOWN', MrefF1, process, Founder1, normal} = receive_any(),
3067    undefined = ets:info(foo),
3068
3069    %% An already dead heir
3070    {Heir2,MrefH2} = my_spawn_monitor(fun()->die end),
3071    {'DOWN', MrefH2, process, Heir2, normal} = receive_any(),
3072    {Founder2,MrefF2} = my_spawn_monitor(fun()->heir_founder(Master,foo_data,Opts)end),
3073    Founder2 ! {go, Heir2},
3074    {"No heir",Founder2} = receive_any(),
3075    {'DOWN', MrefF2, process, Founder2, normal} = receive_any(),
3076    undefined = ets:info(foo),
3077
3078    %% When heir dies before founder
3079    {Founder3,MrefF3} = my_spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
3080    {Heir3,MrefH3} = my_spawn_monitor(fun()->heir_heir(Founder3)end),
3081    Founder3 ! {go, Heir3},
3082    {'DOWN', MrefH3, process, Heir3, normal} = receive_any(),
3083    Founder3 ! die_please,
3084    {'DOWN', MrefF3, process, Founder3, normal} = receive_any(),
3085    undefined = ets:info(foo),
3086
3087    %% When heir dies and pid reused before founder dies
3088    repeat_while(fun() ->
3089			 NextPidIx = erts_debug:get_internal_state(next_pid),
3090			 {Founder4,MrefF4} = my_spawn_monitor(fun()->heir_founder(Master,"The dying heir",Opts)end),
3091			 {Heir4,MrefH4} = my_spawn_monitor(fun()->heir_heir(Founder4)end),
3092			 Founder4 ! {go, Heir4},
3093			 {'DOWN', MrefH4, process, Heir4, normal} = receive_any(),
3094			 erts_debug:set_internal_state(next_pid, NextPidIx),
3095			 DoppelGanger = spawn_monitor_with_pid(Heir4,
3096							       fun()-> die_please = receive_any() end),
3097			 Founder4 ! die_please,
3098			 {'DOWN', MrefF4, process, Founder4, normal} = receive_any(),
3099			 case DoppelGanger of
3100			     {Heir4,MrefH4_B} ->
3101				 Heir4 ! die_please,
3102				 {'DOWN', MrefH4_B, process, Heir4, normal} = receive_any(),
3103				 undefined = ets:info(foo),
3104				 false;
3105			     failed ->
3106				 io:format("Failed to spawn process with pid ~p\n", [Heir4]),
3107				 true % try again
3108			 end
3109		 end),
3110
3111    verify_etsmem(EtsMem).
3112
3113heir_founder(Master, HeirData, Opts) ->
3114    {go,Heir} = receive_any(),
3115    HeirTpl = case Heir of
3116		  none -> {heir,none};
3117		  _ -> {heir, Heir, HeirData}
3118	      end,
3119    T = ets_new(foo,[named_table, private, HeirTpl | Opts]),
3120    true = ets:insert(T,{key,1}),
3121    [{key,1}] = ets:lookup(T,key),
3122    Self = self(),
3123    Self = ets:info(T,owner),
3124    case ets:info(T,heir) of
3125	none ->
3126	    true = (Heir =:= none) orelse (not is_process_alive(Heir)),
3127	    Master ! {"No heir",self()};
3128
3129	Heir ->
3130	    true = is_process_alive(Heir),
3131	    Heir ! {table,T,HeirData},
3132	    die_please = receive_any()
3133    end.
3134
3135
3136heir_heir(Founder) ->
3137    heir_heir(Founder, none).
3138heir_heir(Founder, Mode) ->
3139    {table,T,HeirData} = receive_any(),
3140    {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
3141    case HeirData of
3142	"The dying heir" -> exit(normal);
3143	_ -> ok
3144    end,
3145
3146    Mref = case Mode of
3147	       link -> process_flag(trap_exit, true),
3148		       link(Founder);
3149	       monitor -> erlang:monitor(process,Founder);
3150	       none -> ok
3151	   end,
3152    Founder ! die_please,
3153    Msg = case HeirData of
3154	      "The busy heir" -> receive_any_spinning();
3155	      _ -> receive_any()
3156	  end,
3157    {'ETS-TRANSFER', T, Founder, HeirData} = Msg,
3158    foo = T,
3159    Self = self(),
3160    Self = ets:info(T,owner),
3161    Self = ets:info(T,heir),
3162    [{key,1}] = ets:lookup(T,key),
3163    true = ets:insert(T,{key,2}),
3164    [{key,2}] = ets:lookup(T,key),
3165    case Mode of % Verify that EXIT or DOWN comes after ETS-TRANSFER
3166	link ->
3167	    {'EXIT',Founder,normal} = receive_any(),
3168	    process_flag(trap_exit, false);
3169	monitor ->
3170	    {'DOWN', Mref, process, Founder, normal} = receive_any();
3171	none -> ok
3172    end.
3173
3174
3175heir_1(HeirData,Mode,Opts) ->
3176    io:format("test with heir_data = ~p\n", [HeirData]),
3177    Master = self(),
3178    Founder = my_spawn_link(fun() -> heir_founder(Master,HeirData,Opts) end),
3179    io:format("founder spawned = ~p\n", [Founder]),
3180    {Heir,Mref} = my_spawn_monitor(fun() -> heir_heir(Founder,Mode) end),
3181    io:format("heir spawned = ~p\n", [{Heir,Mref}]),
3182    Founder ! {go, Heir},
3183    {'DOWN', Mref, process, Heir, normal} = receive_any().
3184
3185%% Test ets:give_way/3.
3186give_away(Config) when is_list(Config) ->
3187    repeat_for_opts(fun give_away_do/1).
3188
3189give_away_do(Opts) ->
3190    T = ets_new(foo,[named_table, private | Opts]),
3191    true = ets:insert(T,{key,1}),
3192    [{key,1}] = ets:lookup(T,key),
3193    Parent = self(),
3194
3195    %% Give and then give back
3196    {Receiver,Mref} = my_spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
3197    give_me = receive_any(),
3198    true = ets:give_away(T,Receiver,here_you_are),
3199    {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
3200    Receiver ! give_back,
3201    {'ETS-TRANSFER',T,Receiver,"Tillbakakaka"} = receive_any(),
3202    [{key,2}] = ets:lookup(T,key),
3203    {'DOWN', Mref, process, Receiver, normal} = receive_any(),
3204
3205    %% Give and then let receiver keep it
3206    true = ets:insert(T,{key,1}),
3207    {Receiver3,Mref3} = my_spawn_monitor(fun()-> give_away_receiver(T,Parent) end),
3208    give_me = receive_any(),
3209    true = ets:give_away(T,Receiver3,here_you_are),
3210    {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
3211    Receiver3 ! die_please,
3212    {'DOWN', Mref3, process, Receiver3, normal} = receive_any(),
3213    undefined = ets:info(T),
3214
3215    %% Give and then kill receiver to get back
3216    T2 = ets_new(foo,[private | Opts]),
3217    true = ets:insert(T2,{key,1}),
3218    ets:setopts(T2,{heir,self(),"Som en gummiboll..."}),
3219    {Receiver2,Mref2} = my_spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
3220    give_me = receive_any(),
3221    true = ets:give_away(T2,Receiver2,here_you_are),
3222    {'EXIT',{badarg,_}} = (catch ets:lookup(T2,key)),
3223    Receiver2 ! die_please,
3224    {'ETS-TRANSFER',T2,Receiver2,"Som en gummiboll..."} = receive_any(),
3225    [{key,2}] = ets:lookup(T2,key),
3226    {'DOWN', Mref2, process, Receiver2, normal} = receive_any(),
3227
3228    %% Some negative testing
3229    {'EXIT',{badarg,_}} = (catch ets:give_away(T2,Receiver,"To a dead one")),
3230    {'EXIT',{badarg,_}} = (catch ets:give_away(T2,self(),"To myself")),
3231    {'EXIT',{badarg,_}} = (catch ets:give_away(T2,"not a pid","To wrong type")),
3232
3233    true = ets:delete(T2),
3234    {ReceiverNeg,MrefNeg} = my_spawn_monitor(fun()-> give_away_receiver(T2,Parent) end),
3235    give_me = receive_any(),
3236    {'EXIT',{badarg,_}} = (catch ets:give_away(T2,ReceiverNeg,"A deleted table")),
3237
3238    T3 = ets_new(foo,[public | Opts]),
3239    my_spawn_link(fun()-> {'EXIT',{badarg,_}} = (catch ets:give_away(T3,ReceiverNeg,"From non owner")),
3240			  Parent ! done
3241		  end),
3242    done = receive_any(),
3243    ReceiverNeg ! no_soup_for_you,
3244    {'DOWN', MrefNeg, process, ReceiverNeg, normal} = receive_any(),
3245    ok.
3246
3247give_away_receiver(T, Giver) ->
3248    {'EXIT',{badarg,_}} = (catch ets:lookup(T,key)),
3249    Giver ! give_me,
3250    case receive_any() of
3251	{'ETS-TRANSFER',T,Giver,here_you_are} ->
3252	    [{key,1}] = ets:lookup(T,key),
3253	    true = ets:insert(T,{key,2}),
3254	    case receive_any() of
3255		give_back ->
3256		    true = ets:give_away(T,Giver,"Tillbakakaka"),
3257		    {'EXIT',{badarg,_}} = (catch ets:lookup(T,key));
3258		die_please ->
3259		    ok
3260	    end;
3261	no_soup_for_you ->
3262	    ok
3263    end.
3264
3265
3266%% Test ets:setopts/2.
3267setopts(Config) when is_list(Config) ->
3268    repeat_for_opts(fun setopts_do/1, [write_concurrency,all_types]).
3269
3270setopts_do(Opts) ->
3271    Self = self(),
3272    T = ets_new(foo,[named_table, private | Opts]),
3273    none = ets:info(T,heir),
3274    Heir = my_spawn_link(fun()->heir_heir(Self) end),
3275    ets:setopts(T,{heir,Heir,"Data"}),
3276    Heir = ets:info(T,heir),
3277    ets:setopts(T,{heir,self(),"Data"}),
3278    Self = ets:info(T,heir),
3279    ets:setopts(T,[{heir,Heir,"Data"}]),
3280    Heir = ets:info(T,heir),
3281    ets:setopts(T,[{heir,none}]),
3282    none = ets:info(T,heir),
3283
3284    {'EXIT',{badarg,_}} = (catch ets:setopts(T,[{heir,self(),"Data"},false])),
3285    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,self()})),
3286    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false})),
3287    {'EXIT',{badarg,_}} = (catch ets:setopts(T,heir)),
3288    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{heir,false,"Data"})),
3289    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{false,self(),"Data"})),
3290
3291    ets:setopts(T,{protection,protected}),
3292    ets:setopts(T,{protection,public}),
3293    ets:setopts(T,{protection,private}),
3294    ets:setopts(T,[{protection,protected}]),
3295    ets:setopts(T,[{protection,public}]),
3296    ets:setopts(T,[{protection,private}]),
3297
3298    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection})),
3299    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,false})),
3300    {'EXIT',{badarg,_}} = (catch ets:setopts(T,{protection,private,false})),
3301    {'EXIT',{badarg,_}} = (catch ets:setopts(T,protection)),
3302    ets:delete(T),
3303    unlink(Heir),
3304    exit(Heir, bang),
3305    ok.
3306
3307%% All kinds of operations with bad table argument.
3308bad_table(Config) when is_list(Config) ->
3309
3310    %% Open and close disk_log to stabilize etsmem.
3311    Name = make_ref(),
3312    File = filename:join([proplists:get_value(priv_dir, Config),"bad_table.dummy"]),
3313    {ok, Name} = disk_log:open([{name, Name}, {file, File}]),
3314    disk_log:close(Name),
3315    file:delete(File),
3316
3317    EtsMem = etsmem(),
3318
3319    repeat_for_opts(fun(Opts) -> bad_table_do(Opts,File) end,
3320		    [write_concurrency, all_types]),
3321    verify_etsmem(EtsMem),
3322    ok.
3323
3324bad_table_do(Opts, DummyFile) ->
3325    Parent = self(),
3326    {Pid,Mref} = my_spawn_opt(fun()-> ets_new(priv,[private,named_table | Opts]),
3327				      Priv = ets_new(priv,[private | Opts]),
3328				      ets_new(prot,[protected,named_table | Opts]),
3329				      Prot = ets_new(prot,[protected | Opts]),
3330				      Parent ! {self(),Priv,Prot},
3331				      die_please = receive_any()
3332			      end,
3333			      [link, monitor]),
3334    {Pid,Priv,Prot} = receive_any(),
3335    MatchSpec = {{key,'_'}, [], ['$$']},
3336    Fun = fun(X,_) -> X end,
3337    OpList = [{delete,[key],update},
3338	      {delete_all_objects,[],update},
3339	      {delete_object,[{key,data}],update},
3340	      {first,[],read},
3341	      {foldl,[Fun, 0], read, tabarg_last},
3342	      {foldr,[Fun, 0], read, tabarg_last},
3343	      %%{from_dets,[DetsTab], update},
3344	      {give_away,[Pid, data], update},
3345	      %%{info, [], read},
3346	      %%{info, [safe_fixed], read},
3347	      %%{init_table,[Name, InitFun],update},
3348	      {insert, [{key,data}], update},
3349	      {insert_new, [{key,data}], update},
3350	      {insert_new, [[{key,data},{other,data}]], update},
3351	      {last, [], read},
3352	      {lookup, [key], read},
3353	      {lookup_element, [key, 2], read},
3354	      {match, [{}], read},
3355	      {match, [{},17], read},
3356	      {match_delete, [{}], update},
3357	      {match_object, [{}], read},
3358	      {match_object, [{},17], read},
3359	      {member,[key], read},
3360	      {next, [key], read},
3361	      {prev, [key], read},
3362	      {rename, [new_name], update},
3363	      {safe_fixtable, [true], read},
3364	      {select,[MatchSpec], read},
3365	      {select,[MatchSpec,17], read},
3366	      {select_count,[MatchSpec], read},
3367	      {select_delete,[MatchSpec], update},
3368	      {setopts, [{heir,none}], update},
3369	      {slot, [0], read},
3370	      {tab2file, [DummyFile], read, {return,{error,badtab}}},
3371	      {tab2file, [DummyFile,[]], read, {return,{error,badtab}}},
3372	      {tab2list, [], read},
3373	      %%{table,[], read},
3374	      %%{to_dets, [DetsTab], read},
3375	      {update_counter,[key,1], update},
3376	      {update_element,[key,{2,new_data}], update}
3377	     ],
3378    Info = {Opts, Priv, Prot},
3379    lists:foreach(fun(Op) -> bad_table_op(Info, Op) end,
3380                  OpList),
3381    Pid ! die_please,
3382    {'DOWN', Mref, process, Pid, normal} = receive_any(),
3383    ok.
3384
3385bad_table_op({Opts,Priv,Prot}, Op) ->
3386    %%io:format("Doing Op=~p on ~p's\n",[Op,Type]),
3387    T1 = ets_new(noname,Opts),
3388    bad_table_call(noname,Op),
3389    ets:delete(T1),
3390    bad_table_call(T1,Op),
3391    T2 = ets_new(named,[named_table | Opts]),
3392    ets:delete(T2),
3393    bad_table_call(named,Op),
3394    bad_table_call(T2,Op),
3395    bad_table_call(priv,Op),
3396    bad_table_call(Priv,Op),
3397    case element(3,Op) of
3398	update ->
3399	    bad_table_call(prot,Op),
3400	    bad_table_call(Prot,Op);
3401	read -> ok
3402    end.
3403
3404bad_table_call(T,{F,Args,_}) ->
3405    {'EXIT',{badarg,_}} = (catch apply(ets, F, [T|Args]));
3406bad_table_call(T,{F,Args,_,tabarg_last}) ->
3407    {'EXIT',{badarg,_}} = (catch apply(ets, F, Args++[T]));
3408bad_table_call(T,{F,Args,_,{return,Return}}) ->
3409    try
3410	Return = apply(ets, F, [T|Args])
3411    catch
3412	error:badarg -> ok
3413    end.
3414
3415
3416%% Check rename of ets tables.
3417rename(Config) when is_list(Config) ->
3418    repeat_for_opts(fun rename_do/1, [write_concurrency, all_types]).
3419
3420rename_do(Opts) ->
3421    EtsMem = etsmem(),
3422    ets_new(foobazz,[named_table, public | Opts]),
3423    ets:insert(foobazz,{foo,bazz}),
3424    ungermanbazz = ets:rename(foobazz,ungermanbazz),
3425    {'EXIT',{badarg, _}} = (catch ets:lookup(foobazz,foo)),
3426    [{foo,bazz}] = ets:lookup(ungermanbazz,foo),
3427    {'EXIT',{badarg,_}} =  (catch ets:rename(ungermanbazz,"no atom")),
3428    ets:delete(ungermanbazz),
3429    verify_etsmem(EtsMem).
3430
3431%% Check rename of unnamed ets table.
3432rename_unnamed(Config) when is_list(Config) ->
3433    repeat_for_opts(fun rename_unnamed_do/1,
3434                    [write_concurrency,all_types]).
3435
3436rename_unnamed_do(Opts) ->
3437    EtsMem = etsmem(),
3438    Tab = ets_new(bonkz,[public | Opts]),
3439    {'EXIT',{badarg, _}} = (catch ets:insert(bonkz,{foo,bazz})),
3440    bonkz = ets:info(Tab, name),
3441    Tab = ets:rename(Tab, tjabonkz),
3442    {'EXIT',{badarg, _}} = (catch ets:insert(tjabonkz,{foo,bazz})),
3443    tjabonkz = ets:info(Tab, name),
3444    ets:delete(Tab),
3445    verify_etsmem(EtsMem).
3446
3447%% Rename a table with many fixations, and at the same time delete it.
3448evil_rename(Config) when is_list(Config) ->
3449    EtsMem = etsmem(),
3450    evil_rename_1(old_hash, new_hash, [public,named_table]),
3451    evil_rename_1(old_tree, new_tree, [public,ordered_set,named_table]),
3452    wait_for_test_procs(true),
3453    verify_etsmem(EtsMem).
3454
3455evil_rename_1(Old, New, Flags) ->
3456    process_flag(trap_exit, true),
3457    Old = ets_new(Old, Flags),
3458    Fixer = fun() -> ets:safe_fixtable(Old, true) end,
3459    crazy_fixtable(15000, Fixer),
3460    erlang:yield(),
3461    New = ets:rename(Old, New),
3462    erlang:yield(),
3463    ets:delete(New),
3464    ok.
3465
3466crazy_fixtable(N, Fixer) ->
3467    Dracula = ets_new(count_dracula, [public]),
3468    ets:insert(Dracula, {count,0}),
3469    SpawnFun = fun() ->
3470		       Fixer(),
3471		       case ets:update_counter(Dracula, count, 1) rem 15 of
3472			   0 -> evil_creater_destroyer();
3473			   _ -> erlang:hibernate(erlang, error, [dont_wake_me])
3474		       end
3475	       end,
3476    crazy_fixtable_1(N, SpawnFun),
3477    crazy_fixtable_wait(N, Dracula),
3478    Dracula.
3479
3480crazy_fixtable_wait(N, Dracula) ->
3481    case ets:lookup(Dracula, count) of
3482	[{count,N}] ->
3483	    ets:delete(Dracula);
3484	Other ->
3485	    io:format("~p\n", [Other]),
3486	    receive after 10 -> ok end,
3487	    crazy_fixtable_wait(N, Dracula)
3488    end.
3489
3490crazy_fixtable_1(0, _) ->
3491    ok;
3492crazy_fixtable_1(N, Fun) ->
3493    %%FIXME my_spawn_link(Fun),
3494    my_spawn_link(Fun),
3495    crazy_fixtable_1(N-1, Fun).
3496
3497evil_creater_destroyer() ->
3498    T1 = evil_create_fixed_tab(),
3499    ets:delete(T1).
3500
3501evil_create_fixed_tab() ->
3502    T = ets_new(arne, [public]),
3503    ets:safe_fixtable(T, true),
3504    T.
3505
3506%% Tests that the return values and errors are equal for set's and
3507%% ordered_set's where applicable.
3508interface_equality(Config) when is_list(Config) ->
3509    repeat_for_opts(fun interface_equality_do/1).
3510
3511interface_equality_do(Opts) ->
3512    EtsMem = etsmem(),
3513    Set = ets_new(set,[set | Opts]),
3514    OrderedSet = ets_new(ordered_set,[ordered_set | Opts]),
3515    F = fun(X,T,FF) -> case X of
3516                           0 -> true;
3517                           _ ->
3518                               ets:insert(T, {X,
3519                                              integer_to_list(X),
3520                                              X rem 10}),
3521                               FF(X-1,T,FF)
3522                       end
3523        end,
3524    F(100,Set,F),
3525    F(100,OrderedSet,F),
3526    equal_results(ets, insert, Set, OrderedSet, [{a,"a"}]),
3527    equal_results(ets, insert, Set, OrderedSet, [{1,1,"1"}]),
3528    equal_results(ets, lookup, Set, OrderedSet, [10]),
3529    equal_results(ets, lookup, Set, OrderedSet, [1000]),
3530    equal_results(ets, delete, Set, OrderedSet, [10]),
3531    equal_results(ets, delete, Set, OrderedSet, [nott]),
3532    equal_results(ets, lookup, Set, OrderedSet, [1000]),
3533    equal_results(ets, insert, Set, OrderedSet, [10]),
3534    equal_results(ets, next, Set, OrderedSet, ['$end_of_table']),
3535    equal_results(ets, prev, Set, OrderedSet, ['$end_of_table']),
3536    equal_results(ets, match, Set, OrderedSet, [{'_','_','_'}]),
3537    equal_results(ets, match, Set, OrderedSet, [{'_','_','_','_'}]),
3538    equal_results(ets, match, Set, OrderedSet, [{$3,$2,2}]),
3539    equal_results(ets, match, Set, OrderedSet, ['_']),
3540    equal_results(ets, match, Set, OrderedSet, ['$1']),
3541    equal_results(ets, match, Set, OrderedSet, [{'_','$50',3}]),
3542    equal_results(ets, match, Set, OrderedSet, [['_','$50',3]]),
3543    equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
3544    equal_results(ets, match_delete, Set, OrderedSet, [{'_','_',4}]),
3545    equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
3546    equal_results(ets, match_object, Set, OrderedSet, [{'_','_',5}]),
3547    equal_results(ets, match_object, Set, OrderedSet, [{'_','_',4}]),
3548    equal_results(ets, match_object, Set, OrderedSet, ['_']),
3549    equal_results(ets, match_object, Set, OrderedSet, ['$5011']),
3550    equal_results(ets, match_delete, Set, OrderedSet, ['$20']),
3551    equal_results(ets, lookup_element, Set, OrderedSet, [13,2]),
3552    equal_results(ets, lookup_element, Set, OrderedSet, [13,4]),
3553    equal_results(ets, lookup_element, Set, OrderedSet, [14,2]),
3554    equal_results(ets, delete, Set, OrderedSet, []),
3555    verify_etsmem(EtsMem).
3556
3557equal_results(M, F, FirstArg1, FirstArg2 ,ACommon) ->
3558    Res = maybe_sort((catch apply(M,F, [FirstArg1 | ACommon]))),
3559    Res = maybe_sort((catch apply(M,F,[FirstArg2 | ACommon]))).
3560
3561maybe_sort(L) when is_list(L) ->
3562    lists:sort(L);
3563maybe_sort({'EXIT',{Reason, List}}) when is_list(List) ->
3564    {'EXIT',{Reason, lists:map(fun({Module, Function, _, _}) ->
3565				       {Module, Function, '_'}
3566			       end,
3567			       List)}};
3568maybe_sort(Any) ->
3569    Any.
3570
3571%% Test match, match_object and match_delete in ordered set's.
3572ordered_match(Config) when is_list(Config)->
3573    repeat_for_opts(fun ordered_match_do/1).
3574
3575ordered_match_do(Opts) ->
3576    EtsMem = etsmem(),
3577    F = fun(X,T,FF) -> case X of
3578			   0 -> true;
3579			   _ ->
3580			       ets:insert(T, {X,
3581					      integer_to_list(X),
3582					      X rem 10,
3583					      X rem 100,
3584					      X rem 1000}),
3585			       FF(X-1,T,FF)
3586		       end
3587	end,
3588    T1 = ets_new(xxx,[ordered_set| Opts]),
3589    F(3000,T1,F),
3590    [[3,3],[3,3],[3,3]] = ets:match(T1, {'_','_','$1','$2',3}),
3591    F2 = fun(X,Rem,Res,FF) -> case X of
3592				  0 -> [];
3593				  _ ->
3594				      case X rem Rem of
3595					  Res ->
3596					      FF(X-1,Rem,Res,FF) ++
3597						  [{X,
3598						    integer_to_list(X),
3599						    X rem 10,
3600						    X rem 100,
3601						    X rem 1000}];
3602					  _ ->
3603					      FF(X-1,Rem,Res,FF)
3604				      end
3605			      end
3606	 end,
3607    OL1 = F2(3000,100,2,F2),
3608    OL1 = ets:match_object(T1, {'_','_','_',2,'_'}),
3609    true = ets:match_delete(T1,{'_','_','_',2,'_'}),
3610    [] = ets:match_object(T1, {'_','_','_',2,'_'}),
3611    OL2 = F2(3000,100,3,F2),
3612    OL2 = ets:match_object(T1, {'_','_','_',3,'_'}),
3613    ets:delete(T1),
3614    verify_etsmem(EtsMem).
3615
3616
3617%% Test basic functionality in ordered_set's.
3618ordered(Config) when is_list(Config) ->
3619    repeat_for_opts(fun ordered_do/1).
3620
3621ordered_do(Opts) ->
3622    EtsMem = etsmem(),
3623    T = ets_new(oset, [ordered_set | Opts]),
3624    InsList = [
3625	       25,26,27,28,
3626	       5,6,7,8,
3627	       21,22,23,24,
3628	       9,10,11,12,
3629	       1,2,3,4,
3630	       17,18,19,20,
3631	       13,14,15,16,
3632	       1 bsl 33
3633	      ],
3634    lists:foreach(fun(X) ->
3635			  ets:insert(T,{X,integer_to_list(X)})
3636		  end,
3637		  InsList),
3638    IL2 = lists:map(fun(X) -> {X,integer_to_list(X)} end, InsList),
3639    L1 = pick_all_forward(T),
3640    L2 = pick_all_backwards(T),
3641    S1 = lists:sort(IL2),
3642    S2 = lists:reverse(lists:sort(IL2)),
3643    S1 = L1,
3644    S2 = L2,
3645    [{1,"1"}] = ets:slot(T,0),
3646    [{28,"28"}] = ets:slot(T,27),
3647    [{1 bsl 33,_}] = ets:slot(T,28),
3648    27 = ets:prev(T,28),
3649    [{7,"7"}] = ets:slot(T,6),
3650    '$end_of_table' = ets:next(T,1 bsl 33),
3651    [{12,"12"}] = ets:slot(T,11),
3652    '$end_of_table' = ets:slot(T,29),
3653    [{1,"1"}] = ets:slot(T,0),
3654    28 = ets:prev(T,1 bsl 33),
3655    1 = ets:next(T,0),
3656    pick_all_forward(T),
3657    [{7,"7"}] = ets:slot(T,6),
3658    L2 = pick_all_backwards(T),
3659    [{7,"7"}] = ets:slot(T,6),
3660    ets:delete(T),
3661    verify_etsmem(EtsMem).
3662
3663pick_all(_T,'$end_of_table',_How) ->
3664    [];
3665pick_all(T,Last,How) ->
3666    This = case How of
3667	       next ->
3668		   ets:next(T,Last);
3669	       prev ->
3670		   ets:prev(T,Last)
3671	   end,
3672    [LastObj] = ets:lookup(T,Last),
3673    [LastObj | pick_all(T,This,How)].
3674
3675pick_all_forward(T) ->
3676    pick_all(T,ets:first(T),next).
3677pick_all_backwards(T) ->
3678    pick_all(T,ets:last(T),prev).
3679
3680
3681
3682%% Small test case for both set and bag type ets tables.
3683setbag(Config) when is_list(Config) ->
3684    EtsMem = etsmem(),
3685    lists:foreach(fun(SetType) ->
3686                          Set = ets_new(SetType,[SetType]),
3687                          Bag = ets_new(bag,[bag]),
3688                          Key = {foo,bar},
3689
3690                          %% insert some value
3691                          ets:insert(Set,{Key,val1}),
3692                          ets:insert(Bag,{Key,val1}),
3693
3694                          %% insert new value for same key again
3695                          ets:insert(Set,{Key,val2}),
3696                          ets:insert(Bag,{Key,val2}),
3697
3698                          %% check
3699                          [{Key,val2}] = ets:lookup(Set,Key),
3700                          [{Key,val1},{Key,val2}] = ets:lookup(Bag,Key),
3701
3702                          true = ets:delete(Set),
3703                          true = ets:delete(Bag)
3704                  end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]),
3705    verify_etsmem(EtsMem).
3706
3707%% Test case to check proper return values for illegal ets_new() calls.
3708badnew(Config) when is_list(Config) ->
3709    EtsMem = etsmem(),
3710    {'EXIT',{badarg,_}} = (catch ets:new(12,[])),
3711    {'EXIT',{badarg,_}} = (catch ets:new({a,b},[])),
3712    {'EXIT',{badarg,_}} = (catch ets:new(name,[foo])),
3713    {'EXIT',{badarg,_}} = (catch ets:new(name,{bag})),
3714    {'EXIT',{badarg,_}} = (catch ets:new(name,bag)),
3715    verify_etsmem(EtsMem).
3716
3717%% OTP-2314. Test case to check that a non-proper list does not
3718%% crash the emulator.
3719verybadnew(Config) when is_list(Config) ->
3720    EtsMem = etsmem(),
3721    {'EXIT',{badarg,_}} = (catch ets:new(verybad,[set|protected])),
3722    verify_etsmem(EtsMem).
3723
3724%% Small check to see if named tables work.
3725named(Config) when is_list(Config) ->
3726    EtsMem = etsmem(),
3727    Tab = make_table(foo,
3728		     [named_table],
3729		     [{key,val}]),
3730    [{key,val}] = ets:lookup(foo,key),
3731    true = ets:delete(Tab),
3732    verify_etsmem(EtsMem).
3733
3734%% Test case to check if specified keypos works.
3735keypos2(Config) when is_list(Config) ->
3736    EtsMem = etsmem(),
3737    lists:foreach(fun(SetType) ->
3738                          Tab = make_table(foo,
3739                                           [SetType,{keypos,2}],
3740                                           [{val,key}, {val2,key}]),
3741                          [{val2,key}] = ets:lookup(Tab,key),
3742                          true = ets:delete(Tab)
3743                  end, [set, cat_ord_set,stim_cat_ord_set,ordered_set]),
3744    verify_etsmem(EtsMem).
3745
3746%% Privacy check. Check that a named(public/private/protected) table
3747%% cannot be read by the wrong process(es).
3748privacy(Config) when is_list(Config) ->
3749    repeat_for_opts(fun privacy_do/1).
3750
3751privacy_do(Opts) ->
3752    EtsMem = etsmem(),
3753    process_flag(trap_exit,true),
3754    Parent = self(),
3755    Owner = my_spawn_link(fun() -> privacy_owner(Parent, Opts) end),
3756    receive
3757	{'EXIT',Owner,Reason} ->
3758	    exit({privacy_test,Reason});
3759	ok ->
3760	    ok
3761    end,
3762
3763    privacy_check(pub,prot,priv),
3764
3765    Owner ! {shift,1,{pub,prot,priv}},
3766    receive
3767        {Pub1,Prot1,Priv1} ->
3768            ok = privacy_check(Pub1,Prot1,Priv1),
3769            Owner ! {shift,2,{Pub1,Prot1,Priv1}}
3770    end,
3771
3772    receive
3773        {Pub2,Prot2,Priv2} ->
3774            ok = privacy_check(Pub2,Prot2,Priv2),
3775            Owner ! {shift,0,{Pub2,Prot2,Priv2}}
3776    end,
3777
3778    receive
3779        {Pub3,Prot3,Priv3} ->
3780            ok = privacy_check(Pub3,Prot3,Priv3)
3781    end,
3782
3783    Owner ! die,
3784    receive {'EXIT',Owner,_} -> ok end,
3785    verify_etsmem(EtsMem).
3786
3787privacy_check(Pub,Prot,Priv) ->
3788    %% check read rights
3789    [] = ets:lookup(Pub, foo),
3790    [] = ets:lookup(Prot,foo),
3791    {'EXIT',{badarg,_}} = (catch ets:lookup(Priv,foo)),
3792
3793    %% check write rights
3794    true = ets:insert(Pub, {1,foo}),
3795    {'EXIT',{badarg,_}} = (catch ets:insert(Prot,{2,foo})),
3796    {'EXIT',{badarg,_}} = (catch ets:insert(Priv,{3,foo})),
3797
3798    %% check that it really wasn't written, either
3799    [] = ets:lookup(Prot,foo),
3800    ok.
3801
3802privacy_owner(Boss, Opts) ->
3803    ets_new(pub, [public,named_table | Opts]),
3804    ets_new(prot,[protected,named_table | Opts]),
3805    ets_new(priv,[private,named_table | Opts]),
3806    Boss ! ok,
3807    privacy_owner_loop(Boss).
3808
3809privacy_owner_loop(Boss) ->
3810    receive
3811	{shift,N,Pub_Prot_Priv} ->
3812	    {Pub,Prot,Priv} = rotate_tuple(Pub_Prot_Priv, N),
3813
3814	    ets:setopts(Pub,{protection,public}),
3815	    ets:setopts(Prot,{protection,protected}),
3816	    ets:setopts(Priv,{protection,private}),
3817	    Boss ! {Pub,Prot,Priv},
3818	    privacy_owner_loop(Boss);
3819
3820	die -> ok
3821    end.
3822
3823rotate_tuple(Tuple, 0) ->
3824    Tuple;
3825rotate_tuple(Tuple, N) ->
3826    [H|T] = tuple_to_list(Tuple),
3827    rotate_tuple(list_to_tuple(T ++ [H]), N-1).
3828
3829
3830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3831
3832
3833%% Check lookup in an empty table and lookup of a non-existing key.
3834empty(Config) when is_list(Config) ->
3835    repeat_for_opts_all_table_types(fun empty_do/1).
3836
3837empty_do(Opts) ->
3838    EtsMem = etsmem(),
3839    Tab = ets_new(foo,Opts),
3840    [] = ets:lookup(Tab,key),
3841    true = ets:insert(Tab,{key2,val}),
3842    [] = ets:lookup(Tab,key),
3843    true = ets:delete(Tab),
3844    verify_etsmem(EtsMem).
3845
3846%% Check proper return values for illegal insert operations.
3847badinsert(Config) when is_list(Config) ->
3848    repeat_for_opts_all_table_types(fun badinsert_do/1).
3849
3850badinsert_do(Opts) ->
3851    EtsMem = etsmem(),
3852    {'EXIT',{badarg,_}} = (catch ets:insert(foo,{key,val})),
3853
3854    Tab = ets_new(foo,Opts),
3855    {'EXIT',{badarg,_}} = (catch ets:insert(Tab,{})),
3856
3857    Tab3 = ets_new(foo,[{keypos,3}| Opts]),
3858    {'EXIT',{badarg,_}} = (catch ets:insert(Tab3,{a,b})),
3859
3860    {'EXIT',{badarg,_}} = (catch ets:insert(Tab,[key,val2])),
3861    true = ets:delete(Tab),
3862    true = ets:delete(Tab3),
3863    verify_etsmem(EtsMem).
3864
3865%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3866
3867
3868%% Check proper return values from bad lookups in existing/non existing
3869%% ets tables.
3870badlookup(Config) when is_list(Config) ->
3871    EtsMem = etsmem(),
3872    {'EXIT',{badarg,_}} = (catch ets:lookup(foo,key)),
3873    Tab = ets_new(foo,[]),
3874    ets:delete(Tab),
3875    {'EXIT',{badarg,_}} = (catch ets:lookup(Tab,key)),
3876    verify_etsmem(EtsMem).
3877
3878%% Test that lookup returns objects in order of insertion for bag and dbag.
3879lookup_order(Config) when is_list(Config) ->
3880    EtsMem = etsmem(),
3881    repeat_for_opts(fun lookup_order_do/1,
3882                    [write_concurrency,[bag,duplicate_bag]]),
3883    verify_etsmem(EtsMem),
3884    ok.
3885
3886lookup_order_do(Opts) ->
3887    lookup_order_2(Opts, false),
3888    lookup_order_2(Opts, true).
3889
3890lookup_order_2(Opts, Fixed) ->
3891    io:format("Opts=~p Fixed=~p\n",[Opts,Fixed]),
3892
3893    A = 1, B = 2, C = 3,
3894    ABC = [A,B,C],
3895    Pair = [{A,B},{B,A},{A,C},{C,A},{B,C},{C,B}],
3896    Combos = [{D1,D2,D3} || D1<-ABC, D2<-Pair, D3<-Pair],
3897    lists:foreach(fun({D1,{D2a,D2b},{D3a,D3b}}) ->
3898			  T = ets_new(foo,Opts),
3899			  case Fixed of
3900			      true -> ets:safe_fixtable(T,true);
3901			      false -> ok
3902			  end,
3903			  S10 = {T,[],key},
3904			  S20 = check_insert(S10,A),
3905			  S30 = check_insert(S20,B),
3906			  S40 = check_insert(S30,C),
3907			  S50 = check_delete(S40,D1),
3908			  S55 = check_insert(S50,D1),
3909			  S60 = check_insert(S55,D1),
3910			  S70 = check_delete(S60,D2a),
3911			  S80 = check_delete(S70,D2b),
3912			  S90 = check_insert(S80,D2a),
3913			  SA0 = check_delete(S90,D3a),
3914			  SB0 = check_delete(SA0,D3b),
3915			  check_insert_new(SB0,D3b),
3916
3917			  true = ets:delete(T)
3918		  end,
3919		  Combos).
3920
3921
3922check_insert({T,List0,Key},Val) ->
3923    %%io:format("insert ~p into ~p\n",[Val,List0]),
3924    ets:insert(T,{Key,Val}),
3925    List1 = case (ets:info(T,type) =:= bag andalso
3926		  lists:member({Key,Val},List0)) of
3927		true -> List0;
3928		false -> [{Key,Val} | List0]
3929	    end,
3930    check_check({T,List1,Key}).
3931
3932check_insert_new({T,List0,Key},Val) ->
3933    %%io:format("insert_new ~p into ~p\n",[Val,List0]),
3934    Ret = ets:insert_new(T,{Key,Val}),
3935    Ret = (List0 =:= []),
3936    List1 = case Ret of
3937		true -> [{Key,Val}];
3938		false -> List0
3939	    end,
3940    check_check({T,List1,Key}).
3941
3942
3943check_delete({T,List0,Key},Val) ->
3944    %%io:format("delete ~p from ~p\n",[Val,List0]),
3945    ets:delete_object(T,{Key,Val}),
3946    List1 = lists:filter(fun(Obj) -> Obj =/= {Key,Val} end,
3947			 List0),
3948    check_check({T,List1,Key}).
3949
3950check_check(S={T,List,Key}) ->
3951    case lists:reverse(ets:lookup(T,Key)) of
3952	List -> ok;
3953        ETS -> io:format("check failed:\nETS: ~p\nCHK: ~p\n", [ETS,List]),
3954	       ct:fail("Invalid return value from ets:lookup")
3955    end,
3956    Items = ets:info(T,size),
3957    Items = length(List),
3958    S.
3959
3960fill_tab(Tab,Val) ->
3961    ets:insert(Tab,{key,Val}),
3962    ets:insert(Tab,{{a,144},Val}),
3963    ets:insert(Tab,{{a,key2},Val}),
3964    ets:insert(Tab,{14,Val}),
3965    ok.
3966
3967
3968%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3969
3970
3971%% OTP-2386. Multiple return elements.
3972lookup_element_mult(Config) when is_list(Config) ->
3973    repeat_for_opts(fun lookup_element_mult_do/1).
3974
3975lookup_element_mult_do(Opts) ->
3976    EtsMem = etsmem(),
3977    T = ets_new(service, [bag, {keypos, 2} | Opts]),
3978    D = lists:reverse(lem_data()),
3979    lists:foreach(fun(X) -> ets:insert(T, X) end, D),
3980    ok = lem_crash_3(T),
3981    ets:insert(T, {0, "heap_key"}),
3982    ets:lookup_element(T, "heap_key", 2),
3983    true = ets:delete(T),
3984    verify_etsmem(EtsMem).
3985
3986lem_data() ->
3987    [{service,'eddie2@boromir',{150,236,14,103},httpd88,self()},
3988     {service,'eddie2@boromir',{150,236,14,103},httpd80,self()},
3989     {service,'eddie3@boromir',{150,236,14,107},httpd88,self()},
3990     {service,'eddie3@boromir',{150,236,14,107},httpd80,self()},
3991     {service,'eddie4@boromir',{150,236,14,108},httpd88,self()}].
3992
3993lem_crash(T) ->
3994    L = ets:lookup_element(T, 'eddie2@boromir', 3),
3995    {erlang:phash(L, 256), L}.
3996
3997lem_crash_3(T) ->
3998    lem_crash(T),
3999    io:format("Survived once~n"),
4000    lem_crash(T),
4001    io:format("Survived twice~n"),
4002    lem_crash(T),
4003    io:format("Survived all!~n"),
4004    ok.
4005
4006%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4007
4008
4009%% Check delete of an element inserted in a `filled' table.
4010delete_elem(Config) when is_list(Config) ->
4011    repeat_for_opts(fun delete_elem_do/1,
4012                    [write_concurrency, all_types]).
4013
4014delete_elem_do(Opts) ->
4015    EtsMem = etsmem(),
4016    Tab = ets_new(foo,Opts),
4017    fill_tab(Tab,foo),
4018    ets:insert(Tab,{{b,key},foo}),
4019    ets:insert(Tab,{{c,key},foo}),
4020    true = ets:delete(Tab,{b,key}),
4021    [] = ets:lookup(Tab,{b,key}),
4022    [{{c,key},foo}] = ets:lookup(Tab,{c,key}),
4023    true = ets:delete(Tab),
4024    verify_etsmem(EtsMem).
4025
4026%% Check that ets:delete() works and releases the name of the
4027%% deleted table.
4028delete_tab(Config) when is_list(Config) ->
4029    repeat_for_opts(fun delete_tab_do/1,
4030                    [write_concurrency,all_types]).
4031
4032delete_tab_do(Opts) ->
4033    Name = foo,
4034    EtsMem = etsmem(),
4035    Name = ets_new(Name, [named_table | Opts]),
4036    true = ets:delete(foo),
4037    %% The name should be available again.
4038    Name = ets_new(Name, [named_table | Opts]),
4039    true = ets:delete(Name),
4040    verify_etsmem(EtsMem).
4041
4042%% Check that ets:delete/1 works and that other processes can run.
4043delete_large_tab(Config) when is_list(Config) ->
4044    ct:timetrap({minutes,60}), %% valgrind needs a lot
4045    KeyRange = 16#ffffff,
4046    Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)],
4047    EtsMem = etsmem(),
4048    repeat_for_opts(fun(Opts) -> delete_large_tab_do(Config,
4049                                                     key_range(Opts,KeyRange),
4050                                                     Data)
4051                    end),
4052    verify_etsmem(EtsMem).
4053
4054delete_large_tab_do(Config, Opts,Data) ->
4055    delete_large_tab_1(Config, foo_hash, Opts, Data, false),
4056    delete_large_tab_1(Config, foo_tree, [ordered_set | Opts], Data, false),
4057    delete_large_tab_1(Config, foo_tree, [stim_cat_ord_set | Opts], Data, false),
4058    delete_large_tab_1(Config, foo_hash_fix, Opts, Data, true).
4059
4060
4061delete_large_tab_1(Config, Name, Flags, Data, Fix) ->
4062    case is_redundant_opts_combo(Flags) of
4063        true -> skip;
4064        false ->
4065            delete_large_tab_2(Config, Name, Flags, Data, Fix)
4066    end.
4067
4068delete_large_tab_2(Config, Name, Flags, Data, Fix) ->
4069    Tab = ets_new(Name, Flags),
4070    ets:insert(Tab, Data),
4071
4072    case Fix of
4073	false -> ok;
4074	true ->
4075	    true = ets:safe_fixtable(Tab, true),
4076	    lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
4077    end,
4078
4079    {priority, Prio} = process_info(self(), priority),
4080    Deleter = self(),
4081    ForceTrap = proplists:get_bool(ets_force_trap, Config),
4082    [SchedTracer]
4083	= start_loopers(1,
4084			Prio,
4085			fun (SC) ->
4086				receive
4087				    {trace, Deleter, out, _} ->
4088                                        case {ets:info(Tab), SC, ForceTrap} of
4089                                            {undefined, _, _} -> ok;
4090                                            {_, 0, true} ->
4091                                                %% Forced first trap of ets:delete,
4092                                                %% tab still reachable
4093                                                ok
4094                                        end,
4095                                        SC+1;
4096				    {trace,
4097				     Deleter,
4098				     register,
4099				     delete_large_tab_done_marker}->
4100					Deleter ! {schedule_count, SC},
4101					exit(normal);
4102				    _ ->
4103					SC
4104				end
4105			end,
4106			0),
4107    SchedTracerMon = monitor(process, SchedTracer),
4108    Loopers = start_loopers(erlang:system_info(schedulers),
4109			    Prio,
4110			    fun (_) -> erlang:yield() end,
4111			    ok),
4112    erlang:yield(),
4113    1 = erlang:trace(self(),true,[running,procs,{tracer,SchedTracer}]),
4114    true = ets:delete(Tab),
4115    %% The register stuff is just a trace marker
4116    true = register(delete_large_tab_done_marker, self()),
4117    true = unregister(delete_large_tab_done_marker),
4118    undefined = ets:info(Tab),
4119    ok = stop_loopers(Loopers),
4120    receive
4121	{schedule_count, N} ->
4122	    io:format("~s: context switches: ~p", [Name,N]),
4123	    if
4124		N >= 5 -> ok;
4125		true -> ct:fail(failed)
4126	    end
4127    end,
4128    receive {'DOWN',SchedTracerMon,process,SchedTracer,_} -> ok end,
4129    ok.
4130
4131%% Delete a large name table and try to create a new table with
4132%% the same name in another process.
4133delete_large_named_table(Config) when is_list(Config) ->
4134    KeyRange = 16#ffffff,
4135    Data = [{erlang:phash2(I, KeyRange),I} || I <- lists:seq(1, 200000)],
4136    EtsMem = etsmem(),
4137    repeat_for_opts(fun(Opts) ->
4138                            delete_large_named_table_do(key_range(Opts,KeyRange),
4139                                                        Data)
4140                    end),
4141    verify_etsmem(EtsMem),
4142    ok.
4143
4144delete_large_named_table_do(Opts,Data) ->
4145    delete_large_named_table_1(foo_hash, [named_table | Opts], Data, false),
4146    delete_large_named_table_1(foo_tree, [ordered_set,named_table | Opts], Data, false),
4147    delete_large_named_table_1(foo_tree, [stim_cat_ord_set,named_table | Opts], Data, false),
4148    delete_large_named_table_1(foo_hash, [named_table | Opts], Data, true).
4149
4150delete_large_named_table_1(Name, Flags, Data, Fix) ->
4151    case is_redundant_opts_combo(Flags) of
4152        true -> skip;
4153        false ->
4154            delete_large_named_table_2(Name, Flags, Data, Fix)
4155    end.
4156
4157delete_large_named_table_2(Name, Flags, Data, Fix) ->
4158    Tab = ets_new(Name, Flags),
4159    ets:insert(Tab, Data),
4160
4161    case Fix of
4162	false -> ok;
4163	true ->
4164	    true = ets:safe_fixtable(Tab, true),
4165	    lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
4166    end,
4167    {Pid, MRef} = my_spawn_opt(fun() ->
4168				       receive
4169					   ets_new ->
4170					       ets_new(Name, [named_table])
4171				       end
4172			       end,
4173			       [link, monitor]),
4174    true = ets:delete(Tab),
4175    Pid ! ets_new,
4176    receive {'DOWN',MRef,process,Pid,_} -> ok end,
4177    ok.
4178
4179%% Delete a large table, and kill the process during the delete.
4180evil_delete(Config) when is_list(Config) ->
4181    KeyRange = 100000,
4182    Data = [{I,I*I} || I <- lists:seq(1, KeyRange)],
4183    repeat_for_opts(fun(Opts) ->
4184                            evil_delete_do(key_range(Opts,KeyRange),
4185                                           Data)
4186                    end).
4187
4188evil_delete_do(Opts,Data) ->
4189    EtsMem = etsmem(),
4190    evil_delete_owner(foo_hash, Opts, Data, false),
4191    verify_etsmem(EtsMem),
4192    evil_delete_owner(foo_hash, Opts, Data, true),
4193    verify_etsmem(EtsMem),
4194    evil_delete_owner(foo_tree, [ordered_set | Opts], Data, false),
4195    verify_etsmem(EtsMem),
4196    evil_delete_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false),
4197    verify_etsmem(EtsMem),
4198    TabA = evil_delete_not_owner(foo_hash, Opts, Data, false),
4199    verify_etsmem(EtsMem),
4200    TabB = evil_delete_not_owner(foo_hash, Opts, Data, true),
4201    verify_etsmem(EtsMem),
4202    TabC = evil_delete_not_owner(foo_tree, [ordered_set | Opts], Data, false),
4203    verify_etsmem(EtsMem),
4204    TabD = evil_delete_not_owner(foo_catree, [stim_cat_ord_set | Opts], Data, false),
4205    verify_etsmem(EtsMem),
4206    lists:foreach(fun(T) -> undefined = ets:info(T) end,
4207		  [TabA,TabB,TabC,TabD]).
4208
4209evil_delete_not_owner(Name, Flags, Data, Fix) ->
4210    case is_redundant_opts_combo(Flags) of
4211        true -> skip;
4212        false ->
4213            evil_delete_not_owner_1(Name, Flags, Data, Fix)
4214    end.
4215
4216evil_delete_not_owner_1(Name, Flags, Data, Fix) ->
4217    io:format("Not owner: ~p, fix = ~p", [Name,Fix]),
4218    Tab = ets_new(Name, [public|Flags]),
4219    ets:insert(Tab, Data),
4220    case Fix of
4221	false -> ok;
4222	true ->
4223	    true = ets:safe_fixtable(Tab, true),
4224	    lists:foreach(fun({K,_}) -> ets:delete(Tab, K) end, Data)
4225    end,
4226    Pid = my_spawn(fun() ->
4227			   P = my_spawn_link(
4228				 fun() ->
4229					 receive kill -> ok end,
4230					 erlang:yield(),
4231					 exit(kill_linked_processes_now)
4232				 end),
4233			   erlang:yield(),
4234			   P ! kill,
4235			   true = ets:delete(Tab)
4236		   end),
4237    Ref = erlang:monitor(process, Pid),
4238    receive {'DOWN',Ref,_,_,_} -> ok end,
4239    Tab.
4240
4241evil_delete_owner(Name, Flags, Data, Fix) ->
4242    case is_redundant_opts_combo(Flags) of
4243        true -> skip;
4244        false ->
4245            evil_delete_owner_1(Name, Flags, Data, Fix)
4246    end.
4247
4248evil_delete_owner_1(Name, Flags, Data, Fix) ->
4249    Fun = fun() ->
4250		  Tab = ets_new(Name, [public|Flags]),
4251		  ets:insert(Tab, Data),
4252		  case Fix of
4253		      false -> ok;
4254		      true ->
4255			  true = ets:safe_fixtable(Tab, true),
4256			  lists:foreach(fun({K,_}) ->
4257						ets:delete(Tab, K)
4258					end, Data)
4259		  end,
4260		  erlang:yield(),
4261		  my_spawn_link(fun() ->
4262					erlang:yield(),
4263					exit(kill_linked_processes_now)
4264				end),
4265		  true = ets:delete(Tab)
4266	  end,
4267    Pid = my_spawn(Fun),
4268    Ref = erlang:monitor(process, Pid),
4269    receive {'DOWN',Ref,_,_,_} -> ok end.
4270
4271
4272exit_large_table_owner(Config) when is_list(Config) ->
4273    %%Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
4274    Laps = 500000 div syrup_factor(),
4275    FEData = fun(Do) -> repeat_while(fun(I) when I =:= Laps -> {false,ok};
4276					(I) -> Do({erlang:phash2(I, 16#ffffff),I}),
4277					       {true, I+1}
4278				     end, 1)
4279	     end,
4280    EtsMem = etsmem(),
4281    repeat_for_opts(fun(Opts) ->
4282                            exit_large_table_owner_do(Opts,
4283                                                      FEData,
4284                                                      Config)
4285                    end),
4286    verify_etsmem(EtsMem).
4287
4288exit_large_table_owner_do(Opts, FEData, Config) ->
4289    verify_rescheduling_exit(Config, FEData, [named_table | Opts], true, 1, 1),
4290    verify_rescheduling_exit(Config, FEData, Opts, false, 1, 1).
4291
4292exit_many_large_table_owner(Config) when is_list(Config) ->
4293    ct:timetrap({minutes,30}), %% valgrind needs a lot
4294    %%Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 500000)],
4295    Laps = 500000 div syrup_factor(),
4296    FEData = fun(Do) -> repeat_while(fun(I) when I =:= Laps -> {false,ok};
4297					(I) -> Do({erlang:phash2(I, 16#ffffff),I}),
4298					       {true, I+1}
4299				     end, 1)
4300	     end,
4301    EtsMem = etsmem(),
4302    repeat_for_opts(fun(Opts) -> exit_many_large_table_owner_do(Opts,FEData,Config) end),
4303    verify_etsmem(EtsMem).
4304
4305exit_many_large_table_owner_do(Opts,FEData,Config) ->
4306    verify_rescheduling_exit(Config, FEData, Opts, true, 1, 4),
4307    verify_rescheduling_exit(Config, FEData, [named_table | Opts], false, 1, 4).
4308
4309exit_many_tables_owner(Config) when is_list(Config) ->
4310    NoData = fun(_Do) -> ok end,
4311    EtsMem = etsmem(),
4312    verify_rescheduling_exit(Config, NoData, [named_table], false, 1000, 1),
4313    verify_rescheduling_exit(Config, NoData, [named_table,{write_concurrency,true}], false, 1000, 1),
4314    verify_etsmem(EtsMem).
4315
4316exit_many_many_tables_owner(Config) when is_list(Config) ->
4317    Data = [{erlang:phash2(I, 16#ffffff),I} || I <- lists:seq(1, 50)],
4318    FEData = fun(Do) -> lists:foreach(Do, Data) end,
4319    repeat_for_opts(fun(Opts) -> exit_many_many_tables_owner_do(Opts,FEData,Config) end).
4320
4321exit_many_many_tables_owner_do(Opts,FEData,Config) ->
4322    verify_rescheduling_exit(Config, FEData, [named_table | Opts], true, 200, 5),
4323    verify_rescheduling_exit(Config, FEData, Opts, false, 200, 5),
4324    wait_for_test_procs(),
4325    EtsMem = etsmem(),
4326    verify_rescheduling_exit(Config, FEData, Opts, true, 200, 5),
4327    verify_rescheduling_exit(Config, FEData, [named_table | Opts], false, 200, 5),
4328    verify_etsmem(EtsMem).
4329
4330
4331count_exit_sched(TP) ->
4332    receive
4333	{trace, TP, in_exiting, 0} ->
4334	    count_exit_sched_out(TP, 1);
4335	{trace, TP, out_exiting, 0} ->
4336	    count_exit_sched_in(TP, 1);
4337	{trace, TP, out_exited, 0} ->
4338	    0
4339    end.
4340
4341count_exit_sched_in(TP, N) ->
4342    receive
4343	{trace, TP, in_exiting, 0} ->
4344	    count_exit_sched_out(TP, N);
4345	{trace, TP, _, _} = Msg ->
4346	    exit({unexpected_trace_msg, Msg})
4347    end.
4348
4349count_exit_sched_out(TP, N) ->
4350    receive
4351	{trace, TP, out_exiting, 0} ->
4352	    count_exit_sched_in(TP, N+1);
4353	{trace, TP, out_exited, 0} ->
4354	    N;
4355	{trace, TP, _, _} = Msg ->
4356	    exit({unexpected_trace_msg, Msg})
4357    end.
4358
4359vre_fix_tables(Tab) ->
4360    Parent = self(),
4361    Go = make_ref(),
4362    my_spawn_link(fun () ->
4363			  true = ets:safe_fixtable(Tab, true),
4364			  Parent ! Go,
4365			  receive infinity -> ok end
4366		  end),
4367    receive Go -> ok end,
4368    ok.
4369
4370verify_rescheduling_exit(Config, ForEachData, Flags, Fix, NOTabs, NOProcs) ->
4371    NoFix = 5,
4372    TestCase = atom_to_list(proplists:get_value(test_case, Config)),
4373    Parent = self(),
4374    KillMe = make_ref(),
4375    PFun =
4376	fun () ->
4377		repeat(
4378		  fun () ->
4379			  Uniq = erlang:unique_integer([positive]),
4380			  Name = list_to_atom(TestCase ++ "-" ++
4381						  integer_to_list(Uniq)),
4382			  Tab = ets_new(Name, Flags),
4383                          ForEachData(fun(Data) -> ets:insert(Tab, Data) end),
4384			  case Fix of
4385			      false -> ok;
4386			      true ->
4387				  lists:foreach(fun (_) ->
4388							vre_fix_tables(Tab)
4389						end,
4390						lists:seq(1,NoFix)),
4391                                  KeyPos = ets:info(Tab,keypos),
4392                                  ForEachData(fun(Data) ->
4393						      ets:delete(Tab, element(KeyPos,Data))
4394                                              end)
4395			  end
4396		  end,
4397		  NOTabs),
4398		Parent ! {KillMe, self()},
4399		receive after infinity -> ok end
4400	end,
4401    TPs = lists:map(fun (_) ->
4402			    TP = my_spawn_link(PFun),
4403			    1 = erlang:trace(TP, true, [exiting]),
4404			    TP
4405		    end,
4406		    lists:seq(1, NOProcs)),
4407    lists:foreach(fun (TP) ->
4408			  receive {KillMe, TP} -> ok end
4409		  end,
4410		  TPs),
4411    LPs = start_loopers(erlang:system_info(schedulers),
4412			normal,
4413			fun (_) ->
4414				erlang:yield()
4415			end,
4416			ok),
4417    lists:foreach(fun (TP) ->
4418			  unlink(TP),
4419			  exit(TP, bang)
4420		  end,
4421		  TPs),
4422    lists:foreach(fun (TP) ->
4423			  XScheds = count_exit_sched(TP),
4424			  io:format("~p XScheds=~p~n",
4425				    [TP, XScheds]),
4426			  true = XScheds >= 3
4427		  end,
4428		  TPs),
4429    stop_loopers(LPs),
4430    ok.
4431
4432
4433
4434%% Make sure that slots for ets tables are cleared properly.
4435table_leak(Config) when is_list(Config) ->
4436    repeat_for_opts_all_non_stim_table_types(fun(Opts) -> table_leak_1(Opts,20000) end).
4437
4438table_leak_1(_,0) -> ok;
4439table_leak_1(Opts,N) ->
4440    T = ets_new(fooflarf, Opts),
4441    true = ets:delete(T),
4442    table_leak_1(Opts,N-1).
4443
4444%% Check proper return values for illegal delete operations.
4445baddelete(Config) when is_list(Config) ->
4446    EtsMem = etsmem(),
4447    {'EXIT',{badarg,_}} = (catch ets:delete(foo)),
4448    Tab = ets_new(foo,[]),
4449    true = ets:delete(Tab),
4450    {'EXIT',{badarg,_}} = (catch ets:delete(Tab)),
4451    verify_etsmem(EtsMem).
4452
4453%% Check that match_delete works. Also tests tab2list function.
4454match_delete(Config) when is_list(Config) ->
4455    EtsMem = etsmem(),
4456    repeat_for_opts(fun match_delete_do/1,
4457                    [write_concurrency,all_types]),
4458    verify_etsmem(EtsMem).
4459
4460match_delete_do(Opts) ->
4461    EtsMem = etsmem(),
4462    Tab = ets_new(kad,Opts),
4463    fill_tab(Tab,foo),
4464    ets:insert(Tab,{{c,key},bar}),
4465    _ = ets:match_delete(Tab,{'_',foo}),
4466    [{{c,key},bar}] = ets:tab2list(Tab),
4467    _ = ets:match_delete(Tab,'_'),
4468    [] = ets:tab2list(Tab),
4469    true = ets:delete(Tab),
4470    verify_etsmem(EtsMem).
4471
4472%% OTP-3005: check match_delete with constant argument.
4473match_delete3(Config) when is_list(Config) ->
4474    repeat_for_opts(fun match_delete3_do/1).
4475
4476match_delete3_do(Opts) ->
4477    EtsMem = etsmem(),
4478    T = make_table(test,
4479		   [duplicate_bag | Opts],
4480		   [{aa,17},
4481		    {cA,1000},
4482		    {cA,17},
4483		    {cA,1000},
4484		    {aa,17}]),
4485    %% 'aa' and 'cA' have the same hash value in the current
4486    %% implementation. This causes the aa's to precede the cA's, to make
4487    %% the test more interesting.
4488    [{cA,1000},{cA,1000}] = ets:match_object(T, {'_', 1000}),
4489    ets:match_delete(T, {cA,1000}),
4490    [] = ets:match_object(T, {'_', 1000}),
4491    ets:delete(T),
4492    verify_etsmem(EtsMem).
4493
4494
4495%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4496
4497%% Test ets:first/1 & ets:next/2.
4498firstnext(Config) when is_list(Config) ->
4499    repeat_for_opts_all_set_table_types(fun firstnext_do/1).
4500
4501firstnext_do(Opts) ->
4502    EtsMem = etsmem(),
4503    Tab = ets_new(foo,Opts),
4504    [] = firstnext_collect(Tab,ets:first(Tab),[]),
4505    fill_tab(Tab,foo),
4506    Len = length(ets:tab2list(Tab)),
4507    Len = length(firstnext_collect(Tab,ets:first(Tab),[])),
4508    true = ets:delete(Tab),
4509    verify_etsmem(EtsMem).
4510
4511firstnext_collect(_Tab,'$end_of_table',List) ->
4512    List;
4513firstnext_collect(Tab,Key,List) ->
4514    firstnext_collect(Tab,ets:next(Tab,Key),[Key|List]).
4515
4516
4517%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4518
4519%% Tests ets:first/1 & ets:next/2.
4520firstnext_concurrent(Config) when is_list(Config) ->
4521    lists:foreach(
4522      fun(TableType) ->
4523              register(master, self()),
4524              TableName = list_to_atom(atom_to_list(?MODULE) ++ atom_to_list(TableType)),
4525              ets_init(TableName, 20, TableType),
4526              [dynamic_go(TableName) || _ <- lists:seq(1, 2)],
4527              receive
4528              after 5000 -> ok
4529              end,
4530              unregister(master)
4531      end, repeat_for_opts_atom2list(ord_set_types)).
4532
4533ets_init(Tab, N, TableType) ->
4534    ets_new(Tab, [named_table,public,TableType]),
4535    cycle(Tab, lists:seq(1,N+1)).
4536
4537cycle(_Tab, [H|T]) when H > length(T)-> ok;
4538cycle(Tab, L) ->
4539    ets:insert(Tab,list_to_tuple(L)),
4540    cycle(Tab, tl(L)++[hd(L)]).
4541
4542dynamic_go(TableName) -> my_spawn_link(fun() -> dynamic_init(TableName) end).
4543
4544dynamic_init(TableName) -> [dyn_lookup(TableName) || _ <- lists:seq(1, 10)].
4545
4546dyn_lookup(T) -> dyn_lookup(T, ets:first(T)).
4547
4548dyn_lookup(_T, '$end_of_table') -> [];
4549dyn_lookup(T, K) ->
4550    NextKey = ets:next(T,K),
4551    case ets:next(T,K) of
4552	NextKey ->
4553	    dyn_lookup(T, NextKey);
4554	NK ->
4555	    io:fwrite("hmmm... ~p =/= ~p~n", [NextKey,NK]),
4556	    exit(failed)
4557    end.
4558
4559%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4560
4561slot(Config) when is_list(Config) ->
4562    repeat_for_opts_all_set_table_types(fun slot_do/1).
4563
4564slot_do(Opts) ->
4565    EtsMem = etsmem(),
4566    Tab = ets_new(foo,Opts),
4567    fill_tab(Tab,foo),
4568    Elts = ets:info(Tab,size),
4569    Elts = slot_loop(Tab,0,0),
4570    case ets:info(Tab, type) of
4571        ordered_set ->
4572            '$end_of_table' = ets:slot(Tab,Elts);
4573        _ -> ok
4574    end,
4575    true = ets:delete(Tab),
4576    verify_etsmem(EtsMem).
4577
4578slot_loop(Tab,SlotNo,EltsSoFar) ->
4579    case ets:slot(Tab,SlotNo) of
4580	'$end_of_table' ->
4581	    {'EXIT',{badarg,_}} =
4582		(catch ets:slot(Tab,SlotNo+1)),
4583	    EltsSoFar;
4584	Elts ->
4585	    slot_loop(Tab,SlotNo+1,EltsSoFar+length(Elts))
4586    end.
4587
4588%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4589
4590hash_clash(Config) when is_list(Config) ->
4591    %% ensure that erlang:phash2 and ets:slot use different hash seed
4592    Tab = ets:new(tab, [set]),
4593    Buckets = erlang:element(1, ets:info(Tab, stats)),
4594    Phash = erlang:phash2(<<"123">>, Buckets),
4595    true = ets:insert(Tab, {<<"123">>, "extra"}),
4596    [] = ets:slot(Tab, Phash).
4597
4598%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4599
4600
4601match1(Config) when is_list(Config) ->
4602    repeat_for_opts_all_set_table_types(fun match1_do/1).
4603
4604match1_do(Opts) ->
4605    EtsMem = etsmem(),
4606    Tab = ets_new(foo,Opts),
4607    fill_tab(Tab,foo),
4608    [] = ets:match(Tab,{}),
4609    ets:insert(Tab,{{one,4},4}),
4610    ets:insert(Tab,{{one,5},5}),
4611    ets:insert(Tab,{{two,4},4}),
4612    ets:insert(Tab,{{two,5},6}),
4613    case ets:match(Tab,{{one,'_'},'$0'}) of
4614	[[4],[5]] -> ok;
4615	[[5],[4]] -> ok
4616    end,
4617    case ets:match(Tab,{{two,'$1'},'$0'}) of
4618	[[4,4],[6,5]] -> ok;
4619	[[6,5],[4,4]] -> ok
4620    end,
4621    case ets:match(Tab,{{two,'$9'},'$4'}) of
4622	[[4,4],[6,5]] -> ok;
4623	[[6,5],[4,4]] -> ok
4624    end,
4625    case ets:match(Tab,{{two,'$9'},'$22'}) of
4626	[[4,4],[5,6]] -> ok;
4627	[[5,6],[4,4]] -> ok
4628    end,
4629    [[4]] = ets:match(Tab,{{two,'$0'},'$0'}),
4630    Len = length(ets:match(Tab,'$0')),
4631    Len = length(ets:match(Tab,'_')),
4632    if Len > 4 -> ok end,
4633    true = ets:delete(Tab),
4634    verify_etsmem(EtsMem).
4635
4636%% Test match with specified keypos bag table.
4637match2(Config) when is_list(Config) ->
4638    repeat_for_opts(fun match2_do/1).
4639
4640match2_do(Opts) ->
4641    EtsMem = etsmem(),
4642    Tab = make_table(foobar,
4643		     [bag, named_table, {keypos, 2} | Opts],
4644		     [{value1, key1},
4645		      {value2_1, key2},
4646		      {value2_2, key2},
4647		      {value3_1, key3},
4648		      {value3_2, key3},
4649		      {value2_1, key2_wannabe}]),
4650    case length(ets:match(Tab, '$1')) of
4651	6 -> ok;
4652	_ -> ct:fail("Length of matched list is wrong.")
4653    end,
4654    [[value3_1],[value3_2]] = ets:match(Tab, {'$1', key3}),
4655    [[key1]] = ets:match(Tab, {value1, '$1'}),
4656    [[key2_wannabe],[key2]] = ets:match(Tab, {value2_1, '$2'}),
4657    [] = ets:match(Tab,{'$1',nosuchkey}),
4658    [] = ets:match(Tab,{'$1',kgY2}), % same hash as key2
4659    [] = ets:match(Tab,{nosuchvalue,'$1'}),
4660    true = ets:delete(Tab),
4661    verify_etsmem(EtsMem).
4662
4663%% Some ets:match_object tests.
4664match_object(Config) when is_list(Config) ->
4665    repeat_for_opts_all_set_table_types(fun match_object_do/1).
4666
4667match_object_do(Opts) ->
4668    EtsMem = etsmem(),
4669    Tab = ets_new(foobar, Opts),
4670    fill_tab(Tab, foo),
4671    ets:insert(Tab,{{one,4},4}),
4672    ets:insert(Tab,{{one,5},5}),
4673    ets:insert(Tab,{{two,4},4}),
4674    ets:insert(Tab,{{two,5},6}),
4675    ets:insert(Tab, {#{camembert=>cabécou},7}),
4676    ets:insert(Tab, {#{"hi"=>"hello","wazzup"=>"awesome","1337"=>"42"},8}),
4677    ets:insert(Tab, {#{"hi"=>"hello",#{"wazzup"=>3}=>"awesome","1337"=>"42"},9}),
4678    ets:insert(Tab, {#{"hi"=>"hello","wazzup"=>#{"awesome"=>3},"1337"=>"42"},10}),
4679    Is = lists:seq(1,100),
4680    M1 = maps:from_list([{I,I}||I <- Is]),
4681    M2 = maps:from_list([{I,"hi"}||I <- Is]),
4682    ets:insert(Tab, {M1,11}),
4683    ets:insert(Tab, {M2,12}),
4684
4685    case ets:match_object(Tab, {{one, '_'}, '$0'}) of
4686	[{{one,5},5},{{one,4},4}] -> ok;
4687	[{{one,4},4},{{one,5},5}] -> ok;
4688	_ -> ct:fail("ets:match_object() returned something funny.")
4689    end,
4690    case ets:match_object(Tab, {{two, '$1'}, '$0'}) of
4691	[{{two,5},6},{{two,4},4}] -> ok;
4692	[{{two,4},4},{{two,5},6}] -> ok;
4693	_ -> ct:fail("ets:match_object() returned something funny.")
4694    end,
4695    case ets:match_object(Tab, {{two, '$9'}, '$4'}) of
4696	[{{two,5},6},{{two,4},4}] -> ok;
4697	[{{two,4},4},{{two,5},6}] -> ok;
4698	_ -> ct:fail("ets:match_object() returned something funny.")
4699    end,
4700    case ets:match_object(Tab, {{two, '$9'}, '$22'}) of
4701	[{{two,5},6},{{two,4},4}] -> ok;
4702	[{{two,4},4},{{two,5},6}] -> ok;
4703	_ -> ct:fail("ets:match_object() returned something funny.")
4704    end,
4705
4706    %% Check that maps are inspected for variables.
4707    [{#{camembert:=cabécou},7}] = ets:match_object(Tab, {#{camembert=>'_'},7}),
4708
4709    [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] =
4710        ets:match_object(Tab, {#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>"42"},9}),
4711    [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] =
4712        ets:match_object(Tab, {#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>'_'},'_'}),
4713    [{#{"hi":="hello","wazzup":=#{"awesome":=3},"1337":="42"},10}] =
4714        ets:match_object(Tab, {#{"wazzup"=>'_',"hi"=>'_',"1337"=>'_'},10}),
4715
4716    %% multiple patterns
4717    Pat = {{#{#{"wazzup"=>3}=>"awesome","hi"=>"hello","1337"=>'_'},'$1'},[{is_integer,'$1'}],['$_']},
4718    [{#{"hi":="hello",#{"wazzup"=>3}:="awesome","1337":="42"},9}] =
4719        ets:select(Tab, [Pat,Pat,Pat,Pat]),
4720    case ets:match_object(Tab, {#{"hi"=>"hello","wazzup"=>'_',"1337"=>"42"},'_'}) of
4721        [{#{"1337" := "42","hi" := "hello","wazzup" := "awesome"},8},
4722         {#{"1337" := "42","hi" := "hello","wazzup" := #{"awesome" := 3}},10}] -> ok;
4723        [{#{"1337" := "42","hi" := "hello","wazzup" := #{"awesome" := 3}},10},
4724         {#{"1337" := "42","hi" := "hello","wazzup" := "awesome"},8}] -> ok;
4725        _ -> ct:fail("ets:match_object() returned something funny.")
4726    end,
4727    case ets:match_object(Tab, {#{"hi"=>'_'},'_'}) of
4728        [{#{"1337":="42", "hi":="hello"},_},
4729         {#{"1337":="42", "hi":="hello"},_},
4730         {#{"1337":="42", "hi":="hello"},_}] -> ok;
4731        _ -> ct:fail("ets:match_object() returned something funny.")
4732    end,
4733
4734    %% match large maps
4735    [{#{1:=1,2:=2,99:=99,100:=100},11}] = ets:match_object(Tab, {M1,11}),
4736    [{#{1:="hi",2:="hi",99:="hi",100:="hi"},12}] = ets:match_object(Tab, {M2,12}),
4737    case ets:match_object(Tab, {#{1=>'_',2=>'_'},'_'}) of
4738        %% only match a part of the map
4739        [{#{1:=1,5:=5,99:=99,100:=100},11},{#{1:="hi",6:="hi",99:="hi"},12}] -> ok;
4740        [{#{1:="hi",2:="hi",59:="hi"},12},{#{1:=1,2:=2,39:=39,100:=100},11}] -> ok;
4741        _ -> ct:fail("ets:match_object() returned something funny.")
4742    end,
4743    case ets:match_object(Tab, {maps:from_list([{I,'_'}||I<-Is]),'_'}) of
4744        %% only match a part of the map
4745        [{#{1:=1,5:=5,99:=99,100:=100},11},{#{1:="hi",6:="hi",99:="hi"},12}] -> ok;
4746        [{#{1:="hi",2:="hi",59:="hi"},12},{#{1:=1,2:=2,39:=39,100:=100},11}] -> ok;
4747        _ -> ct:fail("ets:match_object() returned something funny.")
4748    end,
4749    {'EXIT',{badarg,_}} = (catch ets:match_object(Tab, {#{'$1'=>'_'},7})),
4750    Mve = maps:from_list([{list_to_atom([$$|integer_to_list(I)]),'_'}||I<-Is]),
4751    {'EXIT',{badarg,_}} = (catch ets:match_object(Tab, {Mve,11})),
4752
4753    %% Check that unsuccessful match returns an empty list.
4754    [] = ets:match_object(Tab, {{three,'$0'}, '$92'}),
4755    %% Check that '$0' equals '_'.
4756    Len = length(ets:match_object(Tab, '$0')),
4757    Len = length(ets:match_object(Tab, '_')),
4758    if Len > 4 -> ok end,
4759    true = ets:delete(Tab),
4760    verify_etsmem(EtsMem).
4761
4762%% Tests that db_match_object does not generate a `badarg' when
4763%% resuming a search with no previous matches.
4764match_object2(Config) when is_list(Config) ->
4765    repeat_for_opts_all_table_types(fun match_object2_do/1).
4766
4767match_object2_do(Opts) ->
4768    EtsMem = etsmem(),
4769    KeyRange = 13005,
4770    Tab = ets_new(foo, [{keypos, 2} | Opts], KeyRange),
4771    fill_tab2(Tab, 0, KeyRange),     % match_db_object does 1000
4772						% elements per pass, might
4773						% change in the future.
4774    [] = ets:match_object(Tab, {hej, '$1'}),
4775    ets:delete(Tab),
4776    verify_etsmem(EtsMem).
4777
4778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4779
4780
4781%% OTP-3319. Test tab2list.
4782tab2list(Config) when is_list(Config) ->
4783    repeat_for_all_ord_set_table_types(
4784      fun(Opts) ->
4785              EtsMem = etsmem(),
4786              Tab = make_table(foo,
4787                               Opts,
4788                               [{a,b}, {c,b}, {b,b}, {a,c}]),
4789              [{a,c},{b,b},{c,b}] = ets:tab2list(Tab),
4790              true = ets:delete(Tab),
4791              verify_etsmem(EtsMem)
4792      end).
4793
4794%% Simple general small test.  If this fails, ets is in really bad
4795%% shape.
4796misc1(Config) when is_list(Config) ->
4797    repeat_for_opts_all_table_types(fun misc1_do/1).
4798
4799misc1_do(Opts) ->
4800    EtsMem = etsmem(),
4801    Tab = ets_new(foo,Opts),
4802    true = lists:member(Tab,ets:all()),
4803    ets:delete(Tab),
4804    false = lists:member(Tab,ets:all()),
4805    case catch ets:delete(Tab) of
4806	{'EXIT',_Reason} ->
4807	    verify_etsmem(EtsMem);
4808	true ->
4809	    ct:fail("Delete of nonexisting table returned `true'.")
4810    end,
4811    ok.
4812
4813%% Check the safe_fixtable function.
4814safe_fixtable(Config) when is_list(Config) ->
4815    repeat_for_opts_all_table_types(fun safe_fixtable_do/1).
4816
4817safe_fixtable_do(Opts) ->
4818    EtsMem = etsmem(),
4819    Tab = ets_new(foo, Opts),
4820    fill_tab(Tab, foobar),
4821    true = ets:safe_fixtable(Tab, true),
4822    receive after 1 -> ok end,
4823    true = ets:safe_fixtable(Tab, false),
4824    false = ets:info(Tab,safe_fixed_monotonic_time),
4825    false = ets:info(Tab,safe_fixed),
4826    SysBefore = erlang:timestamp(),
4827    MonBefore = erlang:monotonic_time(),
4828    true = ets:safe_fixtable(Tab, true),
4829    MonAfter = erlang:monotonic_time(),
4830    SysAfter = erlang:timestamp(),
4831    Self = self(),
4832    {FixMonTime,[{Self,1}]} = ets:info(Tab,safe_fixed_monotonic_time),
4833    {FixSysTime,[{Self,1}]} = ets:info(Tab,safe_fixed),
4834    true = is_integer(FixMonTime),
4835    true = MonBefore =< FixMonTime,
4836    true = FixMonTime =< MonAfter,
4837    {FstMs,FstS,FstUs} = FixSysTime,
4838    true = is_integer(FstMs),
4839    true = is_integer(FstS),
4840    true = is_integer(FstUs),
4841    case erlang:system_info(time_warp_mode) of
4842	no_time_warp ->
4843	    true = timer:now_diff(FixSysTime, SysBefore) >= 0,
4844	    true = timer:now_diff(SysAfter, FixSysTime) >= 0;
4845	_ ->
4846	    %% ets:info(Tab,safe_fixed) not timewarp safe...
4847	    ignore
4848    end,
4849    %% Test that an unjustified 'unfix' is a no-op.
4850    {Pid,MRef} = my_spawn_monitor(fun() -> true = ets:safe_fixtable(Tab,false) end),
4851    {'DOWN', MRef, process, Pid, normal} = receive M -> M end,
4852    true = ets:info(Tab,fixed),
4853    {FixMonTime,[{Self,1}]} = ets:info(Tab,safe_fixed_monotonic_time),
4854    {FixSysTime,[{Self,1}]} = ets:info(Tab,safe_fixed),
4855    %% badarg's
4856    {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
4857    true = ets:info(Tab,fixed),
4858    true = ets:safe_fixtable(Tab, false),
4859    false = ets:info(Tab,fixed),
4860    {'EXIT', {badarg, _}} = (catch ets:safe_fixtable(Tab, foobar)),
4861    false = ets:info(Tab,fixed),
4862    ets:delete(Tab),
4863    case catch ets:safe_fixtable(Tab, true) of
4864	{'EXIT', _Reason} ->
4865	    verify_etsmem(EtsMem);
4866	_ ->
4867	    ct:fail("Fixtable on nonexisting table returned `true'")
4868    end,
4869    ok.
4870
4871-define(ets_info(Tab,Item,SlavePid), ets_info(Tab, Item, SlavePid, ?LINE)).
4872
4873%% Tests ets:info result for required tuples.
4874info(Config) when is_list(Config) ->
4875    repeat_for_opts(fun info_do/1,
4876                    [[void, set, bag, duplicate_bag, ordered_set],
4877                     [void, private, protected, public],
4878                     write_concurrency, read_concurrency, compressed]),
4879
4880    undefined = ets:info(non_existing_table_xxyy),
4881    undefined = ets:info(non_existing_table_xxyy,type),
4882    undefined = ets:info(non_existing_table_xxyy,node),
4883    undefined = ets:info(non_existing_table_xxyy,named_table),
4884    undefined = ets:info(non_existing_table_xxyy,safe_fixed_monotonic_time),
4885    undefined = ets:info(non_existing_table_xxyy,safe_fixed),
4886
4887    {'EXIT',{badarg,_}} = (catch ets:info(42)),
4888    {'EXIT',{badarg,_}} = (catch ets:info(42, type)),
4889    {'EXIT',{badarg,_}} = (catch ets:info(make_ref())),
4890    {'EXIT',{badarg,_}} = (catch ets:info(make_ref(), type)),
4891
4892    ok.
4893
4894info_do(Opts) ->
4895    EtsMem = etsmem(),
4896    TableType = lists:foldl(
4897                  fun(Item, Curr) ->
4898                          case Item of
4899                              set -> set;
4900                              ordered_set -> ordered_set;
4901                              cat_ord_set -> ordered_set;
4902                              stim_cat_ord_set -> ordered_set;
4903                              bag -> bag;
4904                              duplicate_bag -> duplicate_bag;
4905                              _ -> Curr
4906                          end
4907                  end, set, Opts),
4908    PublicOrCurr =
4909        fun(Curr) ->
4910                case lists:member({write_concurrency, false}, Opts) or
4911                    lists:member(private, Opts) or
4912                    lists:member(protected, Opts) of
4913                    true -> Curr;
4914                    false -> public
4915                end
4916        end,
4917    Protection = lists:foldl(
4918                   fun(Item, Curr) ->
4919                           case Item of
4920                               public -> public;
4921                               protected -> protected;
4922                               private -> private;
4923                               cat_ord_set -> PublicOrCurr(Curr); %% Special items
4924                               stim_cat_ord_set -> PublicOrCurr(Curr);
4925                               _ -> Curr
4926                           end
4927                   end, protected, Opts),
4928    MeMyselfI=self(),
4929    ThisNode=node(),
4930    Tab = ets_new(foobar, [{keypos, 2} | Opts]),
4931
4932    %% Start slave to also do ets:info from a process not owning the table.
4933    SlavePid = spawn_link(fun Slave() ->
4934                                  receive
4935                                      {Master, Item} ->
4936                                          Master ! {self(), Item, ets:info(Tab, Item)}
4937                                  end,
4938                                  Slave()
4939                          end),
4940
4941    %% Note: ets:info/1 used to return a tuple, but from R11B onwards it
4942    %% returns a list.
4943    Res = ets:info(Tab),
4944    {value, {memory, _Mem}} = lists:keysearch(memory, 1, Res),
4945    {value, {owner, MeMyselfI}} = lists:keysearch(owner, 1, Res),
4946    {value, {name, foobar}} = lists:keysearch(name, 1, Res),
4947    {value, {size, 0}} = lists:keysearch(size, 1, Res),
4948    {value, {node, ThisNode}} = lists:keysearch(node, 1, Res),
4949    {value, {named_table, false}} = lists:keysearch(named_table, 1, Res),
4950    {value, {type, TableType}} = lists:keysearch(type, 1, Res),
4951    {value, {keypos, 2}} = lists:keysearch(keypos, 1, Res),
4952    {value, {protection, Protection}} =
4953	lists:keysearch(protection, 1, Res),
4954    {value, {id, Tab}} = lists:keysearch(id, 1, Res),
4955    {value, {decentralized_counters, _DecentralizedCtrs}} =
4956        lists:keysearch(decentralized_counters, 1, Res),
4957
4958    %% Test 'binary'
4959    [] = ?ets_info(Tab, binary, SlavePid),
4960    BinSz = 100,
4961    RefcBin = list_to_binary(lists:seq(1,BinSz)),
4962    ets:insert(Tab, {RefcBin,key}),
4963    [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid),
4964    ets:insert(Tab, {RefcBin,key2}),
4965    [{BinPtr,BinSz,3}, {BinPtr,BinSz,3}] = ?ets_info(Tab,binary,SlavePid),
4966    ets:delete(Tab, key),
4967    [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid),
4968    case TableType of
4969        ordered_set ->
4970            ets:delete(Tab, key2);
4971        _ ->
4972            ets:safe_fixtable(Tab, true),
4973            ets:delete(Tab, key2),
4974            [{BinPtr,BinSz,2}] = ?ets_info(Tab,binary, SlavePid),
4975            ets:safe_fixtable(Tab, false)
4976    end,
4977    [] = ?ets_info(Tab,binary, SlavePid),
4978    RefcBin = id(RefcBin), % keep alive
4979
4980    unlink(SlavePid),
4981    exit(SlavePid,kill),
4982
4983    true = ets:delete(Tab),
4984    verify_etsmem(EtsMem).
4985
4986ets_info(Tab, Item, SlavePid, _Line) ->
4987    R = ets:info(Tab, Item),
4988    %%io:format("~p: ets:info(~p) -> ~p\n", [_Line, Item, R]),
4989    SlavePid ! {self(), Item},
4990    {SlavePid, Item, R} = receive M -> M end,
4991    R.
4992
4993
4994
4995info_binary_stress(_Config) ->
4996    repeat_for_opts(fun info_binary_stress_do/1,
4997                    [[set,bag,duplicate_bag,ordered_set],
4998                     compressed]).
4999
5000info_binary_stress_do(Opts) ->
5001    Tab = ets_new(info_binary_stress, [public, {write_concurrency,true} | Opts]),
5002
5003    KeyRange = 1000,
5004    ValueRange = 3,
5005    RefcBin = list_to_binary(lists:seq(1,100)),
5006    InitF = fun (_) -> #{insert => 0, delete => 0, delete_object => 0}
5007            end,
5008    ExecF = fun (Counters) ->
5009                    Key = rand:uniform(KeyRange),
5010                    Value = rand:uniform(ValueRange),
5011                    Op = element(rand:uniform(4),{insert,insert,delete,delete_object}),
5012                    case Op of
5013                        insert ->
5014                            ets:insert(Tab, {Key,Value,RefcBin});
5015                        delete ->
5016                            ets:delete(Tab, Key);
5017                        delete_object ->
5018                            ets:delete_object(Tab, {Key,Value,RefcBin})
5019                    end,
5020                    Acc = incr_counter(Op, Counters),
5021
5022                    receive stop ->
5023                                [end_of_work | Acc]
5024                    after 0 ->
5025                            Acc
5026                    end
5027            end,
5028    FiniF = fun (Acc) -> Acc end,
5029    Pids = run_sched_workers(InitF, ExecF, FiniF, infinite),
5030    timer:send_after(500, stop),
5031
5032    Rounds = fun Loop(N, Fix) ->
5033                     ets:info(Tab, binary),
5034                     ets:safe_fixtable(Tab, Fix),
5035                     receive
5036                         stop ->
5037                             ets:safe_fixtable(Tab, false),
5038                             false = ets:info(Tab, fixed),
5039                             N
5040                     after 0 ->
5041                             Loop(N+1, not Fix)
5042                     end
5043             end (1, true),
5044    [P ! stop || P <- Pids],
5045    Results = wait_pids(Pids),
5046    Size = ets:info(Tab,size),
5047    io:format("Ops = ~p\n", [maps_sum(Results)]),
5048    io:format("Size = ~p\n", [Size]),
5049    io:format("Stats = ~p\n", [ets:info(Tab,stats)]),
5050    io:format("Rounds = ~p\n", [Rounds]),
5051    Size = length(ets:info(Tab, binary)),
5052
5053    ets:delete_all_objects(Tab),
5054    [] = ets:info(Tab, binary),
5055    true = ets:delete(Tab),
5056    ok.
5057
5058
5059size_loop(_T, 0, _, _) ->
5060    ok;
5061size_loop(T, I, PrevSize, WhatToTest) ->
5062    Size = ets:info(T, WhatToTest),
5063    case Size < PrevSize of
5064        true ->
5065            io:format("Bad ets:info/2 (got ~p expected >=~p)",
5066                      [Size, PrevSize]),
5067            ct:fail("Bad ets:info/2)");
5068        _ -> ok
5069    end,
5070    size_loop(T, I -1, Size, WhatToTest).
5071
5072add_loop(_T, 0) ->
5073    ok;
5074add_loop(T, I) ->
5075    ets:insert(T, {I}),
5076    add_loop(T, I -1).
5077
5078
5079test_table_counter_concurrency(WhatToTest, TableOptions) ->
5080    IntStatePrevOn =
5081        erts_debug:set_internal_state(available_internal_state, true),
5082    ItemsToAdd = 1000000,
5083    SizeLoopSize = 1000,
5084    T = ets:new(k, TableOptions),
5085    case lists:member(ordered_set, TableOptions) of
5086        true ->
5087            erts_debug:set_internal_state(ets_debug_random_split_join, {T, false});
5088        false -> ok
5089    end,
5090    0 = ets:info(T, size),
5091    P = self(),
5092    SpawnedSizeProcs =
5093        [spawn_link(fun() ->
5094                            size_loop(T, SizeLoopSize, 0, WhatToTest),
5095                            P ! done
5096                    end)
5097         || _ <- lists:seq(1, 6)],
5098    spawn_link(fun() ->
5099                       add_loop(T, ItemsToAdd),
5100                       P ! done_add
5101               end),
5102    [receive
5103         done -> ok;
5104         done_add -> ok
5105     end
5106     || _ <- [ok|SpawnedSizeProcs]],
5107    case WhatToTest =:= size of
5108        true ->
5109            ItemsToAdd = ets:info(T, size);
5110        _ ->
5111            ok
5112    end,
5113    erts_debug:set_internal_state(available_internal_state, IntStatePrevOn),
5114    ok.
5115
5116test_table_size_concurrency(Config) when is_list(Config) ->
5117    case erlang:system_info(schedulers) of
5118        1 -> {skip,"Only valid on smp > 1 systems"};
5119        _ ->
5120            BaseOptions = [public, {write_concurrency, true}],
5121            test_table_counter_concurrency(size, [set | BaseOptions]),
5122            test_table_counter_concurrency(size, [ordered_set | BaseOptions])
5123    end.
5124
5125test_table_memory_concurrency(Config) when is_list(Config) ->
5126    case erlang:system_info(schedulers) of
5127        1 -> {skip,"Only valid on smp > 1 systems"};
5128        _ ->
5129            BaseOptions = [public, {write_concurrency, true}],
5130            test_table_counter_concurrency(memory, [set | BaseOptions]),
5131            test_table_counter_concurrency(memory, [ordered_set | BaseOptions])
5132    end.
5133
5134%% Tests that calling the ets:delete operation on a table T with
5135%% decentralized counters works while ets:info(T, size) operations are
5136%% active
5137test_delete_table_while_size_snapshot(Config) when is_list(Config) ->
5138    %% Run test case in a slave node as other test suites in stdlib
5139    %% depend on that pids are ordered in creation order which is no
5140    %% longer the case when many processes have been started before
5141    Node = start_slave(),
5142    [ok = rpc:call(Node,
5143                   ?MODULE,
5144                   test_delete_table_while_size_snapshot_helper,
5145                   [TableType])
5146     || TableType <- [set, ordered_set]],
5147    test_server:stop_node(Node),
5148    ok.
5149
5150test_delete_table_while_size_snapshot_helper(TableType) ->
5151    TopParent = self(),
5152    repeat_par(
5153      fun() ->
5154              Table = ets:new(t, [public, TableType,
5155                                  {decentralized_counters, true},
5156                                  {write_concurrency, true}]),
5157              Parent = self(),
5158              NrOfSizeProcs = 100,
5159              Pids = [ spawn(fun()-> size_process(Table, Parent) end)
5160                       || _ <- lists:seq(1, NrOfSizeProcs)],
5161              timer:sleep(1),
5162              ets:delete(Table),
5163              [receive
5164                   table_gone ->  ok;
5165                   Problem -> TopParent ! Problem
5166               end || _ <- Pids]
5167      end,
5168      100*erlang:system_info(schedulers_online)),
5169    receive
5170        Problem -> throw(Problem)
5171    after 0 -> ok
5172    end.
5173
5174size_process(Table, Parent) ->
5175    try ets:info(Table, size) of
5176        N when is_integer(N) ->
5177            size_process(Table, Parent);
5178        undefined -> Parent ! table_gone;
5179        E -> Parent ! {got_unexpected, E}
5180    catch
5181        E -> Parent ! {got_unexpected_exception, E}
5182    end.
5183
5184start_slave() ->
5185    MicroSecs = erlang:monotonic_time(),
5186    Name = "ets_" ++ integer_to_list(MicroSecs),
5187    Pa = filename:dirname(code:which(?MODULE)),
5188    {ok, Node} = test_server:start_node(list_to_atom(Name), slave, [{args, "-pa " ++ Pa}]),
5189    Node.
5190
5191repeat_par(FunToRepeat, NrOfTimes) ->
5192    repeat_par_help(FunToRepeat, NrOfTimes, NrOfTimes).
5193
5194repeat_par_help(_FunToRepeat, 0, OrgNrOfTimes) ->
5195    repeat(fun()-> receive done -> ok end end, OrgNrOfTimes);
5196repeat_par_help(FunToRepeat, NrOfTimes, OrgNrOfTimes) ->
5197    Parent = self(),
5198    case NrOfTimes rem 5 of
5199        0 -> timer:sleep(1);
5200        _ -> ok
5201    end,
5202    spawn(fun()->
5203                  FunToRepeat(),
5204                  Parent ! done
5205          end),
5206    repeat_par_help(FunToRepeat, NrOfTimes-1, OrgNrOfTimes).
5207
5208test_decentralized_counters_setting(Config) when is_list(Config) ->
5209    case erlang:system_info(schedulers) of
5210        1 -> {skip,"Only relevant when the number of shedulers > 1"};
5211        _ -> EtsMem = etsmem(),
5212             do_test_decentralized_counters_setting(set),
5213             do_test_decentralized_counters_setting(ordered_set),
5214             do_test_decentralized_counters_default_setting(),
5215             verify_etsmem(EtsMem)
5216    end.
5217
5218do_test_decentralized_counters_setting(TableType) ->
5219    wait_for_memory_deallocations(),
5220    FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage),
5221    lists:foreach(
5222      fun(OptList) ->
5223              T1 = ets:new(t1, [public, TableType] ++ OptList ++ [TableType]),
5224              check_decentralized_counters(T1, false, FlxCtrMemUsage),
5225              ets:delete(T1)
5226      end,
5227      [[{write_concurrency, false}],
5228       [{write_concurrency, true}, {decentralized_counters, false}]]),
5229    lists:foreach(
5230      fun(OptList) ->
5231              T1 = ets:new(t1, [public,
5232                                TableType,
5233                                {write_concurrency, true}] ++ OptList ++ [TableType]),
5234              check_decentralized_counters(T1, true, FlxCtrMemUsage),
5235              ets:delete(T1),
5236              wait_for_memory_deallocations(),
5237              FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage)
5238      end,
5239      [[{decentralized_counters, true}]]),
5240    ok.
5241
5242do_test_decentralized_counters_default_setting() ->
5243    wait_for_memory_deallocations(),
5244    FlxCtrMemUsage = erts_debug:get_internal_state(flxctr_memory_usage),
5245    Set = ets:new(t1, [public, {write_concurrency, true}]),
5246    check_decentralized_counters(Set, false, FlxCtrMemUsage),
5247    ets:delete(Set),
5248    Set2 = ets:new(t1, [public, set, {write_concurrency, true}]),
5249    check_decentralized_counters(Set2, false, FlxCtrMemUsage),
5250    ets:delete(Set2),
5251    OrdSet = ets:new(t1, [public, ordered_set, {write_concurrency, true}]),
5252    check_decentralized_counters(OrdSet, true, FlxCtrMemUsage),
5253    ets:delete(OrdSet),
5254    ok.
5255
5256check_decentralized_counters(T, ExpectedState, InitMemUsage) ->
5257    case {ExpectedState, erts_debug:get_internal_state(flxctr_memory_usage)} of
5258        {false, notsup} -> ok;
5259        {false, X} -> InitMemUsage = X;
5260        {true, notsup} -> ok;
5261        {true, X} when X > InitMemUsage -> ok;
5262        {true, _} -> ct:fail("Decentralized counter not used.")
5263    end,
5264    ExpectedState = ets:info(T, decentralized_counters).
5265
5266%% Test various duplicate_bags stuff.
5267dups(Config) when is_list(Config) ->
5268    repeat_for_opts(fun dups_do/1).
5269
5270dups_do(Opts) ->
5271    EtsMem = etsmem(),
5272    T = make_table(funky,
5273		   [duplicate_bag | Opts],
5274		   [{1, 2}, {1, 2}]),
5275    2 = length(ets:tab2list(T)),
5276    ets:delete(T, 1),
5277    [] = ets:lookup(T, 1),
5278
5279    ets:insert(T, {1, 2, 2}),
5280    ets:insert(T, {1, 2, 4}),
5281    ets:insert(T, {1, 2, 2}),
5282    ets:insert(T, {1, 2, 2}),
5283    ets:insert(T, {1, 2, 4}),
5284
5285    5 = length(ets:tab2list(T)),
5286
5287    5 = length(ets:match(T, {'$1', 2, '$2'})),
5288    3 = length(ets:match(T, {'_', '$1', '$1'})),
5289    ets:match_delete(T, {'_', '$1', '$1'}),
5290    0 = length(ets:match(T, {'_', '$1', '$1'})),
5291    ets:delete(T),
5292    verify_etsmem(EtsMem).
5293
5294%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5295
5296%% Test the ets:tab2file function on an empty ets table.
5297tab2file(Config) when is_list(Config) ->
5298    FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file_case"]),
5299    tab2file_do(FName, [], set),
5300    tab2file_do(FName, [], ordered_set),
5301    tab2file_do(FName, [], cat_ord_set),
5302    tab2file_do(FName, [], stim_cat_ord_set),
5303    tab2file_do(FName, [{sync,true}], set),
5304    tab2file_do(FName, [{sync,false}], set),
5305    {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [{sync,yes}], set)),
5306    {'EXIT',{{badmatch,{error,_}},_}} = (catch tab2file_do(FName, [sync], set)),
5307    ok.
5308
5309tab2file_do(FName, Opts, TableType) ->
5310    %% Write an empty ets table to a file, read back and check properties.
5311    Tab = ets_new(ets_SUITE_foo_tab, [named_table, TableType, public,
5312				      {keypos, 2},
5313				      compressed,
5314				      {write_concurrency,true},
5315				      {read_concurrency,true}]),
5316    ActualTableType =
5317        case TableType of
5318            cat_ord_set -> ordered_set;
5319            stim_cat_ord_set -> ordered_set;
5320            _ -> TableType
5321        end,
5322    catch file:delete(FName),
5323    Res = ets:tab2file(Tab, FName, Opts),
5324    true = ets:delete(Tab),
5325    ok = Res,
5326    %%
5327    EtsMem = etsmem(),
5328    {ok, Tab2} = ets:file2tab(FName),
5329    public = ets:info(Tab2, protection),
5330    true = ets:info(Tab2, named_table),
5331    2 = ets:info(Tab2, keypos),
5332    ActualTableType = ets:info(Tab2, type),
5333    true = ets:info(Tab2, compressed),
5334    Smp = erlang:system_info(smp_support),
5335    Smp = ets:info(Tab2, read_concurrency),
5336    Smp = ets:info(Tab2, write_concurrency) orelse erlang:system_info(schedulers) == 1,
5337    true = ets:delete(Tab2),
5338    verify_etsmem(EtsMem).
5339
5340
5341%% Check the ets:tab2file function on a filled set/bag type ets table.
5342tab2file2(Config) when is_list(Config) ->
5343    repeat_for_opts(fun(Opts) ->
5344                            tab2file2_do(Opts, Config)
5345                    end, [[stim_cat_ord_set,cat_ord_set,set,bag],compressed]).
5346
5347tab2file2_do(Opts, Config) ->
5348    EtsMem = etsmem(),
5349    KeyRange = 10000,
5350    Tab = ets_new(ets_SUITE_foo_tab, [named_table, private, {keypos, 2} | Opts],
5351                  KeyRange),
5352    FName = filename:join([proplists:get_value(priv_dir, Config),"tab2file2_case"]),
5353    ok = fill_tab2(Tab, 0, KeyRange),   % Fill up the table (grucho mucho!)
5354    Len = length(ets:tab2list(Tab)),
5355    Mem = ets:info(Tab, memory),
5356    Type = ets:info(Tab, type),
5357    %%io:format("org tab: ~p\n",[ets:info(Tab)]),
5358    ok = ets:tab2file(Tab, FName),
5359    true = ets:delete(Tab),
5360
5361    EtsMem4 = etsmem(),
5362
5363    {ok, Tab2} = ets:file2tab(FName),
5364    %%io:format("loaded tab: ~p\n",[ets:info(Tab2)]),
5365    private = ets:info(Tab2, protection),
5366    true = ets:info(Tab2, named_table),
5367    2 = ets:info(Tab2, keypos),
5368    Type = ets:info(Tab2, type),
5369    Len = length(ets:tab2list(Tab2)),
5370    Mem = ets:info(Tab2, memory),
5371    true = ets:delete(Tab2),
5372    io:format("Between = ~p\n", [EtsMem4]),
5373    verify_etsmem(EtsMem).
5374
5375-define(test_list, [8,5,4,1,58,125,255, 250, 245, 240, 235,
5376		    230, Num rem 255, 255, 125, 130, 135, 140, 145,
5377		    150, 134, 12, 54, Val rem 255, 12, 3, 6, 9, 126]).
5378-define(big_test_list, [Num rem 256|lists:seq(1, 66)]).
5379-define(test_integer, 2846287468+Num).
5380-define(test_float, 187263.18236-Val).
5381-define(test_atom, some_crazy_atom).
5382-define(test_tuple, {just, 'Some', 'Tuple', 1, [list, item], Val+Num}).
5383
5384%% Insert different datatypes into a ets table.
5385fill_tab2(_Tab, _Val, 0) ->
5386    ok;
5387fill_tab2(Tab, Val, Num) ->
5388    Item =
5389	case Num rem 10 of
5390	    0 -> "String";
5391	    1 -> ?test_atom;
5392	    2 -> ?test_tuple;
5393	    3 -> ?test_integer;
5394	    4 -> ?test_float;
5395	    5 -> list_to_binary(?test_list); %Heap binary
5396	    6 -> list_to_binary(?big_test_list); %Refc binary
5397	    7 -> make_sub_binary(?test_list, Num); %Sub binary
5398	    8 -> ?test_list;
5399	    9 -> fun(X) -> {Tab,Val,X*Num} end
5400	end,
5401    true=ets:insert(Tab, {Item, Val}),
5402    fill_tab2(Tab, Val+1, Num-1),
5403    ok.
5404
5405%% Test verification of tables with object count extended_info.
5406tabfile_ext1(Config) when is_list(Config) ->
5407    repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext1_do(Opts, Config) end).
5408
5409tabfile_ext1_do(Opts,Config) ->
5410    FName = filename:join([proplists:get_value(priv_dir, Config),"nisse.dat"]),
5411    FName2 = filename:join([proplists:get_value(priv_dir, Config),"countflip.dat"]),
5412    KeyRange = 10,
5413    L = lists:seq(1,KeyRange),
5414    T = ets_new(x,Opts,KeyRange),
5415    Name = make_ref(),
5416    [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
5417    ok = ets:tab2file(T,FName,[{extended_info,[object_count]}]),
5418    true = lists:sort(ets:tab2list(T)) =:=
5419	lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
5420    true = lists:sort(ets:tab2list(T)) =:=
5421	lists:sort(ets:tab2list(
5422		     element(2,ets:file2tab(FName,[{verify,true}])))),
5423    {ok,Name} = disk_log:open([{name,Name},{file,FName}]),
5424    {_,[H0|T0]} = disk_log:chunk(Name,start),
5425    disk_log:close(Name),
5426    LH0=tuple_to_list(H0),
5427    {value,{size,N}}=lists:keysearch(size,1,LH0),
5428    NewLH0 = lists:keyreplace(size,1,LH0,{size,N-1}),
5429    NewH0 = list_to_tuple(NewLH0),
5430    NewT0=lists:keydelete(8,1,T0),
5431    file:delete(FName2),
5432    disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
5433    disk_log:log_terms(Name,[NewH0|NewT0]),
5434    disk_log:close(Name),
5435    9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
5436    {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
5437    {ok, _} = ets:tabfile_info(FName2),
5438    {ok, _} = ets:tabfile_info(FName),
5439    file:delete(FName),
5440    file:delete(FName2),
5441    ok.
5442
5443
5444%% Test verification of tables with md5sum extended_info.
5445tabfile_ext2(Config) when is_list(Config) ->
5446    repeat_for_opts_all_set_table_types(fun(Opts) -> tabfile_ext2_do(Opts,Config) end).
5447
5448tabfile_ext2_do(Opts,Config) ->
5449    FName = filename:join([proplists:get_value(priv_dir, Config),"olle.dat"]),
5450    FName2 = filename:join([proplists:get_value(priv_dir, Config),"bitflip.dat"]),
5451    KeyRange = 10,
5452    L = lists:seq(1, KeyRange),
5453    T = ets_new(x, Opts, KeyRange),
5454    Name = make_ref(),
5455    [ets:insert(T,{X,integer_to_list(X)}) || X <- L],
5456    ok = ets:tab2file(T,FName,[{extended_info,[md5sum]}]),
5457    true = lists:sort(ets:tab2list(T)) =:=
5458	lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
5459    true = lists:sort(ets:tab2list(T)) =:=
5460	lists:sort(ets:tab2list(
5461		     element(2,ets:file2tab(FName,[{verify,true}])))),
5462    {ok, Name} = disk_log:open([{name,Name},{file,FName}]),
5463    {_,[H1|T1]} = disk_log:chunk(Name,start),
5464    disk_log:close(Name),
5465    NewT1=lists:keyreplace(8,1,T1,{8,"9"}),
5466    file:delete(FName2),
5467    disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
5468    disk_log:log_terms(Name,[H1|NewT1]),
5469    disk_log:close(Name),
5470    {value,{8,"9"}} = lists:keysearch(8,1,
5471				      ets:tab2list(
5472					element(2,ets:file2tab(FName2)))),
5473    {error,checksum_error} = ets:file2tab(FName2,[{verify,true}]),
5474    {value,{extended_info,[md5sum]}} =
5475	lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName2))),
5476    {value,{extended_info,[md5sum]}} =
5477	lists:keysearch(extended_info,1,element(2,ets:tabfile_info(FName))),
5478    file:delete(FName),
5479    file:delete(FName2),
5480    ok.
5481
5482%% Test verification of (named) tables without extended info.
5483tabfile_ext3(Config) when is_list(Config) ->
5484    repeat_for_all_set_table_types(
5485      fun(Opts) ->
5486              FName = filename:join([proplists:get_value(priv_dir, Config),"namn.dat"]),
5487              FName2 = filename:join([proplists:get_value(priv_dir, Config),"ncountflip.dat"]),
5488              L = lists:seq(1,10),
5489              Name = make_ref(),
5490              ?MODULE = ets_new(?MODULE,[named_table|Opts]),
5491              [ets:insert(?MODULE,{X,integer_to_list(X)}) || X <- L],
5492              ets:tab2file(?MODULE,FName),
5493              {error,cannot_create_table} = ets:file2tab(FName),
5494              true = ets:delete(?MODULE),
5495              {ok,?MODULE} = ets:file2tab(FName),
5496              true = ets:delete(?MODULE),
5497              disk_log:open([{name,Name},{file,FName}]),
5498              {_,[H2|T2]} = disk_log:chunk(Name,start),
5499              disk_log:close(Name),
5500              NewT2=lists:keydelete(8,1,T2),
5501              file:delete(FName2),
5502              disk_log:open([{name,Name},{file,FName2},{mode,read_write}]),
5503              disk_log:log_terms(Name,[H2|NewT2]),
5504              disk_log:close(Name),
5505              9 = length(ets:tab2list(element(2,ets:file2tab(FName2)))),
5506              true = ets:delete(?MODULE),
5507              {error,invalid_object_count} = ets:file2tab(FName2,[{verify,true}]),
5508              {'EXIT',_} = (catch ets:delete(?MODULE)),
5509              {ok,_} = ets:tabfile_info(FName2),
5510              {ok,_} = ets:tabfile_info(FName),
5511              file:delete(FName),
5512              file:delete(FName2)
5513      end),
5514    ok.
5515
5516%% Tests verification of large table with md5 sum.
5517tabfile_ext4(Config) when is_list(Config) ->
5518    repeat_for_all_set_table_types(
5519      fun(Opts) ->
5520              FName = filename:join([proplists:get_value(priv_dir, Config),"bauta.dat"]),
5521              LL = lists:seq(1,10000),
5522              TL = ets_new(x,Opts),
5523              Name2 = make_ref(),
5524              [ets:insert(TL,{X,integer_to_list(X)}) || X <- LL],
5525              ok = ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
5526              {ok, Name2} = disk_log:open([{name, Name2}, {file, FName},
5527                                           {mode, read_only}]),
5528              {C,[_|_]} = disk_log:chunk(Name2,start),
5529              {_,[_|_]} = disk_log:chunk(Name2,C),
5530              disk_log:close(Name2),
5531              true = lists:sort(ets:tab2list(TL)) =:=
5532                  lists:sort(ets:tab2list(element(2,ets:file2tab(FName)))),
5533              Res = [begin
5534                         {ok,FD} = file:open(FName,[binary,read,write]),
5535                         {ok, Bin} = file:pread(FD,0,1000),
5536                         <<B1:N/binary,Ch:8,B2/binary>> = Bin,
5537                         Ch2 = (Ch + 1) rem 255,
5538                         Bin2 = <<B1/binary,Ch2:8,B2/binary>>,
5539                         ok = file:pwrite(FD,0,Bin2),
5540                         ok = file:close(FD),
5541                         X = case ets:file2tab(FName) of
5542                                 {ok,TL2} ->
5543                                     true = lists:sort(ets:tab2list(TL)) =/=
5544                                         lists:sort(ets:tab2list(TL2));
5545                                 _ ->
5546                                     totally_broken
5547                             end,
5548                         {error,Y} = ets:file2tab(FName,[{verify,true}]),
5549                         ets:tab2file(TL,FName,[{extended_info,[md5sum]}]),
5550                         {X,Y}
5551                     end || N <- lists:seq(700,800)],
5552              io:format("~p~n",[Res]),
5553              file:delete(FName)
5554      end),
5555    ok.
5556
5557%% Test that no disk_log is left open when file has been corrupted.
5558badfile(Config) when is_list(Config) ->
5559    PrivDir = proplists:get_value(priv_dir,Config),
5560    File = filename:join(PrivDir, "badfile"),
5561    _ = file:delete(File),
5562    T = ets:new(table, []),
5563    true = ets:insert(T, [{a,1},{b,2}]),
5564    ok = ets:tab2file(T, File, []),
5565    true = ets:delete(T),
5566    [H0 | Ts ] = get_all_terms(l, File),
5567    H1 = tuple_to_list(H0),
5568    H2 = [{K,V} || {K,V} <- H1, K =/= protection],
5569    H = list_to_tuple(H2),
5570    ok = file:delete(File),
5571    write_terms(l, File, [H | Ts]),
5572    %% All mandatory keys are no longer members of the header
5573    {error, badfile} = ets:file2tab(File),
5574    {error, badfile} = ets:tabfile_info(File),
5575    file:delete(File),
5576    [] = disk_log:all(),
5577    ok.
5578
5579get_all_terms(Log, File) ->
5580    {ok, Log} = disk_log:open([{name,Log},
5581                               {file, File},
5582                               {mode, read_only}]),
5583    Ts = get_all_terms(Log),
5584    ok = disk_log:close(Log),
5585    Ts.
5586
5587get_all_terms(Log) ->
5588    get_all_terms1(Log, start, []).
5589
5590get_all_terms1(Log, Cont, Res) ->
5591    case disk_log:chunk(Log, Cont) of
5592	{error, _R} ->
5593            throw(fel);
5594	{Cont2, Terms} ->
5595	    get_all_terms1(Log, Cont2, Res ++ Terms);
5596	eof ->
5597	    Res
5598    end.
5599
5600write_terms(Log, File, Terms) ->
5601    {ok, Log} = disk_log:open([{name,Log},{file, File},{mode,read_write}]),
5602    ok = disk_log:log(Log, Terms),
5603    ok = disk_log:close(Log).
5604
5605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5606
5607make_sub_binary(List, Num) when is_list(List) ->
5608    N = Num rem 23,
5609    Bin = list_to_binary([lists:seq(0, N)|List]),
5610    {_,B} = split_binary(Bin, N+1),
5611    B.
5612
5613
5614%% Lookup stuff like crazy...
5615
5616%% Perform multiple lookups for every key in a large table.
5617heavy_lookup(Config) when is_list(Config) ->
5618    repeat_for_opts_all_set_table_types(fun heavy_lookup_do/1).
5619
5620heavy_lookup_do(Opts) ->
5621    EtsMem = etsmem(),
5622    KeyRange = 7000,
5623    Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange),
5624    ok = fill_tab2(Tab, 0, KeyRange),
5625    _ = [do_lookup(Tab, KeyRange-1) || _ <- lists:seq(1, 50)],
5626    true = ets:delete(Tab),
5627    verify_etsmem(EtsMem).
5628
5629do_lookup(_Tab, 0) -> ok;
5630do_lookup(Tab, N) ->
5631    case ets:lookup(Tab, N) of
5632	[] ->
5633	    io:format("Set #~p was reported as empty. Not valid.",
5634		      [N]),
5635	    exit('Invalid lookup');
5636	_ ->
5637	    do_lookup(Tab, N-1)
5638    end.
5639
5640%% Perform multiple lookups for every element in a large table.
5641heavy_lookup_element(Config) when is_list(Config) ->
5642    repeat_for_opts_all_set_table_types(fun heavy_lookup_element_do/1).
5643
5644heavy_lookup_element_do(Opts) ->
5645    EtsMem = etsmem(),
5646    KeyRange = 7000,
5647    Tab = ets_new(foobar_table, [{keypos, 2} | Opts], KeyRange),
5648    ok = fill_tab2(Tab, 0, KeyRange),
5649    %% lookup ALL elements 50 times
5650    Laps = 50 div syrup_factor(),
5651    _ = [do_lookup_element(Tab, KeyRange-1, 1) || _ <- lists:seq(1, Laps)],
5652    true = ets:delete(Tab),
5653    verify_etsmem(EtsMem).
5654
5655do_lookup_element(_Tab, 0, _) -> ok;
5656do_lookup_element(Tab, N, M) ->
5657    case catch ets:lookup_element(Tab, N, M) of
5658	{'EXIT', {badarg, _}} ->
5659	    case M of
5660		1 -> ct:fail("Set #~p reported as empty. Not valid.",
5661			     [N]),
5662		     exit('Invalid lookup_element');
5663		_ -> do_lookup_element(Tab, N-1, 1)
5664	    end;
5665	_ -> do_lookup_element(Tab, N, M+1)
5666    end.
5667
5668
5669heavy_concurrent(Config) when is_list(Config) ->
5670    ct:timetrap({minutes,120}), %% valgrind needs a lot of time
5671    repeat_for_opts_all_set_table_types(fun do_heavy_concurrent/1).
5672
5673do_heavy_concurrent(Opts) ->
5674    KeyRange = 10000,
5675    Laps = 10000 div syrup_factor(),
5676    EtsMem = etsmem(),
5677    Tab = ets_new(blupp, [public, {keypos, 2} | Opts], KeyRange),
5678    ok = fill_tab2(Tab, 0, KeyRange),
5679    Procs = lists:map(
5680	      fun (N) ->
5681		      my_spawn_link(
5682			fun () ->
5683				do_heavy_concurrent_proc(Tab, Laps, N)
5684			end)
5685	      end,
5686	      lists:seq(1, 500)),
5687    lists:foreach(fun (P) ->
5688			  M = erlang:monitor(process, P),
5689			  receive
5690			      {'DOWN', M, process, P, _} ->
5691				  ok
5692			  end
5693		  end,
5694		  Procs),
5695    true = ets:delete(Tab),
5696    verify_etsmem(EtsMem).
5697
5698do_heavy_concurrent_proc(_Tab, 0, _Offs) ->
5699    done;
5700do_heavy_concurrent_proc(Tab, N, Offs) when (N+Offs) rem 100 == 0 ->
5701    Data = {"here", are, "S O M E ", data, "toooooooooooooooooo", insert,
5702	    make_ref(), make_ref(), make_ref()},
5703    true=ets:insert(Tab, {{self(),Data}, N}),
5704    do_heavy_concurrent_proc(Tab, N-1, Offs);
5705do_heavy_concurrent_proc(Tab, N, Offs) ->
5706    _ = ets:lookup(Tab, N),
5707    do_heavy_concurrent_proc(Tab, N-1, Offs).
5708
5709
5710fold_empty(Config) when is_list(Config) ->
5711    repeat_for_opts_all_set_table_types(
5712      fun(Opts) ->
5713              EtsMem = etsmem(),
5714              Tab = make_table(a, Opts, []),
5715              [] = ets:foldl(fun(_X) -> exit(hej) end, [], Tab),
5716              [] = ets:foldr(fun(_X) -> exit(hej) end, [], Tab),
5717              true = ets:delete(Tab),
5718              verify_etsmem(EtsMem)
5719      end),
5720    ok.
5721
5722foldl(Config) when is_list(Config) ->
5723    repeat_for_opts_all_table_types(
5724      fun(Opts) ->
5725              EtsMem = etsmem(),
5726              L = [{a,1}, {c,3}, {b,2}],
5727              LS = lists:sort(L),
5728              Tab = make_table(a, Opts, L),
5729              LS = lists:sort(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
5730              true = ets:delete(Tab),
5731              verify_etsmem(EtsMem)
5732      end),
5733    ok.
5734
5735foldr(Config) when is_list(Config) ->
5736    repeat_for_opts_all_table_types(
5737      fun(Opts) ->
5738              EtsMem = etsmem(),
5739              L = [{a,1}, {c,3}, {b,2}],
5740              LS = lists:sort(L),
5741              Tab = make_table(a, Opts, L),
5742              LS = lists:sort(ets:foldr(fun(E,A) -> [E|A] end, [], Tab)),
5743              true = ets:delete(Tab),
5744              verify_etsmem(EtsMem)
5745      end),
5746    ok.
5747
5748foldl_ordered(Config) when is_list(Config) ->
5749    repeat_for_opts_all_ord_set_table_types(
5750      fun(Opts) ->
5751              EtsMem = etsmem(),
5752              L = [{a,1}, {c,3}, {b,2}],
5753              LS = lists:sort(L),
5754              Tab = make_table(a, Opts, L),
5755              LS = lists:reverse(ets:foldl(fun(E,A) -> [E|A] end, [], Tab)),
5756              true = ets:delete(Tab),
5757              verify_etsmem(EtsMem)
5758      end),
5759    ok.
5760
5761foldr_ordered(Config) when is_list(Config) ->
5762    repeat_for_opts_all_ord_set_table_types(
5763      fun(Opts) ->
5764              EtsMem = etsmem(),
5765              L = [{a,1}, {c,3}, {b,2}],
5766              LS = lists:sort(L),
5767              Tab = make_table(a, Opts, L),
5768              LS = ets:foldr(fun(E,A) -> [E|A] end, [], Tab),
5769              true = ets:delete(Tab),
5770              verify_etsmem(EtsMem)
5771      end),
5772    ok.
5773
5774%% Test ets:member BIF.
5775member(Config) when is_list(Config) ->
5776    repeat_for_opts(fun member_do/1, [write_concurrency, all_types]).
5777
5778member_do(Opts) ->
5779    EtsMem = etsmem(),
5780    T = ets_new(xxx, Opts),
5781    false = ets:member(T,hej),
5782    E = fun(0,_F)->ok;
5783	   (N,F) ->
5784		ets:insert(T,{N,N rem 10}),
5785		F(N-1,F)
5786	end,
5787    E(10000,E),
5788    false = ets:member(T,hej),
5789    true = ets:member(T,1),
5790    false = ets:member(T,20000),
5791    ets:delete(T,5),
5792    false = ets:member(T,5),
5793    ets:safe_fixtable(T,true),
5794    ets:delete(T,6),
5795    false = ets:member(T,6),
5796    ets:safe_fixtable(T,false),
5797    false = ets:member(T,6),
5798    ets:delete(T),
5799    {'EXIT',{badarg,_}} = (catch ets:member(finnsinte, 23)),
5800    {'EXIT',{badarg,_}} = (catch ets:member(T, 23)),
5801    verify_etsmem(EtsMem).
5802
5803
5804build_table(L1,L2,Num) ->
5805    T = ets_new(xxx, [ordered_set]),
5806    lists:foreach(
5807      fun(X1) ->
5808	      lists:foreach(
5809		fun(X2) ->
5810			F = fun(FF,N) ->
5811				    ets:insert(T,{{X1,X2,N}, X1, X2, N}),
5812				    case N of
5813					0 ->
5814					    ok;
5815					_ ->
5816					    FF(FF,N-1)
5817				    end
5818			    end,
5819			F(F,Num)
5820		end, L2)
5821      end, L1),
5822    T.
5823
5824build_table2(L1,L2,Num) ->
5825    T = ets_new(xxx, [ordered_set]),
5826    lists:foreach(
5827      fun(X1) ->
5828	      lists:foreach(
5829		fun(X2) ->
5830			F = fun(FF,N) ->
5831				    ets:insert(T,{{N,X1,X2}, N, X1, X2}),
5832				    case N of
5833					0 ->
5834					    ok;
5835					_ ->
5836					    FF(FF,N-1)
5837				    end
5838			    end,
5839			F(F,Num)
5840		end, L2)
5841      end, L1),
5842    T.
5843
5844time_match_object(Tab,Match, Res) ->
5845    T1 = erlang:monotonic_time(microsecond),
5846    Res = ets:match_object(Tab,Match),
5847    T2 = erlang:monotonic_time(microsecond),
5848    T2 - T1.
5849
5850time_match(Tab,Match) ->
5851    T1 = erlang:monotonic_time(microsecond),
5852    ets:match(Tab,Match),
5853    T2 = erlang:monotonic_time(microsecond),
5854    T2 - T1.
5855
5856seventyfive_percent_success(_,S,Fa,0) ->
5857    true = (S > ((S + Fa) * 0.75));
5858
5859seventyfive_percent_success(F, S, Fa, N) when is_function(F, 0) ->
5860    try F() of
5861        _ ->
5862	    seventyfive_percent_success(F, S+1, Fa, N-1)
5863    catch error:_ ->
5864	    seventyfive_percent_success(F, S, Fa+1, N-1)
5865    end.
5866
5867fifty_percent_success(_,S,Fa,0) ->
5868    true = (S > ((S + Fa) * 0.5));
5869
5870fifty_percent_success(F, S, Fa, N) when is_function(F, 0) ->
5871    try F() of
5872        _ ->
5873	    fifty_percent_success(F, S+1, Fa, N-1)
5874    catch
5875        error:_ ->
5876	    fifty_percent_success(F, S, Fa+1, N-1)
5877    end.
5878
5879create_random_string(0) ->
5880    [];
5881
5882create_random_string(OfLength) ->
5883    C = case rand:uniform(2) of
5884	    1 ->
5885		(rand:uniform($Z - $A + 1) - 1) + $A;
5886	    _ ->
5887		(rand:uniform($z - $a + 1) - 1) + $a
5888	end,
5889    [C | create_random_string(OfLength - 1)].
5890
5891
5892create_random_tuple(OfLength) ->
5893    list_to_tuple(lists:map(fun(X) ->
5894				    list_to_atom([X])
5895			    end,create_random_string(OfLength))).
5896
5897create_partly_bound_tuple(OfLength) ->
5898    case rand:uniform(2) of
5899	1 ->
5900	    create_partly_bound_tuple1(OfLength);
5901	_ ->
5902	    create_partly_bound_tuple3(OfLength)
5903    end.
5904
5905create_partly_bound_tuple1(OfLength) ->
5906    T0 = create_random_tuple(OfLength),
5907    I = rand:uniform(OfLength),
5908    setelement(I,T0,'$1').
5909
5910
5911set_n_random_elements(T0,0,_,_) ->
5912    T0;
5913set_n_random_elements(T0,N,OfLength,GenFun) ->
5914    I = rand:uniform(OfLength),
5915    What = GenFun(I),
5916    case element(I,T0) of
5917	What ->
5918	    set_n_random_elements(T0,N,OfLength,GenFun);
5919	_Else ->
5920	    set_n_random_elements(setelement(I,T0,What),
5921				  N-1,OfLength,GenFun)
5922    end.
5923
5924make_dollar_atom(I) ->
5925    list_to_atom([$$] ++ integer_to_list(I)).
5926create_partly_bound_tuple2(OfLength) ->
5927    T0 = create_random_tuple(OfLength),
5928    I = rand:uniform(OfLength - 1),
5929    set_n_random_elements(T0,I,OfLength,fun make_dollar_atom/1).
5930
5931create_partly_bound_tuple3(OfLength) ->
5932    T0 = create_random_tuple(OfLength),
5933    I = rand:uniform(OfLength - 1),
5934    set_n_random_elements(T0,I,OfLength,fun(_) -> '_' end).
5935
5936do_n_times(_,0) ->
5937    ok;
5938do_n_times(Fun,N) ->
5939    Fun(),
5940    case N rem 1000 of
5941	0 ->
5942	    io:format(".");
5943	_ ->
5944	    ok
5945    end,
5946    do_n_times(Fun,N-1).
5947
5948make_table(Name, Options, Elements) ->
5949    T = ets_new(Name, Options),
5950    lists:foreach(fun(E) -> ets:insert(T, E) end, Elements),
5951    T.
5952
5953filltabint(Tab,0) ->
5954    Tab;
5955filltabint(Tab,N) ->
5956    ets:insert(Tab,{N,integer_to_list(N)}),
5957    filltabint(Tab,N-1).
5958
5959filltabint2(Tab,0) ->
5960    Tab;
5961filltabint2(Tab,N) ->
5962    ets:insert(Tab,{N + N rem 2,integer_to_list(N)}),
5963    filltabint2(Tab,N-1).
5964filltabint3(Tab,0) ->
5965    Tab;
5966filltabint3(Tab,N) ->
5967    ets:insert(Tab,{N + N rem 2,integer_to_list(N + N rem 2)}),
5968    filltabint3(Tab,N-1).
5969xfilltabint(Tab,N) ->
5970    case ets:info(Tab,type) of
5971	bag ->
5972	    filltabint2(Tab,N);
5973	duplicate_bag ->
5974	    ets:select_delete(Tab,[{'_',[],[true]}]),
5975	    filltabint3(Tab,N);
5976	_ ->
5977	    filltabint(Tab,N)
5978    end.
5979
5980filltabintup(Tab,0) ->
5981    Tab;
5982filltabintup(Tab,N) ->
5983    ets:insert(Tab,{{N,integer_to_list(N)},integer_to_list(N)}),
5984    filltabintup(Tab,N-1).
5985
5986filltabintup2(Tab,0) ->
5987    Tab;
5988filltabintup2(Tab,N) ->
5989    ets:insert(Tab,{{N + N rem 2,integer_to_list(N)},integer_to_list(N)}),
5990    filltabintup2(Tab,N-1).
5991filltabintup3(Tab,0) ->
5992    Tab;
5993filltabintup3(Tab,N) ->
5994    ets:insert(Tab,{{N + N rem 2,integer_to_list(N + N rem 2)},integer_to_list(N + N rem 2)}),
5995    filltabintup3(Tab,N-1).
5996
5997filltabstr(Tab,N) ->
5998    filltabstr(Tab,0,N).
5999filltabstr(Tab,N,N) ->
6000    Tab;
6001filltabstr(Tab,Floor,N) when N > Floor ->
6002    ets:insert(Tab,{integer_to_list(N),N}),
6003    filltabstr(Tab,Floor,N-1).
6004
6005filltabstr2(Tab,0) ->
6006    Tab;
6007filltabstr2(Tab,N) ->
6008    ets:insert(Tab,{integer_to_list(N),N}),
6009    ets:insert(Tab,{integer_to_list(N),N+1}),
6010    filltabstr2(Tab,N-1).
6011filltabstr3(Tab,0) ->
6012    Tab;
6013filltabstr3(Tab,N) ->
6014    ets:insert(Tab,{integer_to_list(N),N}),
6015    ets:insert(Tab,{integer_to_list(N),N}),
6016    filltabstr3(Tab,N-1).
6017xfilltabstr(Tab,N) ->
6018    case ets:info(Tab,type) of
6019	bag ->
6020	    filltabstr2(Tab,N);
6021	duplicate_bag ->
6022	    ets:select_delete(Tab,[{'_',[],[true]}]),
6023	    filltabstr3(Tab,N);
6024	_ ->
6025	    filltabstr(Tab,N)
6026    end.
6027
6028fill_sets_int(N) ->
6029    fill_sets_int(N,[]).
6030fill_sets_int(N,Opts) ->
6031    Tab1 = ets_new(xxx, [ordered_set|Opts]),
6032    filltabint(Tab1,N),
6033    Tab2 = ets_new(xxx, [set|Opts]),
6034    filltabint(Tab2,N),
6035    Tab3 = ets_new(xxx, [bag|Opts]),
6036    filltabint2(Tab3,N),
6037    Tab4 = ets_new(xxx, [duplicate_bag|Opts]),
6038    filltabint3(Tab4,N),
6039    [Tab1,Tab2,Tab3,Tab4].
6040
6041fill_sets_intup(N,Opts) ->
6042    Tab1 = ets_new(xxx, [ordered_set|Opts]),
6043    filltabintup(Tab1,N),
6044    Tab2 = ets_new(xxx, [set|Opts]),
6045    filltabintup(Tab2,N),
6046    Tab3 = ets_new(xxx, [bag|Opts]),
6047    filltabintup2(Tab3,N),
6048    Tab4 = ets_new(xxx, [duplicate_bag|Opts]),
6049    filltabintup3(Tab4,N),
6050    [Tab1,Tab2,Tab3,Tab4].
6051
6052check_fun(_Tab,_Fun,'$end_of_table') ->
6053    ok;
6054check_fun(Tab,Fun,Item) ->
6055    lists:foreach(fun(Obj) ->
6056			  true = Fun(Obj)
6057		  end,
6058		  ets:lookup(Tab,Item)),
6059    check_fun(Tab,Fun,ets:next(Tab,Item)).
6060
6061check(Tab,Fun,N) ->
6062    N = ets:info(Tab, size),
6063    check_fun(Tab,Fun,ets:first(Tab)).
6064
6065
6066
6067del_one_by_one_set(T,N,N) ->
6068    0 = ets:info(T,size),
6069    ok;
6070del_one_by_one_set(T,From,To) ->
6071    N = ets:info(T,size),
6072    ets:delete_object(T,{From, integer_to_list(From)}),
6073    N = (ets:info(T,size) + 1),
6074    Next = if
6075	       From < To ->
6076		   From + 1;
6077	       true ->
6078		   From - 1
6079	   end,
6080    del_one_by_one_set(T,Next,To).
6081
6082del_one_by_one_bag(T,N,N) ->
6083    0 = ets:info(T,size),
6084    ok;
6085del_one_by_one_bag(T,From,To) ->
6086    N = ets:info(T,size),
6087    ets:delete_object(T,{From + From rem 2, integer_to_list(From)}),
6088    N = (ets:info(T,size) + 1),
6089    Next = if
6090	       From < To ->
6091		   From + 1;
6092	       true ->
6093		   From - 1
6094	   end,
6095    del_one_by_one_bag(T,Next,To).
6096
6097
6098del_one_by_one_dbag_1(T,N,N) ->
6099    0 = ets:info(T,size),
6100    ok;
6101del_one_by_one_dbag_1(T,From,To) ->
6102    N = ets:info(T,size),
6103    ets:delete_object(T,{From, integer_to_list(From)}),
6104    case From rem 2 of
6105	0 ->
6106	    N = (ets:info(T,size) + 2);
6107	1 ->
6108	    N = ets:info(T,size)
6109    end,
6110    Next = if
6111	       From < To ->
6112		   From + 1;
6113	       true ->
6114		   From - 1
6115	   end,
6116    del_one_by_one_dbag_1(T,Next,To).
6117
6118del_one_by_one_dbag_2(T,N,N) ->
6119    0 = ets:info(T,size),
6120    ok;
6121del_one_by_one_dbag_2(T,From,To) ->
6122    N = ets:info(T,size),
6123    ets:delete_object(T,{From, integer_to_list(From)}),
6124    case From rem 2 of
6125	0 ->
6126	    N = (ets:info(T,size) + 3);
6127	1 ->
6128	    N = (ets:info(T,size) + 1)
6129    end,
6130    Next = if
6131	       From < To ->
6132		   From + 1;
6133	       true ->
6134		   From - 1
6135	   end,
6136    del_one_by_one_dbag_2(T,Next,To).
6137
6138del_one_by_one_dbag_3(T,N,N) ->
6139    0 = ets:info(T,size),
6140    ok;
6141del_one_by_one_dbag_3(T,From,To) ->
6142    N = ets:info(T,size),
6143    Obj = {From + From rem 2, integer_to_list(From)},
6144    ets:delete_object(T,Obj),
6145    case From rem 2 of
6146	0 ->
6147	    N = (ets:info(T,size) + 2);
6148	1 ->
6149	    N = (ets:info(T,size) + 1),
6150	    Obj2 = {From, integer_to_list(From)},
6151	    ets:delete_object(T,Obj2),
6152	    N = (ets:info(T,size) + 2)
6153    end,
6154    Next = if
6155	       From < To ->
6156		   From + 1;
6157	       true ->
6158		   From - 1
6159	   end,
6160    del_one_by_one_dbag_3(T,Next,To).
6161
6162
6163successive_delete(Table,From,To,Type) ->
6164    successive_delete(Table,From,To,Type,ets:info(Table,type)).
6165
6166successive_delete(_Table,N,N,_,_) ->
6167    ok;
6168successive_delete(Table,From,To,Type,TType) ->
6169    MS = case Type of
6170	     bound ->
6171		 [{{From,'_'},[],[true]}];
6172	     unbound ->
6173		 [{{'$1','_'},[],[{'==', '$1', From}]}]
6174	 end,
6175    case TType of
6176	X when X == bag; X == duplicate_bag ->
6177	    %%erlang:display(From),
6178	    case From rem 2 of
6179		0 ->
6180		    2 = ets:select_delete(Table,MS);
6181		_ ->
6182		    0 = ets:select_delete(Table,MS)
6183	    end;
6184	_ ->
6185	    1 = ets:select_delete(Table,MS)
6186    end,
6187    Next = if
6188	       From < To ->
6189		   From + 1;
6190	       true ->
6191		   From - 1
6192	   end,
6193    successive_delete(Table, Next, To, Type,TType).
6194
6195gen_dets_filename(Config,N) ->
6196    filename:join(proplists:get_value(priv_dir,Config),
6197		  "testdets_" ++ integer_to_list(N) ++ ".dets").
6198
6199otp_6842_select_1000(Config) when is_list(Config) ->
6200    repeat_for_opts_all_ord_set_table_types(
6201      fun(Opts) ->
6202              KeyRange = 10000,
6203              Tab = ets_new(xxx, Opts, KeyRange),
6204              [ets:insert(Tab,{X,X}) || X <- lists:seq(1,KeyRange)],
6205              AllTrue = lists:duplicate(10,true),
6206              AllTrue =
6207                  [ length(
6208                      element(1,
6209                              ets:select(Tab,[{'_',[],['$_']}],X*1000))) =:=
6210                        X*1000 || X <- lists:seq(1,10) ],
6211              Sequences = [[1000,1000,1000,1000,1000,1000,1000,1000,1000,1000],
6212                           [2000,2000,2000,2000,2000],
6213                           [3000,3000,3000,1000],
6214                           [4000,4000,2000],
6215                           [5000,5000],
6216                           [6000,4000],
6217                           [7000,3000],
6218                           [8000,2000],
6219                           [9000,1000],
6220                           [10000]],
6221              AllTrue = [ check_seq(Tab, ets:select(Tab,[{'_',[],['$_']}],hd(L)),L) ||
6222                            L <- Sequences ],
6223              ets:delete(Tab)
6224      end),
6225    ok.
6226
6227check_seq(_,'$end_of_table',[]) ->
6228    true;
6229check_seq(Tab,{L,C},[H|T]) when length(L) =:= H ->
6230    check_seq(Tab, ets:select(C),T);
6231check_seq(A,B,C) ->
6232    erlang:display({A,B,C}),
6233    false.
6234
6235otp_6338(Config) when is_list(Config) ->
6236    repeat_for_opts_all_ord_set_table_types(
6237      fun(Opts) ->
6238              L = binary_to_term(<<131,108,0,0,0,2,104,2,108,0,0,0,2,103,100,0,19,112,112,
6239                                   98,49,95,98,115,49,50,64,98,108,97,100,101,95,48,95,53,
6240                                   0,0,33,50,0,0,0,4,1,98,0,0,23,226,106,100,0,4,101,120,
6241                                   105,116,104,2,108,0,0,0,2,104,2,100,0,3,115,98,109,100,
6242                                   0,19,112,112,98,50,95,98,115,49,50,64,98,108,97,100,
6243                                   101,95,48,95,56,98,0,0,18,231,106,100,0,4,114,101,99,
6244                                   118,106>>),
6245              T = ets_new(xxx,Opts),
6246              lists:foreach(fun(X) -> ets:insert(T,X) end,L),
6247              [[4839,recv]] = ets:match(T,{[{sbm,ppb2_bs12@blade_0_8},'$1'],'$2'}),
6248              ets:delete(T)
6249      end),
6250    ok.
6251
6252%% OTP-15660: Verify select not doing excessive trapping
6253%%            when process have mbuf heap fragments.
6254select_mbuf_trapping(Config) when is_list(Config) ->
6255    select_mbuf_trapping_do(set),
6256    select_mbuf_trapping_do(ordered_set).
6257
6258select_mbuf_trapping_do(Type) ->
6259    T = ets:new(xxx, [Type]),
6260    NKeys = 50,
6261    [ets:insert(T, {K, value}) || K <- lists:seq(1,NKeys)],
6262
6263    {priority, Prio} = process_info(self(), priority),
6264    Tracee = self(),
6265    [SchedTracer]
6266	= start_loopers(1, Prio,
6267			fun (SC) ->
6268				receive
6269				    {trace, Tracee, out, _} ->
6270					SC+1;
6271				    done ->
6272					Tracee ! {schedule_count, SC},
6273                                        exit(normal)
6274				end
6275			end,
6276			0),
6277
6278    erlang:garbage_collect(),
6279    1 = erlang:trace(self(), true, [running,{tracer,SchedTracer}]),
6280
6281    %% Artificially create an mbuf heap fragment
6282    MbufTerm = "Frag me up",
6283    MbufTerm = erts_debug:set_internal_state(mbuf, MbufTerm),
6284
6285    Keys = ets:select(T, [{{'$1', value}, [], ['$1']}]),
6286    NKeys = length(Keys),
6287
6288    1 = erlang:trace(self(), false, [running]),
6289    Ref = erlang:trace_delivered(Tracee),
6290    receive
6291        {trace_delivered, Tracee, Ref} ->
6292            SchedTracer ! done
6293    end,
6294    receive
6295	{schedule_count, N} ->
6296	    io:format("~p context switches: ~p", [Type,N]),
6297	    if
6298		N < 3 -> ok;
6299		true -> ct:fail(failed)
6300	    end
6301    end,
6302    true = ets:delete(T),
6303    ok.
6304
6305
6306
6307%% Elements could come in the wrong order in a bag if a rehash occurred.
6308otp_5340(Config) when is_list(Config) ->
6309    repeat_for_opts(fun otp_5340_do/1).
6310
6311otp_5340_do(Opts) ->
6312    N = 3000,
6313    T = ets_new(otp_5340, [bag,public | Opts]),
6314    Ids = [1,2,3,4,5],
6315    [w(T, N, Id) || Id <- Ids],
6316    verify(T, Ids),
6317    ets:delete(T).
6318
6319w(_,0, _) -> ok;
6320w(T,N, Id) ->
6321    ets:insert(T, {N, Id}),
6322    w(T,N-1,Id).
6323
6324verify(T, Ids) ->
6325    List = my_tab_to_list(T),
6326    Errors = lists:filter(fun(Bucket) ->
6327				  verify2(Bucket, Ids)
6328			  end, List),
6329    case Errors of
6330	[] ->
6331	    ok;
6332	_ ->
6333	    io:format("Failed:\n~p\n", [Errors]),
6334	    ct:fail(failed)
6335    end.
6336
6337verify2([{_N,Id}|RL], [Id|R]) ->
6338    verify2(RL,R);
6339verify2([],[]) -> false;
6340verify2(_Err, _) ->
6341    true.
6342
6343%% delete_object followed by delete on fixed bag failed to delete objects.
6344otp_7665(Config) when is_list(Config) ->
6345    repeat_for_opts(fun otp_7665_do/1).
6346
6347otp_7665_do(Opts) ->
6348    Tab = ets_new(otp_7665,[bag | Opts]),
6349    Min = 0,
6350    Max = 10,
6351    lists:foreach(fun(N)-> otp_7665_act(Tab,Min,Max,N) end,
6352		  lists:seq(Min,Max)),
6353    true = ets:delete(Tab).
6354
6355otp_7665_act(Tab,Min,Max,DelNr) ->
6356    List1 = [{key,N} || N <- lists:seq(Min,Max)],
6357    true = ets:insert(Tab, List1),
6358    true = ets:safe_fixtable(Tab, true),
6359    true = ets:delete_object(Tab, {key,DelNr}),
6360    List2 = lists:sort(lists:delete({key,DelNr}, List1)),
6361
6362    %% Now verify that we find all remaining objects
6363    List2 = lists:sort(ets:lookup(Tab,key)),
6364    EList2 = lists:sort(lists:map(fun({key,N})-> N end,
6365                                  List2)),
6366    EList2 = lists:sort(ets:lookup_element(Tab,key,2)),
6367    true = ets:delete(Tab, key),
6368    [] = ets:lookup(Tab, key),
6369    true = ets:safe_fixtable(Tab, false),
6370    ok.
6371
6372%% Whitebox testing of meta name table hashing.
6373meta_wb(Config) when is_list(Config) ->
6374    EtsMem = etsmem(),
6375    repeat_for_opts_all_non_stim_table_types(fun meta_wb_do/1),
6376    verify_etsmem(EtsMem).
6377
6378
6379meta_wb_do(Opts) ->
6380    %% Do random new/delete/rename of colliding named tables
6381    Names0 = [pioneer | colliding_names(pioneer)],
6382
6383    %% Remove any names that happen to exist as tables already
6384    Names = lists:filter(fun(Name) -> ets:info(Name) == undefined end,
6385                         Names0),
6386    Len = length(Names),
6387    OpFuns = {fun meta_wb_new/4, fun meta_wb_delete/4, fun meta_wb_rename/4},
6388
6389    true = (Len >= 3),
6390
6391    io:format("Colliding names = ~p\n",[Names]),
6392    F = fun(0,_,_) -> ok;
6393	   (N,Tabs,Me) ->
6394		Name1 = lists:nth(rand:uniform(Len), Names),
6395		Name2 = lists:nth(rand:uniform(Len), Names),
6396		Op = element(rand:uniform(3),OpFuns),
6397		NTabs = Op(Name1, Name2, Tabs, Opts),
6398		Me(N-1, NTabs, Me)
6399	end,
6400    F(Len*100, [], F),
6401
6402    %% cleanup
6403    lists:foreach(fun(Name)->catch ets:delete(Name) end,
6404		  Names).
6405
6406meta_wb_new(Name, _, Tabs, Opts) ->
6407    case (catch ets_new(Name,[named_table|Opts])) of
6408	Name ->
6409	    false = lists:member(Name, Tabs),
6410	    [Name | Tabs];
6411	{'EXIT',{badarg,_}} ->
6412	    true = lists:member(Name, Tabs),
6413	    Tabs
6414    end.
6415meta_wb_delete(Name, _, Tabs, _) ->
6416    case (catch ets:delete(Name)) of
6417	true ->
6418	    true = lists:member(Name, Tabs),
6419	    lists:delete(Name, Tabs);
6420	{'EXIT',{badarg,_}} ->
6421	    false = lists:member(Name, Tabs),
6422	    Tabs
6423    end.
6424meta_wb_rename(Old, New, Tabs, _) ->
6425    case (catch ets:rename(Old,New)) of
6426	New ->
6427	    true = lists:member(Old, Tabs)
6428		andalso not lists:member(New, Tabs),
6429	    [New | lists:delete(Old, Tabs)];
6430	{'EXIT',{badarg,_}} ->
6431	    true = not lists:member(Old, Tabs)
6432		orelse lists:member(New,Tabs),
6433	    Tabs
6434    end.
6435
6436
6437colliding_names(Name) ->
6438    erts_debug:set_internal_state(colliding_names, {Name,5}).
6439
6440
6441%% OTP_6913: Grow and shrink.
6442
6443grow_shrink(Config) when is_list(Config) ->
6444    repeat_for_all_set_table_types(
6445      fun(Opts) ->
6446              EtsMem = etsmem(),
6447
6448              Set = ets_new(a, Opts, 5000),
6449              grow_shrink_0(0, 3071, 3000, 5000, Set),
6450              ets:delete(Set),
6451
6452              verify_etsmem(EtsMem)
6453      end).
6454
6455grow_shrink_0(N, _, _, Max, _) when N >= Max ->
6456    ok;
6457grow_shrink_0(N0, GrowN, ShrinkN, Max, T) ->
6458    N1 = grow_shrink_1(N0, GrowN, ShrinkN, T),
6459    grow_shrink_0(N1, GrowN, ShrinkN, Max, T).
6460
6461grow_shrink_1(N0, GrowN, ShrinkN, T) ->
6462    N1 = grow_shrink_2(N0+1, N0 + GrowN, T),
6463    grow_shrink_3(N1, N1 - ShrinkN, T).
6464
6465grow_shrink_2(N, GrowTo, _) when N > GrowTo ->
6466    GrowTo;
6467grow_shrink_2(N, GrowTo, T) ->
6468    true = ets:insert(T, {N,a}),
6469    grow_shrink_2(N+1, GrowTo, T).
6470
6471grow_shrink_3(N, ShrinkTo, _) when N =< ShrinkTo ->
6472    ShrinkTo;
6473grow_shrink_3(N, ShrinkTo, T) ->
6474    true = ets:delete(T, N),
6475    grow_shrink_3(N-1, ShrinkTo, T).
6476
6477%% Grow a hash table that still contains pseudo-deleted objects.
6478grow_pseudo_deleted(Config) when is_list(Config) ->
6479    only_if_smp(fun() -> grow_pseudo_deleted_do() end).
6480
6481grow_pseudo_deleted_do() ->
6482    lists:foreach(fun(Type) -> grow_pseudo_deleted_do(Type) end,
6483		  [set,bag,duplicate_bag]).
6484
6485grow_pseudo_deleted_do(Type) ->
6486    process_flag(scheduler,1),
6487    Self = self(),
6488    T = ets_new(kalle,[Type,public,{write_concurrency,true}]),
6489    Mod = 7, Mult = 10000,
6490    filltabint(T,Mod*Mult),
6491    true = ets:safe_fixtable(T,true),
6492    Mult = ets:select_delete(T,
6493			     [{{'$1', '_'},
6494			       [{'=:=', {'rem', '$1', Mod}, 0}],
6495			       [true]}]),
6496    Left = Mult*(Mod-1),
6497    Left = ets:info(T,size),
6498    Mult = get_kept_objects(T),
6499    filltabstr(T,Mult),
6500    my_spawn_opt(
6501      fun() ->
6502	      true = ets:info(T,fixed),
6503	      Self ! start,
6504	      io:put_chars("Starting to filltabstr...\n"),
6505	      do_tc(fun() ->
6506			    filltabstr(T, Mult, Mult+10000)
6507		    end,
6508		    fun(Elapsed) ->
6509			    io:format("Done with filltabstr in ~p ms\n",
6510				      [Elapsed])
6511		    end),
6512	      Self ! done
6513      end, [link, {scheduler,2}]),
6514    start = receive_any(),
6515    io:format("Unfixing table... nitems=~p\n", [ets:info(T, size)]),
6516    do_tc(fun() ->
6517		  true = ets:safe_fixtable(T, false)
6518	  end,
6519	  fun(Elapsed) ->
6520		  io:format("Unfix table done in ~p ms. nitems=~p\n",
6521			    [Elapsed,ets:info(T, size)])
6522	  end),
6523    false = ets:info(T,fixed),
6524    0 = get_kept_objects(T),
6525    done = receive_any(),
6526    %%verify_table_load(T), % may fail if concurrency is poor (genny)
6527    ets:delete(T),
6528    process_flag(scheduler,0).
6529
6530%% Shrink a hash table that still contains pseudo-deleted objects.
6531shrink_pseudo_deleted(Config) when is_list(Config) ->
6532    only_if_smp(fun()->shrink_pseudo_deleted_do() end).
6533
6534shrink_pseudo_deleted_do() ->
6535    lists:foreach(fun(Type) -> shrink_pseudo_deleted_do(Type) end,
6536		  [set,bag,duplicate_bag]).
6537
6538shrink_pseudo_deleted_do(Type) ->
6539    process_flag(scheduler,1),
6540    Self = self(),
6541    T = ets_new(kalle,[Type,public,{write_concurrency,true}]),
6542    Half = 10000,
6543    filltabint(T,Half*2),
6544    true = ets:safe_fixtable(T,true),
6545    Half = ets:select_delete(T,
6546			     [{{'$1', '_'},
6547			       [{'>', '$1', Half}],
6548			       [true]}]),
6549    Half = ets:info(T,size),
6550    Half = get_kept_objects(T),
6551    my_spawn_opt(
6552      fun()-> true = ets:info(T,fixed),
6553	      Self ! start,
6554	      io:put_chars("Starting to delete... ~p\n"),
6555	      do_tc(fun() ->
6556			    del_one_by_one_set(T, 1, Half+1)
6557		    end,
6558		    fun(Elapsed) ->
6559			    io:format("Done with delete in ~p ms.\n",
6560				      [Elapsed])
6561		    end),
6562	      Self ! done
6563      end, [link, {scheduler,2}]),
6564    start = receive_any(),
6565    io:format("Unfixing table... nitems=~p\n", [ets:info(T, size)]),
6566    do_tc(fun() ->
6567		  true = ets:safe_fixtable(T, false)
6568	  end,
6569	  fun(Elapsed) ->
6570		  io:format("Unfix table done in ~p ms. nitems=~p\n",
6571			    [Elapsed,ets:info(T, size)])
6572	  end),
6573    false = ets:info(T,fixed),
6574    0 = get_kept_objects(T),
6575    done = receive_any(),
6576    %%verify_table_load(T), % may fail if concurrency is poor (genny)
6577    ets:delete(T),
6578    process_flag(scheduler,0).
6579
6580
6581
6582meta_lookup_unnamed_read(Config) when is_list(Config) ->
6583    InitF = fun(_) -> Tab = ets_new(unnamed,[]),
6584		      true = ets:insert(Tab,{key,data}),
6585		      Tab
6586	    end,
6587    ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
6588			Tab
6589	    end,
6590    FiniF = fun(Tab) -> true = ets:delete(Tab)
6591	    end,
6592    run_smp_workers(InitF,ExecF,FiniF,10000).
6593
6594meta_lookup_unnamed_write(Config) when is_list(Config) ->
6595    InitF = fun(_) -> Tab = ets_new(unnamed,[]),
6596		      {Tab,0}
6597	    end,
6598    ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
6599			    {Tab,N+1}
6600	    end,
6601    FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
6602	    end,
6603    run_smp_workers(InitF,ExecF,FiniF,10000).
6604
6605meta_lookup_named_read(Config) when is_list(Config) ->
6606    InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
6607			      Tab = ets_new(Name,[named_table]),
6608			      true = ets:insert(Tab,{key,data}),
6609			      Tab
6610	    end,
6611    ExecF = fun(Tab) -> [{key,data}] = ets:lookup(Tab,key),
6612			Tab
6613	    end,
6614    FiniF = fun(Tab) -> true = ets:delete(Tab)
6615	    end,
6616    run_smp_workers(InitF,ExecF,FiniF,10000).
6617
6618meta_lookup_named_write(Config) when is_list(Config) ->
6619    InitF = fun([ProcN|_]) -> Name = list_to_atom(integer_to_list(ProcN)),
6620			      Tab = ets_new(Name,[named_table]),
6621			      {Tab,0}
6622	    end,
6623    ExecF = fun({Tab,N}) -> true = ets:insert(Tab,{key,N}),
6624			    {Tab,N+1}
6625	    end,
6626    FiniF = fun({Tab,_}) -> true = ets:delete(Tab)
6627	    end,
6628    run_smp_workers(InitF,ExecF,FiniF,10000).
6629
6630meta_newdel_unnamed(Config) when is_list(Config) ->
6631    InitF = fun(_) -> ok end,
6632    ExecF = fun(_) -> Tab = ets_new(unnamed,[]),
6633		      true = ets:delete(Tab)
6634	    end,
6635    FiniF = fun(_) -> ok end,
6636    run_smp_workers(InitF,ExecF,FiniF,10000).
6637
6638meta_newdel_named(Config) when is_list(Config) ->
6639    InitF = fun([ProcN|_]) -> list_to_atom(integer_to_list(ProcN))
6640	    end,
6641    ExecF = fun(Name) -> Name = ets_new(Name,[named_table]),
6642			 true = ets:delete(Name),
6643			 Name
6644	    end,
6645    FiniF = fun(_) -> ok end,
6646    run_smp_workers(InitF,ExecF,FiniF,10000).
6647
6648%% Concurrent insert's on same table.
6649smp_insert(Config) when is_list(Config) ->
6650    repeat_for_opts(fun smp_insert_do/1,
6651                    [[set,ordered_set,stim_cat_ord_set]]).
6652
6653smp_insert_do(Opts) ->
6654    KeyRange = 10000,
6655    ets_new(smp_insert,[named_table,public,{write_concurrency,true}|Opts],
6656            KeyRange),
6657    InitF = fun(_) -> ok end,
6658    ExecF = fun(_) -> true = ets:insert(smp_insert,{rand:uniform(KeyRange)})
6659            end,
6660    FiniF = fun(_) -> ok end,
6661    run_smp_workers(InitF,ExecF,FiniF,100000),
6662    verify_table_load(smp_insert),
6663    ets:delete(smp_insert).
6664
6665%% Concurrent deletes on same fixated table.
6666smp_fixed_delete(Config) when is_list(Config) ->
6667    only_if_smp(fun() -> smp_fixed_delete_do() end).
6668
6669smp_fixed_delete_do() ->
6670    T = ets_new(foo,[public,{write_concurrency,true}]),
6671    %%Mem = ets:info(T,memory),
6672    NumOfObjs = 100000,
6673    filltabint(T,NumOfObjs),
6674    ets:safe_fixtable(T,true),
6675    Buckets = num_of_buckets(T),
6676    InitF = fun([ProcN,NumOfProcs|_]) -> {ProcN,NumOfProcs} end,
6677    ExecF = fun({Key,_}) when Key > NumOfObjs ->
6678                    [end_of_work];
6679               ({Key,Increment}) ->
6680                    true = ets:delete(T,Key),
6681                    {Key+Increment,Increment}
6682            end,
6683    FiniF = fun(_) -> ok end,
6684    run_sched_workers(InitF,ExecF,FiniF,NumOfObjs),
6685    0 = ets:info(T,size),
6686    true = ets:info(T,fixed),
6687    Buckets = num_of_buckets(T),
6688    case ets:info(T,type) of
6689        set -> NumOfObjs = get_kept_objects(T);
6690        _ -> ok
6691    end,
6692    ets:safe_fixtable(T,false),
6693    %% Will fail as unfix does not shrink the table:
6694    %%Mem = ets:info(T,memory),
6695    %%verify_table_load(T),
6696    ets:delete(T).
6697
6698%% ERL-720
6699%% Provoke race between ets:delete and table unfix (by select_count)
6700%% that caused ets_misc memory counter to indicate false leak.
6701delete_unfix_race(Config) when is_list(Config) ->
6702    EtsMem = etsmem(),
6703    Table = ets:new(t,[set,public,{write_concurrency,true}]),
6704    InsertOp =
6705        fun() ->
6706                receive stop ->
6707                        false
6708                after 0 ->
6709                        ets:insert(Table, {rand:uniform(10)}),
6710                        true
6711                end
6712        end,
6713    DeleteOp =
6714        fun() ->
6715                receive stop ->
6716                        false
6717                after 0 ->
6718                        ets:delete(Table, rand:uniform(10)),
6719                        true
6720                end
6721        end,
6722    SelectOp =
6723        fun() ->
6724                ets:select_count(Table, ets:fun2ms(fun(X) -> true end))
6725        end,
6726    Main = self(),
6727    Ins = spawn(fun()-> repeat_while(InsertOp), Main ! self() end),
6728    Del = spawn(fun()-> repeat_while(DeleteOp), Main ! self() end),
6729    spawn(fun()->
6730                  repeat(SelectOp, 10000),
6731                  Del ! stop,
6732                  Ins ! stop
6733          end),
6734    [receive Pid -> ok end || Pid <- [Ins,Del]],
6735    ets:delete(Table),
6736    verify_etsmem(EtsMem).
6737
6738num_of_buckets(T) ->
6739    case ets:info(T,type) of
6740        set -> element(1,ets:info(T,stats));
6741        bag -> element(1,ets:info(T,stats));
6742        duplicate_bag -> element(1,ets:info(T,stats));
6743        _ -> ok
6744    end.
6745
6746%% Fixate hash table while other process is busy doing unfix.
6747smp_unfix_fix(Config) when is_list(Config) ->
6748    only_if_smp(fun()-> smp_unfix_fix_do() end).
6749
6750smp_unfix_fix_do() ->
6751    process_flag(scheduler,1),
6752    Parent = self(),
6753    T = ets_new(foo,[public,{write_concurrency,true}]),
6754    %%Mem = ets:info(T,memory),
6755    NumOfObjs = 100000,
6756    Deleted = 50000,
6757    filltabint(T,NumOfObjs),
6758    ets:safe_fixtable(T,true),
6759    Buckets = num_of_buckets(T),
6760    Deleted = ets:select_delete(T,[{{'$1', '_'},
6761				    [{'=<','$1', Deleted}],
6762				    [true]}]),
6763    Buckets = num_of_buckets(T),
6764    Left = NumOfObjs - Deleted,
6765    Left = ets:info(T,size),
6766    true = ets:info(T,fixed),
6767    Deleted = get_kept_objects(T),
6768
6769    {Child, Mref} =
6770	my_spawn_opt(
6771	  fun()->
6772		  true = ets:info(T,fixed),
6773		  Parent ! start,
6774		  io:format("Child waiting for table to be unfixed... mem=~p\n",
6775			    [ets:info(T, memory)]),
6776		  do_tc(fun() ->
6777				repeat_while(fun()-> ets:info(T, fixed) end)
6778			end,
6779			fun(Elapsed) ->
6780				io:format("Table unfixed in ~p ms."
6781					  " Child Fixating! mem=~p\n",
6782					  [Elapsed,ets:info(T,memory)])
6783			end),
6784		  true = ets:safe_fixtable(T,true),
6785		  repeat_while(fun(Key) when Key =< NumOfObjs ->
6786				       ets:delete(T,Key), {true,Key+1};
6787				  (Key) -> {false,Key}
6788			       end,
6789			       Deleted),
6790		  0 = ets:info(T,size),
6791		  true = get_kept_objects(T) >= Left,
6792		  done = receive_any()
6793	  end,
6794	  [link, monitor, {scheduler,2}]),
6795
6796    start = receive_any(),
6797    true = ets:info(T,fixed),
6798    io:put_chars("Parent starting to unfix... ~p\n"),
6799    do_tc(fun() ->
6800		  ets:safe_fixtable(T, false)
6801	  end,
6802	  fun(Elapsed) ->
6803		  io:format("Parent done with unfix in ~p ms.\n",
6804			    [Elapsed])
6805	  end),
6806    Child ! done,
6807    {'DOWN', Mref, process, Child, normal} = receive_any(),
6808    false = ets:info(T,fixed),
6809    0 = get_kept_objects(T),
6810    %%verify_table_load(T),
6811    ets:delete(T),
6812    process_flag(scheduler,0).
6813
6814%% Unsafe unfix was done by trapping select/match.
6815otp_8166(Config) when is_list(Config) ->
6816    only_if_smp(3, fun()-> otp_8166_do(false),
6817			   otp_8166_do(true)
6818		   end).
6819
6820otp_8166_do(WC) ->
6821    %% Bug scenario: One process segv while reading the table because another
6822    %% process is doing unfix without write-lock at the end of a trapping match_object.
6823    process_flag(scheduler,1),
6824    T = ets_new(foo,[public, {write_concurrency,WC}]),
6825    NumOfObjs = 3000,  %% Need more than 1000 live objects for match_object to trap one time
6826    Deleted = NumOfObjs div 2,
6827    filltabint(T,NumOfObjs),
6828    {ReaderPid, ReaderMref} = my_spawn_opt(fun()-> otp_8166_reader(T,NumOfObjs) end,
6829                                           [link, monitor, {scheduler,2}]),
6830    {ZombieCrPid, ZombieCrMref} = my_spawn_opt(fun()-> otp_8166_zombie_creator(T,Deleted) end,
6831                                               [link, monitor, {scheduler,3}]),
6832
6833    repeat(fun() -> ZombieCrPid ! {loop, self()},
6834		    zombies_created = receive_any(),
6835		    otp_8166_trapper(T, 10, ZombieCrPid)
6836	   end, 100),
6837
6838    ReaderPid ! quit,
6839    {'DOWN', ReaderMref, process, ReaderPid, normal} = receive_any(),
6840    ZombieCrPid ! quit,
6841    {'DOWN', ZombieCrMref, process, ZombieCrPid, normal} = receive_any(),
6842    false = ets:info(T,fixed),
6843    0 = get_kept_objects(T),
6844    %%verify_table_load(T),
6845    ets:delete(T),
6846    process_flag(scheduler,0).
6847
6848%% Keep reading the table
6849otp_8166_reader(T, NumOfObjs) ->
6850    repeat_while(fun(0) ->
6851			 receive quit -> {false,done}
6852			 after 0 -> {true,NumOfObjs}
6853			 end;
6854		    (Key) ->
6855			 ets:lookup(T,Key),
6856			 {true, Key-1}
6857		 end,
6858		 NumOfObjs).
6859
6860%% Do a match_object that will trap and thereby fixate and then unfixate the table
6861otp_8166_trapper(T, Try, ZombieCrPid) ->
6862    [] = ets:match_object(T,{'_',"Pink Unicorn"}),
6863    case {ets:info(T,fixed),Try} of
6864	{true,1} ->
6865	    io:format("failed to provoke unsafe unfix, give up...\n",[]),
6866	    ZombieCrPid ! unfix;
6867	{true,_} ->
6868	    io:format("trapper too fast, trying again...\n",[]),
6869	    otp_8166_trapper(T, Try-1, ZombieCrPid);
6870	{false,_} -> done
6871    end.
6872
6873
6874%% Fixate table and create some pseudo-deleted objects (zombies)
6875%% Then wait for trapper to fixate before unfixing, as we want the trappers'
6876%% unfix to be the one that purges the zombies.
6877otp_8166_zombie_creator(T,Deleted) ->
6878    case receive_any() of
6879	quit -> done;
6880
6881	{loop,Pid} ->
6882	    filltabint(T,Deleted),
6883	    ets:safe_fixtable(T,true),
6884	    Deleted = ets:select_delete(T,[{{'$1', '_'},
6885					    [{'=<','$1', Deleted}],
6886					    [true]}]),
6887	    Pid ! zombies_created,
6888	    repeat_while(fun() -> case ets:info(T,safe_fixed_monotonic_time) of
6889				      {_,[_P1,_P2]} ->
6890					  false;
6891				      _ ->
6892					  receive unfix -> false
6893					  after 0 -> true
6894					  end
6895				  end
6896			 end),
6897	    ets:safe_fixtable(T,false),
6898	    otp_8166_zombie_creator(T,Deleted);
6899
6900	unfix ->
6901	    io:format("ignore unfix in outer loop?\n",[]),
6902	    otp_8166_zombie_creator(T,Deleted)
6903    end.
6904
6905
6906
6907
6908verify_table_load(T) ->
6909    case ets:info(T,type) of
6910        ordered_set -> ok;
6911        _ ->
6912            Stats = ets:info(T,stats),
6913            {Buckets,AvgLen,StdDev,ExpSD,_MinLen,_MaxLen,_} = Stats,
6914            ok = if
6915                     AvgLen > 1.2 ->
6916                         io:format("Table overloaded: Stats=~p\n~p\n",
6917                                   [Stats, ets:info(T)]),
6918                         false;
6919
6920                     Buckets>256, AvgLen < 0.47 ->
6921                         io:format("Table underloaded: Stats=~p\n~p\n",
6922                                   [Stats, ets:info(T)]),
6923                         false;
6924
6925                     StdDev > ExpSD*2 ->
6926                         io:format("Too large standard deviation (poor hashing?),"
6927                                   " stats=~p\n~p\n",[Stats, ets:info(T)]),
6928                         false;
6929
6930                     true ->
6931                         io:format("Stats = ~p\n~p\n",[Stats, ets:info(T)]),
6932                         ok
6933                 end
6934    end.
6935
6936
6937%% ets:select on a tree with NIL key object.
6938otp_8732(Config) when is_list(Config) ->
6939    repeat_for_all_ord_set_table_types(
6940      fun(Opts) ->
6941              KeyRange = 999,
6942              KeyFun = fun(K) -> integer_to_list(K) end,
6943              Tab = ets_new(noname,Opts, KeyRange, KeyFun),
6944              filltabstr(Tab, KeyRange),
6945              ets:insert(Tab,{[],"nasty NIL object"}),
6946              [] = ets:match(Tab,{'_',nomatch}) %% Will hang if bug not fixed
6947      end),
6948    ok.
6949
6950
6951%% Run concurrent select_delete (and inserts) on same table.
6952smp_select_delete(Config) when is_list(Config) ->
6953    repeat_for_opts(fun smp_select_delete_do/1,
6954                    [[set,ordered_set,stim_cat_ord_set],
6955                     read_concurrency, compressed]).
6956
6957smp_select_delete_do(Opts) ->
6958    KeyRange = 10000,
6959    begin % indentation
6960              T = ets_new(smp_select_delete,[named_table,public,{write_concurrency,true}|Opts],
6961                          KeyRange),
6962              Mod = 17,
6963              Zeros = erlang:make_tuple(Mod,0),
6964              InitF = fun(_) -> Zeros end,
6965              ExecF = fun(Diffs0) ->
6966                              case rand:uniform(20) of
6967                                  1 ->
6968                                      Mod = 17,
6969                                      Eq = rand:uniform(Mod) - 1,
6970                                      Deleted = ets:select_delete(T,
6971                                                                  [{{'_', '$1'},
6972                                                                    [{'=:=', {'rem', '$1', Mod}, Eq}],
6973                                                                    [true]}]),
6974                                      Diffs1 = setelement(Eq+1, Diffs0,
6975                                                          element(Eq+1,Diffs0) - Deleted),
6976                                      Diffs1;
6977                                  _ ->
6978                                      Key = rand:uniform(KeyRange),
6979                                      Eq = Key rem Mod,
6980                                      case ets:insert_new(T,{Key,Key}) of
6981                                          true ->
6982                                              Diffs1 = setelement(Eq+1, Diffs0,
6983                                                                  element(Eq+1,Diffs0)+1),
6984                                              Diffs1;
6985                                          false -> Diffs0
6986                                      end
6987                              end
6988                      end,
6989              FiniF = fun(Result) -> Result end,
6990              Results = run_sched_workers(InitF,ExecF,FiniF,20000),
6991              TotCnts = lists:foldl(fun(Diffs, Sum) -> add_lists(Sum,tuple_to_list(Diffs)) end,
6992                                    lists:duplicate(Mod, 0), Results),
6993              io:format("TotCnts = ~p\n",[TotCnts]),
6994              LeftInTab = lists:foldl(fun(N,Sum) -> Sum+N end,
6995                                      0, TotCnts),
6996              io:format("LeftInTab = ~p\n",[LeftInTab]),
6997              LeftInTab = ets:info(T,size),
6998              lists:foldl(fun(Cnt,Eq) ->
6999                                  WasCnt = ets:select_count(T,
7000                                                            [{{'_', '$1'},
7001                                                              [{'=:=', {'rem', '$1', Mod}, Eq}],
7002                                                              [true]}]),
7003                                  io:format("~p: ~p =?= ~p\n",[Eq,Cnt,WasCnt]),
7004                                  Cnt = WasCnt,
7005                                  Eq+1
7006                          end,
7007                          0, TotCnts),
7008              %% May fail as select_delete does not shrink table (enough)
7009              %%verify_table_load(T),
7010              LeftInTab = ets:select_delete(T, [{{'$1','$1'}, [], [true]}]),
7011              0 = ets:info(T,size),
7012              false = ets:info(T,fixed),
7013              ets:delete(T)
7014    end, % indentation
7015    ok.
7016
7017smp_select_replace(Config) when is_list(Config) ->
7018    repeat_for_opts(fun smp_select_replace_do/1,
7019                    [[set,ordered_set,stim_cat_ord_set,duplicate_bag],
7020                     compressed]).
7021
7022smp_select_replace_do(Opts) ->
7023    KeyRange = 20,
7024    T = ets_new(smp_select_replace,
7025                [public, {write_concurrency, true} | Opts],
7026                KeyRange),
7027    InitF = fun (_) -> 0 end,
7028    ExecF = fun (Cnt0) ->
7029                    CounterId = rand:uniform(KeyRange),
7030                    Match = [{{'$1', '$2'},
7031                              [{'=:=', '$1', CounterId}],
7032                              [{{'$1', {'+', '$2', 1}}}]}],
7033                    Cnt1 = case ets:select_replace(T, Match) of
7034                               1 -> Cnt0+1;
7035                               0 ->
7036                                   ets:insert_new(T, {CounterId, 0}),
7037                                   Cnt0
7038                           end,
7039                    receive stop ->
7040                            [end_of_work | Cnt1]
7041                    after 0 ->
7042                            Cnt1
7043                    end
7044            end,
7045    FiniF = fun (Cnt) -> Cnt end,
7046    Pids = run_sched_workers(InitF, ExecF, FiniF, infinite),
7047    receive after 3*1000 -> ok end,
7048    [P ! stop || P <- Pids],
7049    Results = wait_pids(Pids),
7050    FinalCounts = ets:select(T, [{{'_', '$1'}, [], ['$1']}]),
7051    Total = lists:sum(FinalCounts),
7052    Total = lists:sum(Results),
7053    KeyRange = ets:select_delete(T, [{{'_', '_'}, [], [true]}]),
7054    0 = ets:info(T, size),
7055    true = ets:delete(T),
7056    ok.
7057
7058%% Iterate ordered_set with write_concurrency
7059%% and make sure we hit all "stable" long lived keys
7060%% while "volatile" objects are randomly inserted and deleted.
7061smp_ordered_iteration(Config) when is_list(Config) ->
7062    repeat_for_opts(fun smp_ordered_iteration_do/1,
7063                    [[cat_ord_set,stim_cat_ord_set]]).
7064
7065
7066smp_ordered_iteration_do(Opts) ->
7067    KeyRange = 1000,
7068    OffHeap = erts_test_utils:mk_ext_pid({a@b,1}, 4711, 1),
7069    KeyFun = fun(K, Type) ->
7070                     {K div 10, K rem 10, Type, OffHeap}
7071             end,
7072    StimKeyFun = fun(K) ->
7073                         KeyFun(K, element(rand:uniform(3),
7074                                           {stable, other, volatile}))
7075                 end,
7076    T = ets_new(smp_ordered_iteration, [public, {write_concurrency,true} | Opts],
7077                KeyRange, StimKeyFun),
7078    NStable = KeyRange div 4,
7079    prefill_table(T, KeyRange, NStable, fun(K) -> {KeyFun(K, stable), 0} end),
7080    NStable = ets:info(T, size),
7081    NVolatile = KeyRange div 2,
7082    prefill_table(T, KeyRange, NVolatile, fun(K) -> {KeyFun(K, volatile), 0} end),
7083
7084    InitF = fun (_) -> #{insert => 0, delete => 0,
7085                         select_delete_bk => 0, select_delete_pbk => 0,
7086                         select_replace_bk => 0, select_replace_pbk => 0}
7087            end,
7088    ExecF = fun (Counters) ->
7089                    K = rand:uniform(KeyRange),
7090                    Key = KeyFun(K, volatile),
7091                    Acc = case rand:uniform(22) of
7092                              R when R =< 10 ->
7093                                  ets:insert(T, {Key}),
7094                                  incr_counter(insert, Counters);
7095                              R when R =< 15 ->
7096                                  ets:delete(T, Key),
7097                                  incr_counter(delete, Counters);
7098                              R when R =< 19 ->
7099                                  %% Delete bound key
7100                                  ets:select_delete(T, [{{Key, '_'}, [], [true]}]),
7101                                  incr_counter(select_delete_bk, Counters);
7102                              R when R =< 20 ->
7103                                  %% Delete partially bound key
7104                                  ets:select_delete(T, [{{{K div 10, '_', volatile, '_'}, '_'}, [], [true]}]),
7105                                  incr_counter(select_delete_pbk, Counters);
7106                              R when R =< 21 ->
7107                                  %% Replace bound key
7108                                  ets:select_replace(T, [{{Key, '$1'}, [],
7109                                                          [{{{const,Key}, {'+','$1',1}}}]}]),
7110                                  incr_counter(select_replace_bk, Counters);
7111                              _ ->
7112                                  %% Replace partially bound key
7113                                  ets:select_replace(T, [{{{K div 10, '_', volatile, '_'}, '$1'}, [],
7114                                                          [{{{element,1,'$_'}, {'+','$1',1}}}]}]),
7115                                  incr_counter(select_replace_pbk, Counters)
7116                    end,
7117                    receive stop ->
7118                            [end_of_work | Acc]
7119                    after 0 ->
7120                            Acc
7121                    end
7122            end,
7123    FiniF = fun (Acc) -> Acc end,
7124    Pids = run_sched_workers(InitF, ExecF, FiniF, infinite),
7125    timer:send_after(1000, stop),
7126
7127    Log2ChunkMax = math:log2(NStable*2),
7128    Rounds = fun Loop(N) ->
7129                     MS = [{{{'_', '_', stable, '_'}, '_'}, [], [true]}],
7130                     NStable = ets:select_count(T, MS),
7131                     NStable = count_stable(T, next, ets:first(T), 0),
7132                     NStable = count_stable(T, prev, ets:last(T), 0),
7133                     NStable = length(ets:select(T, MS)),
7134                     NStable = length(ets:select_reverse(T, MS)),
7135                     Chunk = round(math:pow(2, rand:uniform()*Log2ChunkMax)),
7136                     NStable = ets_select_chunks_count(T, MS, Chunk),
7137                     receive stop -> N
7138                     after 0 -> Loop(N+1)
7139                     end
7140             end (1),
7141    [P ! stop || P <- Pids],
7142    Results = wait_pids(Pids),
7143    io:format("Ops = ~p\n", [maps_sum(Results)]),
7144    io:format("Diff = ~p\n", [ets:info(T,size) - NStable - NVolatile]),
7145    io:format("Stats = ~p\n", [ets:info(T,stats)]),
7146    io:format("Rounds = ~p\n", [Rounds]),
7147    true = ets:delete(T),
7148
7149    %% Verify no leakage of offheap key data
7150    ok = erts_test_utils:check_node_dist(),
7151    ok.
7152
7153incr_counter(Name, Counters) ->
7154    Counters#{Name => maps:get(Name, Counters, 0) + 1}.
7155
7156count_stable(T, Next, {_, _, stable, _}=Key, N) ->
7157    count_stable(T, Next, ets:Next(T, Key), N+1);
7158count_stable(T, Next, {_, _, volatile, _}=Key, N) ->
7159    count_stable(T, Next, ets:Next(T, Key), N);
7160count_stable(_, _, '$end_of_table', N) ->
7161    N.
7162
7163ets_select_chunks_count(T, MS, Chunk) ->
7164    ets_select_chunks_count(ets:select(T, MS, Chunk), 0).
7165
7166ets_select_chunks_count('$end_of_table', N) ->
7167    N;
7168ets_select_chunks_count({List, Continuation}, N) ->
7169    ets_select_chunks_count(ets:select(Continuation),
7170                           length(List) + N).
7171
7172maps_sum([Ma | Tail]) when is_map(Ma) ->
7173    maps_sum([lists:sort(maps:to_list(Ma)) | Tail]);
7174maps_sum([La, Mb | Tail]) ->
7175    Lab = lists:zipwith(fun({K,Va}, {K,Vb}) -> {K,Va+Vb} end,
7176                        La,
7177                        lists:sort(maps:to_list(Mb))),
7178    maps_sum([Lab | Tail]);
7179maps_sum([L]) ->
7180    L.
7181
7182
7183
7184
7185%% Test different types.
7186types(Config) when is_list(Config) ->
7187    init_externals(),
7188    repeat_for_opts(fun types_do/1, [repeat_for_opts_atom2list(set_types),
7189                                     compressed]).
7190
7191types_do(Opts) ->
7192    EtsMem = etsmem(),
7193    T = ets_new(xxx,Opts),
7194    Fun = fun(Term) ->
7195		  ets:insert(T,{Term}),
7196		  [{Term}] = ets:lookup(T,Term),
7197		  ets:insert(T,{Term,xxx}),
7198		  [{Term,xxx}] = ets:lookup(T,Term),
7199		  ets:insert(T,{Term,"xxx"}),
7200		  [{Term,"xxx"}] = ets:lookup(T,Term),
7201		  ets:insert(T,{xxx,Term}),
7202		  [{xxx,Term}] = ets:lookup(T,xxx),
7203		  ets:insert(T,{"xxx",Term}),
7204		  [{"xxx",Term}] = ets:lookup(T,"xxx"),
7205		  ets:delete_all_objects(T),
7206		  0 = ets:info(T,size)
7207          end,
7208    test_terms(Fun, strict),
7209    ets:delete(T),
7210    verify_etsmem(EtsMem).
7211
7212
7213%% OTP-9932: Memory overwrite when inserting large integers in compressed bag.
7214%% Will crash with segv on 64-bit opt if not fixed.
7215otp_9932(Config) when is_list(Config) ->
7216    T = ets_new(xxx, [bag, compressed]),
7217    Fun = fun(N) ->
7218		  Key = {1316110174588445 bsl N,1316110174588583 bsl N},
7219		  S = {Key, Key},
7220		  true = ets:insert(T, S),
7221		  [S] = ets:lookup(T, Key),
7222		  true = ets:insert(T, S),
7223		  [S] = ets:lookup(T, Key)
7224	  end,
7225    lists:foreach(Fun, lists:seq(0, 16)),
7226    ets:delete(T).
7227
7228
7229%% vm-deadlock caused by race between ets:delete and others on
7230%% write_concurrency table.
7231otp_9423(Config) when is_list(Config) ->
7232    repeat_for_all_non_stim_set_table_types(
7233      fun(Opts) ->
7234              InitF = fun(_) -> {0,0} end,
7235              ExecF = fun({S,F}) ->
7236                              receive
7237                                  stop ->
7238                                      io:format("~p got stop\n", [self()]),
7239                                      [end_of_work | {"Succeded=",S,"Failed=",F}]
7240                              after 0 ->
7241                                      %%io:format("~p (~p) doing lookup\n", [self(), {S,F}]),
7242                                      try ets:lookup(otp_9423, key) of
7243                                          [] -> {S+1,F}
7244                                      catch
7245                                          error:badarg -> {S,F+1}
7246                                      end
7247                              end
7248                      end,
7249              FiniF = fun(R) -> R end,
7250              case run_smp_workers(InitF, ExecF, FiniF, infinite, 1) of
7251                  Pids when is_list(Pids) ->
7252                      %%[P ! start || P <- Pids],
7253                      repeat(fun() -> ets_new(otp_9423, [named_table, public,
7254                                                         {write_concurrency,true}|Opts]),
7255                                      ets:delete(otp_9423)
7256                             end, 10000),
7257                      [P ! stop || P <- Pids],
7258                      wait_pids(Pids),
7259                      ok;
7260
7261                  Skipped -> Skipped
7262              end
7263      end).
7264
7265
7266
7267%% Corrupted binary in compressed table
7268otp_10182(Config) when is_list(Config) ->
7269    repeat_for_opts_all_table_types(
7270      fun(Opts) ->
7271              Bin = <<"aHR0cDovL2hvb3RzdWl0ZS5jb20vYy9wcm8tYWRyb2xsLWFi">>,
7272              Key = {test, Bin},
7273              Value = base64:decode(Bin),
7274              In = {Key,Value},
7275              Db = ets_new(undefined, Opts),
7276              ets:insert(Db, In),
7277              [Out] = ets:lookup(Db, Key),
7278              io:format("In :  ~p\nOut: ~p\n", [In,Out]),
7279              ets:delete(Db),
7280              In = Out
7281      end).
7282
7283%% Test that ets:all include/exclude tables that we know are created/deleted
7284ets_all(Config) when is_list(Config) ->
7285    Pids = [spawn_link(fun() -> ets_all_run() end) || _ <- [1,2]],
7286    receive after 3*1000 -> ok end,
7287    [begin unlink(P), exit(P,kill) end || P <- Pids],
7288    ok.
7289
7290ets_all_run() ->
7291    Table = ets:new(undefined, []),
7292    true = lists:member(Table, ets:all()),
7293    ets:delete(Table),
7294    false = lists:member(Table, ets:all()),
7295    ets_all_run().
7296
7297create_tables(N) ->
7298    create_tables(N, []).
7299
7300create_tables(0, Ts) ->
7301    Ts;
7302create_tables(N, Ts) ->
7303    create_tables(N-1, [ets:new(tjo, [])|Ts]).
7304
7305massive_ets_all(Config) when is_list(Config) ->
7306    Me = self(),
7307    InitTables = lists:sort(ets:all()),
7308    io:format("InitTables=~p~n", [InitTables]),
7309    PMs0 = lists:map(fun (Sid) ->
7310                             my_spawn_opt(fun () ->
7311                                                  Ts = create_tables(250),
7312                                                  Me ! {self(), up, Ts},
7313                                                  receive {Me, die} -> ok end
7314                                          end,
7315                                          [link, monitor, {scheduler, Sid}])
7316                     end,
7317                     lists:seq(1, erlang:system_info(schedulers_online))),
7318    AllRes = lists:sort(lists:foldl(fun ({P, _M}, Ts) ->
7319                                            receive
7320                                                {P, up, PTs} ->
7321                                                    PTs ++ Ts
7322                                            end
7323                                    end,
7324                                    InitTables,
7325                                    PMs0)),
7326    AllRes = lists:sort(ets:all()),
7327    PMs1 = lists:map(fun (_) ->
7328                             my_spawn_opt(fun () ->
7329                                                  AllRes = lists:sort(ets:all())
7330                                          end,
7331                                          [link, monitor])
7332                     end, lists:seq(1, 50)),
7333    lists:foreach(fun ({P, M}) ->
7334                          receive
7335                              {'DOWN', M, process, P, _} ->
7336                                  ok
7337                          end
7338                  end, PMs1),
7339    PMs2 = lists:map(fun (_) ->
7340                             my_spawn_opt(fun () ->
7341                                                  _ = ets:all()
7342                                          end,
7343                                          [link, monitor])
7344                     end, lists:seq(1, 50)),
7345    lists:foreach(fun ({P, _M}) ->
7346                          P ! {Me, die}
7347                  end, PMs0),
7348    lists:foreach(fun ({P, M}) ->
7349                          receive
7350                              {'DOWN', M, process, P, _} ->
7351                                  ok
7352                          end
7353                  end, PMs0 ++ PMs2),
7354    EndTables = lists:sort(ets:all()),
7355    io:format("EndTables=~p~n", [EndTables]),
7356    InitTables = EndTables,
7357    ok.
7358
7359
7360take(Config) when is_list(Config) ->
7361    %% Simple test for set tables.
7362    T1 = ets_new(a, [set]),
7363    [] = ets:take(T1, foo),
7364    ets:insert(T1, {foo,bar}),
7365    [] = ets:take(T1, bar),
7366    [{foo,bar}] = ets:take(T1, foo),
7367    [] = ets:tab2list(T1),
7368    %% Non-immediate key.
7369    ets:insert(T1, {{'not',<<"immediate">>},ok}),
7370    [{{'not',<<"immediate">>},ok}] = ets:take(T1, {'not',<<"immediate">>}),
7371    %% Same with ordered tables.
7372    repeat_for_all_ord_set_table_types(
7373      fun(Opts) ->
7374              T2 = ets_new(b, Opts),
7375              [] = ets:take(T2, foo),
7376              ets:insert(T2, {foo,bar}),
7377              [] = ets:take(T2, bar),
7378              [{foo,bar}] = ets:take(T2, foo),
7379              [] = ets:tab2list(T2),
7380              ets:insert(T2, {{'not',<<"immediate">>},ok}),
7381              [{{'not',<<"immediate">>},ok}] = ets:take(T2, {'not',<<"immediate">>}),
7382              %% Arithmetically-equal keys.
7383              ets:insert(T2, [{1.0,float},{2,integer}]),
7384              [{1.0,float}] = ets:take(T2, 1),
7385              [{2,integer}] = ets:take(T2, 2.0),
7386              [] = ets:tab2list(T2),
7387              ets:delete(T2)
7388      end),
7389    %% Same with bag.
7390    T3 = ets_new(c, [bag]),
7391    ets:insert(T3, [{1,1},{1,2},{3,3}]),
7392    R = lists:sort([{1,1},{1,2}]),
7393    R = lists:sort(ets:take(T3, 1)),
7394    [{3,3}] = ets:take(T3, 3),
7395    [] = ets:tab2list(T3),
7396    ets:delete(T1),
7397    ets:delete(T3),
7398    ok.
7399
7400whereis_table(Config) when is_list(Config) ->
7401    %% Do we return 'undefined' when the named table doesn't exist?
7402    undefined = ets:whereis(whereis_test),
7403
7404    %% Does the tid() refer to the same table as the name?
7405    whereis_test = ets:new(whereis_test, [named_table]),
7406    Tid = ets:whereis(whereis_test),
7407
7408    ets:insert(whereis_test, [{hello}, {there}]),
7409
7410    [[{hello}],[{there}]] = ets:match(whereis_test, '$1'),
7411    [[{hello}],[{there}]] = ets:match(Tid, '$1'),
7412
7413    true = ets:delete_all_objects(Tid),
7414
7415    [] = ets:match(whereis_test, '$1'),
7416    [] = ets:match(Tid, '$1'),
7417
7418    %% Does the name disappear when deleted through the tid()?
7419    true = ets:delete(Tid),
7420    undefined = ets:info(whereis_test),
7421    {'EXIT',{badarg, _}} = (catch ets:match(whereis_test, '$1')),
7422
7423    %% Is the old tid() broken when the table is re-created with the same
7424    %% name?
7425    whereis_test = ets:new(whereis_test, [named_table]),
7426    [] = ets:match(whereis_test, '$1'),
7427    {'EXIT',{badarg, _}} = (catch ets:match(Tid, '$1')),
7428
7429    ok.
7430
7431ms_excessive_nesting(Config) when is_list(Config) ->
7432    MkMSCond = fun (_Fun, N) when N < 0 -> true;
7433                   (Fun, N) -> {'orelse', {'==', N, '$1'}, Fun(Fun, N-1)}
7434               end,
7435    %% Ensure it compiles with substantial but reasonable
7436    %% (hmm...) nesting
7437    MS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 100)], [{{'$1', blipp}}]}],
7438    io:format("~p~n", [erlang:match_spec_test({1, blupp}, MS, table)]),
7439    _ = ets:match_spec_compile(MS),
7440    %% Now test match_spec_compile() and select_replace()
7441    %% with tree and hash using excessive nesting. These
7442    %% used to seg-fault the emulator due to recursion
7443    %% beyond the end of the C-stack.
7444    %%
7445    %% We expect to get a system_limit error, but don't
7446    %% fail if it compiles (someone must have rewritten
7447    %% compilation of match specs to use an explicit
7448    %% stack instead of using recursion).
7449    ENMS = [{{'$1', '$2'}, [MkMSCond(MkMSCond, 1000000)], [{{'$1', blipp}}]}],
7450    io:format("~p~n", [erlang:match_spec_test({1, blupp}, ENMS, table)]),
7451    ENMSC = try
7452                ets:match_spec_compile(ENMS),
7453                "compiled"
7454            catch
7455                error:system_limit ->
7456                    "got system_limit"
7457            end,
7458    Tree = ets:new(tree, [ordered_set]),
7459    SRT = try
7460              ets:select_replace(Tree, ENMS),
7461              "compiled"
7462          catch
7463              error:system_limit ->
7464                  "got system_limit"
7465          end,
7466    Hash = ets:new(hash, [set]),
7467    SRH = try
7468              ets:select_replace(Hash, ENMS),
7469              "compiled"
7470          catch
7471              error:system_limit ->
7472                  "got system_limit"
7473          end,
7474    {comment, "match_spec_compile() "++ENMSC++"; select_replace(_,[ordered_set]) "++SRT++"; select_replace(_,[set]) "++SRH}.
7475
7476%% The following help functions are used by
7477%% throughput_benchmark. They are declared on the top level beacuse
7478%% declaring them as function local funs cause a scalability issue.
7479get_op([{_,O}], _RandNum) ->
7480    O;
7481get_op([{Prob,O}|Rest], RandNum) ->
7482    case RandNum < Prob of
7483        true -> O;
7484        false -> get_op(Rest, RandNum)
7485    end.
7486do_op(Table, ProbHelpTab, Range, Operations) ->
7487    RandNum = rand:uniform(),
7488    Op = get_op(ProbHelpTab, RandNum),
7489    #{ Op := TheOp} = Operations,
7490    TheOp(Table, Range).
7491do_work(WorksDoneSoFar, Table, ProbHelpTab, Range, Operations) ->
7492    receive
7493        stop -> WorksDoneSoFar
7494    after
7495        0 -> do_op(Table, ProbHelpTab, Range, Operations),
7496             do_work(WorksDoneSoFar + 1, Table, ProbHelpTab, Range, Operations)
7497    end.
7498
7499prefill_table(T, KeyRange, Num, ObjFun) ->
7500    Parent = self(),
7501    spawn_link(fun() ->
7502                       prefill_table_helper(T, KeyRange, Num, ObjFun),
7503                       Parent ! done
7504               end),
7505    receive done -> ok end.
7506
7507prefill_table_helper(T, KeyRange, Num, ObjFun) ->
7508    Seed = rand:uniform(KeyRange),
7509    %%io:format("prefill_table: Seed = ~p\n", [Seed]),
7510    RState = unique_rand_start(KeyRange, Seed),
7511    prefill_table_loop(T, RState, Num, ObjFun).
7512
7513prefill_table_loop(_, _, 0, _) ->
7514    ok;
7515prefill_table_loop(T, RS0, N, ObjFun) ->
7516    {Key, RS1} = unique_rand_next(RS0),
7517    ets:insert(T, ObjFun(Key)),
7518    prefill_table_loop(T, RS1, N-1, ObjFun).
7519
7520inserter_proc_starter(T, ToInsert, Parent) ->
7521    receive
7522        start -> ok
7523    end,
7524    inserter_proc(T, ToInsert, [], Parent, false).
7525
7526inserter_proc(T, [], Inserted, Parent, _) ->
7527    inserter_proc(T, Inserted, [], Parent, true);
7528inserter_proc(T, [I | ToInsert], Inserted, Parent, CanStop) ->
7529    Stop =
7530        case CanStop of
7531            true ->
7532                receive
7533                    stop -> Parent ! stopped
7534                after 0 -> no_stop
7535                end;
7536            false -> no_stop
7537        end,
7538    case Stop of
7539        no_stop ->
7540            ets:insert(T, I),
7541            inserter_proc(T, ToInsert, [I | Inserted], Parent, CanStop);
7542        _ -> ok
7543    end.
7544
7545prefill_table_parallel(T, KeyRange, Num, ObjFun) ->
7546    Parent = self(),
7547    spawn_link(fun() ->
7548                       prefill_table_parallel_helper(T, KeyRange, Num, ObjFun),
7549                       Parent ! done
7550               end),
7551    receive done -> ok end.
7552
7553prefill_table_parallel_helper(T, KeyRange, Num, ObjFun) ->
7554    NrOfSchedulers = erlang:system_info(schedulers),
7555    Seed = rand:uniform(KeyRange),
7556    %%io:format("prefill_table: Seed = ~p\n", [Seed]),
7557    RState = unique_rand_start(KeyRange, Seed),
7558    InsertMap = prefill_insert_map_loop(T, RState, Num, ObjFun, #{}, NrOfSchedulers),
7559    Self = self(),
7560    Pids = [
7561        begin
7562            InserterFun =
7563                fun() ->
7564                    inserter_proc_starter(T, ToInsert, Self)
7565                end,
7566            spawn_link(InserterFun)
7567        end
7568        || ToInsert <- maps:values(InsertMap)],
7569    [Pid ! start || Pid <- Pids],
7570    timer:sleep(1000),
7571    [Pid ! stop || Pid <- Pids],
7572    [receive stopped -> ok end || _Pid <- Pids].
7573
7574prefill_insert_map_loop(_, _, 0, _, InsertMap, _NrOfSchedulers) ->
7575    InsertMap;
7576prefill_insert_map_loop(T, RS0, N, ObjFun, InsertMap, NrOfSchedulers) ->
7577    {Key, RS1} = unique_rand_next(RS0),
7578    Sched = N rem NrOfSchedulers,
7579    PrevInserts = maps:get(Sched, InsertMap, []),
7580    NewPrevInserts = [ObjFun(Key) | PrevInserts],
7581    NewInsertMap = maps:put(Sched, NewPrevInserts, InsertMap),
7582    prefill_insert_map_loop(T, RS1, N-1, ObjFun, NewInsertMap, NrOfSchedulers).
7583
7584-record(ets_throughput_bench_config,
7585        {benchmark_duration_ms = 3000,
7586         recover_time_ms = 1000,
7587         thread_counts = not_set,
7588         key_ranges = [1000000],
7589         init_functions = [fun prefill_table/4],
7590         nr_of_repeats = 1,
7591         scenarios =
7592             [
7593              [
7594               {0.5, insert},
7595               {0.5, delete}
7596              ],
7597              [
7598               {0.1, insert},
7599               {0.1, delete},
7600               {0.8, lookup}
7601              ],
7602              [
7603               {0.01, insert},
7604               {0.01, delete},
7605               {0.98, lookup}
7606              ],
7607              [
7608               {1.0, lookup}
7609              ],
7610              [
7611               {0.1, insert},
7612               {0.1, delete},
7613               {0.4, lookup},
7614               {0.4, nextseq10}
7615              ],
7616              [
7617               {0.1, insert},
7618               {0.1, delete},
7619               {0.4, lookup},
7620               {0.4, nextseq100}
7621              ],
7622              [
7623               {0.1, insert},
7624               {0.1, delete},
7625               {0.4, lookup},
7626               {0.4, nextseq1000}
7627              ],
7628              [
7629               {1.0, nextseq1000}
7630              ],
7631              [
7632               {0.1, insert},
7633               {0.1, delete},
7634               {0.79, lookup},
7635               {0.01, selectAll}
7636              ],
7637              [
7638               {0.1, insert},
7639               {0.1, delete},
7640               {0.7999, lookup},
7641               {0.0001, selectAll}
7642              ],
7643              [
7644               {0.1, insert},
7645               {0.1, delete},
7646               {0.799999, lookup},
7647               {0.000001, selectAll}
7648              ],
7649              [
7650               {0.1, insert},
7651               {0.1, delete},
7652               {0.79, lookup},
7653               {0.01, partial_select1000}
7654              ],
7655              [
7656               {0.1, insert},
7657               {0.1, delete},
7658               {0.7999, lookup},
7659               {0.0001, partial_select1000}
7660              ],
7661              [
7662               {0.1, insert},
7663               {0.1, delete},
7664               {0.799999, lookup},
7665               {0.000001, partial_select1000}
7666              ]
7667             ],
7668         table_types =
7669             [
7670              [ordered_set, public],
7671              [ordered_set, public, {write_concurrency, true}],
7672              [ordered_set, public, {read_concurrency, true}],
7673              [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
7674              [set, public],
7675              [set, public, {write_concurrency, true}],
7676              [set, public, {read_concurrency, true}],
7677              [set, public, {write_concurrency, true}, {read_concurrency, true}]
7678             ],
7679         etsmem_fun = fun() -> ok end,
7680         verify_etsmem_fun = fun(_) -> true end,
7681         notify_res_fun = fun(_Name, _Throughput) -> ok end,
7682         print_result_paths_fun =
7683             fun(ResultPath, _LatestResultPath) ->
7684                     Comment =
7685                         io_lib:format("<a href=\"file:///~s\">Result visualization</a>",[ResultPath]),
7686                     {comment, Comment}
7687             end
7688       }).
7689
7690stdout_notify_res(ResultPath, LatestResultPath) ->
7691    io:format("Result Location: /~s~n", [ResultPath]),
7692    io:format("Latest Result Location: ~s~n", [LatestResultPath]).
7693
7694throughput_benchmark() ->
7695    throughput_benchmark(
7696      #ets_throughput_bench_config{
7697         print_result_paths_fun = fun stdout_notify_res/2}).
7698
7699throughput_benchmark(
7700  #ets_throughput_bench_config{
7701     benchmark_duration_ms  = BenchmarkDurationMs,
7702     recover_time_ms        = RecoverTimeMs,
7703     thread_counts          = ThreadCountsOpt,
7704     key_ranges             = KeyRanges,
7705     init_functions         = InitFuns,
7706     nr_of_repeats          = NrOfRepeats,
7707     scenarios              = Scenarios,
7708     table_types            = TableTypes,
7709     etsmem_fun             = ETSMemFun,
7710     verify_etsmem_fun      = VerifyETSMemFun,
7711     notify_res_fun         = NotifyResFun,
7712     print_result_paths_fun = PrintResultPathsFun}) ->
7713    NrOfSchedulers = erlang:system_info(schedulers),
7714    %% Definitions of operations that are supported by the benchmark
7715    NextSeqOp =
7716        fun (T, KeyRange, SeqSize) ->
7717                Start = rand:uniform(KeyRange),
7718                Last =
7719                    lists:foldl(
7720                      fun(_, Prev) ->
7721                              case Prev of
7722                                  '$end_of_table'-> ok;
7723                                  _ ->
7724                                      try ets:next(T, Prev) of
7725                                           Normal -> Normal
7726                                       catch
7727                                           error:badarg ->
7728                                               % sets (not ordered_sets) cannot handle when the argument
7729                                               % to next is not in the set
7730                                               rand:uniform(KeyRange)
7731                                       end
7732                              end
7733                      end,
7734                      Start,
7735                      lists:seq(1, SeqSize)),
7736                case Last =:= -1 of
7737                    true -> io:format("Will never be printed");
7738                    false -> ok
7739                end
7740        end,
7741    PartialSelectOp =
7742        fun (T, KeyRange, SeqSize) ->
7743                Start = rand:uniform(KeyRange),
7744                Last = Start + SeqSize,
7745                case -1 =:= ets:select_count(T,
7746                                             ets:fun2ms(fun({X}) when X > Start andalso X =< Last  -> true end)) of
7747                    true -> io:format("Will never be printed");
7748                    false -> ok
7749                end
7750
7751        end,
7752    %% Mapping benchmark operation names to their corresponding functions that do them
7753    Operations =
7754        #{insert =>
7755              fun(T,KeyRange) ->
7756                      Num = rand:uniform(KeyRange),
7757                      ets:insert(T, {Num})
7758              end,
7759          delete =>
7760              fun(T,KeyRange) ->
7761                      Num = rand:uniform(KeyRange),
7762                      ets:delete(T, Num)
7763              end,
7764          lookup =>
7765              fun(T,KeyRange) ->
7766                      Num = rand:uniform(KeyRange),
7767                      ets:lookup(T, Num)
7768              end,
7769          nextseq10 =>
7770              fun(T,KeyRange) -> NextSeqOp(T,KeyRange,10) end,
7771          nextseq100 =>
7772              fun(T,KeyRange) -> NextSeqOp(T,KeyRange,100) end,
7773          nextseq1000 =>
7774              fun(T,KeyRange) -> NextSeqOp(T,KeyRange,1000) end,
7775          selectAll =>
7776              fun(T,_KeyRange) ->
7777                      case -1 =:= ets:select_count(T, ets:fun2ms(fun(_X) -> true end)) of
7778                          true -> io:format("Will never be printed");
7779                          false -> ok
7780                      end
7781              end,
7782          partial_select1000 =>
7783              fun(T,KeyRange) -> PartialSelectOp(T,KeyRange,1000) end
7784         },
7785    %% Helper functions
7786    CalculateThreadCounts = fun Calculate([Count|Rest]) ->
7787                                    case Count > NrOfSchedulers of
7788                                        true -> lists:reverse(Rest);
7789                                        false -> Calculate([Count*2,Count|Rest])
7790                                    end
7791                            end,
7792    CalculateOpsProbHelpTab =
7793        fun Calculate([{_, OpName}], _) ->
7794                [{1.0, OpName}];
7795            Calculate([{OpPropability, OpName}|Res], Current) ->
7796                NewCurrent = Current + OpPropability,
7797                [{NewCurrent, OpName}| Calculate(Res, NewCurrent)]
7798        end,
7799    RenderScenario =
7800        fun R([], StringSoFar) ->
7801                StringSoFar;
7802            R([{Fraction, Operation}], StringSoFar) ->
7803                io_lib:format("~s ~f% ~p",[StringSoFar, Fraction * 100.0, Operation]);
7804            R([{Fraction, Operation}|Rest], StringSoFar) ->
7805                R(Rest,
7806                  io_lib:format("~s ~f% ~p, ",[StringSoFar, Fraction * 100.0, Operation]))
7807        end,
7808    SafeFixTableIfRequired =
7809        fun(Table, Scenario, On) ->
7810                case set =:= ets:info(Table, type) of
7811                    true ->
7812                        HasNotRequiringOp  =
7813                            lists:search(
7814                              fun({_,nextseq10}) -> true;
7815                                 ({_,nextseq100}) -> true;
7816                                 ({_,nextseq1000}) -> true;
7817                                 (_) -> false
7818                              end, Scenario),
7819                        case HasNotRequiringOp of
7820                            false -> ok;
7821                            _ -> ets:safe_fixtable(Table, On)
7822                        end;
7823                    false -> ok
7824                end
7825        end,
7826    DataHolder =
7827        fun DataHolderFun(Data)->
7828                receive
7829                    {get_data, Pid} -> Pid ! {ets_bench_data, Data};
7830                    D -> DataHolderFun([Data,D])
7831                end
7832        end,
7833    DataHolderPid = spawn_link(fun()-> DataHolder([]) end),
7834    PrintData =
7835        fun (Str, List) ->
7836                io:format(Str, List),
7837                DataHolderPid ! io_lib:format(Str, List)
7838        end,
7839    GetData =
7840        fun () ->
7841                DataHolderPid ! {get_data, self()},
7842                receive {ets_bench_data, Data} -> Data end
7843        end,
7844    %% Function that runs a benchmark instance and returns the number
7845    %% of operations that were performed
7846    RunBenchmark =
7847        fun({NrOfProcs, TableConfig, Scenario, Range, Duration, InitFun}) ->
7848                ProbHelpTab = CalculateOpsProbHelpTab(Scenario, 0),
7849                Table = ets:new(t, TableConfig),
7850                Nobj = Range div 2,
7851                case InitFun of
7852                    not_set -> prefill_table(Table, Range, Nobj, fun(K) -> {K} end);
7853                    _ -> InitFun(Table, Range, Nobj, fun(K) -> {K} end)
7854                end,
7855                Nobj = ets:info(Table, size),
7856                SafeFixTableIfRequired(Table, Scenario, true),
7857                ParentPid = self(),
7858                Worker =
7859                    fun() ->
7860                            receive start -> ok end,
7861                            WorksDone =
7862                                do_work(0, Table, ProbHelpTab, Range, Operations),
7863                            ParentPid ! WorksDone
7864                    end,
7865                ChildPids =
7866                    lists:map(fun(_N) ->spawn_link(Worker)end, lists:seq(1, NrOfProcs)),
7867                erlang:garbage_collect(),
7868                timer:sleep(RecoverTimeMs),
7869                lists:foreach(fun(Pid) -> Pid ! start end, ChildPids),
7870                timer:sleep(Duration),
7871                lists:foreach(fun(Pid) -> Pid ! stop end, ChildPids),
7872                TotalWorksDone = lists:foldl(
7873                                   fun(_, Sum) ->
7874                                           receive
7875                                               Count -> Sum + Count
7876                                           end
7877                                   end, 0, ChildPids),
7878                SafeFixTableIfRequired(Table, Scenario, false),
7879                ets:delete(Table),
7880                TotalWorksDone
7881        end,
7882    RunBenchmarkInSepProcess =
7883        fun(ParameterTuple) ->
7884                P = self(),
7885                Results =
7886                    [begin
7887                         spawn_link(fun()-> P ! {bench_result, RunBenchmark(ParameterTuple)} end),
7888                         receive {bench_result, Res} -> Res end
7889                     end || _ <- lists:seq(1, NrOfRepeats)],
7890                lists:sum(Results) / NrOfRepeats
7891        end,
7892    RunBenchmarkAndReport =
7893        fun(ThreadCount,
7894            TableType,
7895            Scenario,
7896            KeyRange,
7897            Duration,
7898            InitFunName,
7899            InitFun) ->
7900                Result = RunBenchmarkInSepProcess({ThreadCount,
7901                                                   TableType,
7902                                                   Scenario,
7903                                                   KeyRange,
7904                                                   Duration,
7905                                                   InitFun}),
7906                Throughput = Result/(Duration/1000.0),
7907                PrintData("; ~f",[Throughput]),
7908                Name = io_lib:format("Scenario: ~s, ~w, Key Range Size: ~w, "
7909                                     "# of Processes: ~w, Table Type: ~w",
7910                                     [InitFunName, Scenario, KeyRange, ThreadCount, TableType]),
7911                NotifyResFun(Name, Throughput)
7912        end,
7913    ThreadCounts =
7914        case ThreadCountsOpt of
7915            not_set ->
7916                CalculateThreadCounts([1]);
7917            _ -> ThreadCountsOpt
7918        end,
7919    %% Run the benchmark
7920    PrintData("# Each instance of the benchmark runs for ~w seconds:~n", [BenchmarkDurationMs/1000]),
7921    PrintData("# The result of a benchmark instance is presented as a number representing~n",[]),
7922    PrintData("# the number of operations performed per second:~n~n~n",[]),
7923    PrintData("# To plot graphs for the results below:~n",[]),
7924    PrintData("# 1. Open \"$ERL_TOP/lib/stdlib/test/ets_SUITE_data/visualize_throughput.html\" in a web browser~n",[]),
7925    PrintData("# 2. Copy the lines between \"#BENCHMARK STARTED$\" and \"#BENCHMARK ENDED$\" below~n",[]),
7926    PrintData("# 3. Paste the lines copied in step 2 to the text box in the browser window opened in~n",[]),
7927    PrintData("#    step 1 and press the Render button~n~n",[]),
7928    PrintData("#BENCHMARK STARTED$~n",[]),
7929    EtsMem = ETSMemFun(),
7930    %% The following loop runs all benchmark scenarios and prints the results (i.e, operations/second)
7931    lists:foreach(
7932      fun(KeyRange) ->
7933              lists:foreach(
7934                fun(Scenario) ->
7935                        PrintData("Scenario: ~s | Key Range Size: ~w$~n",
7936                                  [RenderScenario(Scenario, ""), KeyRange]),
7937                        lists:foreach(
7938                          fun(ThreadCount) ->
7939                                  PrintData("; ~w",[ThreadCount])
7940                          end,
7941                          ThreadCounts),
7942                        PrintData("$~n",[]),
7943                        lists:foreach(
7944                          fun(TableType) ->
7945                                  lists:foreach(
7946                                    fun(InitFunArg) ->
7947                                            {InitFunName, InitFun} =
7948                                                case InitFunArg of
7949                                                    {FunName, Fun} -> {FunName, Fun};
7950                                                    Fun -> {"", Fun}
7951                                                end,
7952                                            PrintData("~s,~w ",[InitFunName,TableType]),
7953                                            lists:foreach(
7954                                              fun(ThreadCount) ->
7955                                                      RunBenchmarkAndReport(ThreadCount,
7956                                                                            TableType,
7957                                                                            Scenario,
7958                                                                            KeyRange,
7959                                                                            BenchmarkDurationMs,
7960                                                                            InitFunName,
7961                                                                            InitFun)
7962                                              end,
7963                                              ThreadCounts),
7964                                            PrintData("$~n",[])
7965                                    end,
7966                                    InitFuns)
7967
7968                          end,
7969                          TableTypes)
7970                end,
7971                Scenarios)
7972      end,
7973      KeyRanges),
7974    PrintData("~n#BENCHMARK ENDED$~n~n",[]),
7975    VerifyETSMemFun(EtsMem),
7976    DataDir = filename:join(filename:dirname(code:which(?MODULE)), "ets_SUITE_data"),
7977    TemplatePath = filename:join(DataDir, "visualize_throughput.html"),
7978    {ok, Template} = file:read_file(TemplatePath),
7979    OutputData = string:replace(Template, "#bench_data_placeholder", GetData()),
7980    OutputPath1 = filename:join(DataDir, "ets_bench_result.html"),
7981    {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:now_to_datetime(erlang:timestamp()),
7982    StrTime = lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w",[Year,Month,Day,Hour,Minute,Second])),
7983    OutputPath2 = filename:join(DataDir, io_lib:format("ets_bench_result_~s.html", [StrTime])),
7984    file:write_file(OutputPath1, OutputData),
7985    file:write_file(OutputPath2, OutputData),
7986    PrintResultPathsFun(OutputPath2, OutputPath1).
7987
7988test_throughput_benchmark(Config) when is_list(Config) ->
7989    throughput_benchmark(
7990      #ets_throughput_bench_config{
7991         benchmark_duration_ms = 100,
7992         recover_time_ms = 0,
7993         thread_counts = [1, erlang:system_info(schedulers)],
7994         key_ranges = [50000],
7995         etsmem_fun = fun etsmem/0,
7996         verify_etsmem_fun = fun verify_etsmem/1}).
7997
7998long_throughput_benchmark(Config) when is_list(Config) ->
7999    N = erlang:system_info(schedulers),
8000    throughput_benchmark(
8001      #ets_throughput_bench_config{
8002         benchmark_duration_ms = 3000,
8003         recover_time_ms = 1000,
8004         thread_counts = [1, N div 2, N],
8005         key_ranges = [1000000],
8006         scenarios =
8007             [
8008              [
8009               {0.5, insert},
8010               {0.5, delete}
8011              ],
8012              [
8013               {0.1, insert},
8014               {0.1, delete},
8015               {0.8, lookup}
8016              ],
8017              [
8018               {0.01, insert},
8019               {0.01, delete},
8020               {0.98, lookup}
8021              ],
8022              [
8023               {0.1, insert},
8024               {0.1, delete},
8025               {0.4, lookup},
8026               {0.4, nextseq100}
8027              ],
8028              [
8029               {0.1, insert},
8030               {0.1, delete},
8031               {0.79, lookup},
8032               {0.01, selectAll}
8033              ],
8034              [
8035               {0.1, insert},
8036               {0.1, delete},
8037               {0.79, lookup},
8038               {0.01, partial_select1000}
8039              ]
8040             ],
8041         table_types =
8042             [
8043              [ordered_set, public, {write_concurrency, true}, {read_concurrency, true}],
8044              [set, public, {write_concurrency, true}, {read_concurrency, true}]
8045             ],
8046         etsmem_fun = fun etsmem/0,
8047         verify_etsmem_fun = fun verify_etsmem/1,
8048         notify_res_fun =
8049             fun(Name, Throughput) ->
8050                     SummaryTable =
8051                         proplists:get_value(ets_benchmark_result_summary_tab, Config),
8052                     AddToSummaryCounter =
8053                         case SummaryTable of
8054                             undefined ->
8055                                 fun(_, _) ->
8056                                         ok
8057                                 end;
8058                             Tab ->
8059                                 fun(CounterName, ToAdd) ->
8060                                         OldVal = ets:lookup_element(Tab, CounterName, 2),
8061                                         NewVal = OldVal + ToAdd,
8062                                         ets:insert(Tab, {CounterName, NewVal})
8063                                 end
8064                         end,
8065                     Record =
8066                         fun(NoOfBenchsCtr, TotThrputCtr) ->
8067                                 AddToSummaryCounter(NoOfBenchsCtr, 1),
8068                                 AddToSummaryCounter(TotThrputCtr, Throughput)
8069                         end,
8070                     Record(nr_of_benchmarks, total_throughput),
8071                     case string:find(Name, "ordered_set") of
8072                         nomatch ->
8073                             Record(nr_of_set_benchmarks, total_throughput_set);
8074                         _ ->
8075                             Record(nr_of_ordered_set_benchmarks,
8076                                    total_throughput_ordered_set)
8077                     end,
8078                     ct_event:notify(
8079                          #event{name = benchmark_data,
8080                                 data = [{suite,"ets_bench"},
8081                                         {name, Name},
8082                                         {value,Throughput}]})
8083             end
8084        }).
8085
8086%% This function compares the lookup operation's performance for
8087%% ordered_set ETS tables with and without write_concurrency enabled
8088%% when the data structures have been populated in parallel and
8089%% sequentially.
8090%%
8091%% The main purpose of this function is to check that the
8092%% implementation of ordered_set with write_concurrency (CA tree)
8093%% adapts its structure to contention even when only lookup operations
8094%% are used.
8095lookup_catree_par_vs_seq_init_benchmark() ->
8096    N = erlang:system_info(schedulers),
8097    throughput_benchmark(
8098      #ets_throughput_bench_config{
8099         benchmark_duration_ms = 600000,
8100         recover_time_ms = 1000,
8101         thread_counts = [1, N div 2, N],
8102         key_ranges = [1000000],
8103         init_functions = [{"seq_init", fun prefill_table/4},
8104                           {"par_init", fun prefill_table_parallel/4}],
8105         nr_of_repeats = 1,
8106         scenarios =
8107             [
8108              [
8109               {1.0, lookup}
8110              ]
8111             ],
8112         table_types =
8113             [
8114              [ordered_set, public, {write_concurrency, true}],
8115              [ordered_set, public]
8116             ],
8117          print_result_paths_fun = fun stdout_notify_res/2
8118        }).
8119
8120add_lists(L1,L2) ->
8121    add_lists(L1,L2,[]).
8122add_lists([],[],Acc) ->
8123    lists:reverse(Acc);
8124add_lists([E1|T1], [E2|T2], Acc) ->
8125    add_lists(T1, T2, [E1+E2 | Acc]).
8126
8127run_smp_workers(InitF,ExecF,FiniF,Laps) ->
8128    run_smp_workers(InitF,ExecF,FiniF,Laps, 0).
8129run_smp_workers(InitF,ExecF,FiniF,Laps, Exclude) ->
8130    case erlang:system_info(schedulers_online) of
8131        N when N > Exclude ->
8132            run_workers_do(InitF,ExecF,FiniF,Laps, N - Exclude);
8133        _ ->
8134            {skipped, "Too few schedulers online"}
8135    end.
8136
8137run_sched_workers(InitF,ExecF,FiniF,Laps) ->
8138    run_workers_do(InitF,ExecF,FiniF,Laps,
8139                   erlang:system_info(schedulers)).
8140
8141run_workers_do(InitF,ExecF,FiniF,Laps, NumOfProcs) ->
8142    io:format("starting ~p workers\n",[NumOfProcs]),
8143    Seeds = [{ProcN,rand:uniform(9999)} || ProcN <- lists:seq(1,NumOfProcs)],
8144    Parent = self(),
8145    Pids = [my_spawn_link(fun()-> worker(Seed,InitF,ExecF,FiniF,Laps,Parent,NumOfProcs) end)
8146	    || Seed <- Seeds],
8147    case Laps of
8148	infinite -> Pids;
8149	_ -> wait_pids(Pids)
8150    end.
8151
8152worker({ProcN,Seed}, InitF, ExecF, FiniF, Laps, Parent, NumOfProcs) ->
8153    io:format("smp worker ~p, seed=~p~n",[self(),Seed]),
8154    rand:seed(default, {Seed,Seed,Seed}),
8155    State1 = InitF([ProcN, NumOfProcs]),
8156    State2 = worker_loop(Laps, ExecF, State1),
8157    Result = FiniF(State2),
8158    io:format("worker ~p done\n",[self()]),
8159    Parent ! {self(), Result}.
8160
8161worker_loop(0, _, State) ->
8162    State;
8163worker_loop(_, _, [end_of_work|State]) ->
8164    State;
8165worker_loop(infinite, ExecF, State) ->
8166    worker_loop(infinite,ExecF,ExecF(State));
8167worker_loop(N, ExecF, State) ->
8168    worker_loop(N-1,ExecF,ExecF(State)).
8169
8170wait_pids(Pids) ->
8171    wait_pids(Pids,[]).
8172wait_pids([],Acc) ->
8173    Acc;
8174wait_pids(Pids, Acc) ->
8175    receive
8176	{Pid,Result} ->
8177	    true = lists:member(Pid,Pids),
8178	    Others = lists:delete(Pid,Pids),
8179	    %%io:format("wait_pid got ~p from ~p\n",[Result,Pid]),
8180	    wait_pids(Others,[Result | Acc])
8181    after 60*1000 ->
8182	    io:format("Still waiting for workers ~p\n",[Pids]),
8183            wait_pids(Pids, Acc)
8184    end.
8185
8186
8187
8188
8189my_tab_to_list(Ts) ->
8190    Key = ets:first(Ts),
8191    my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)]).
8192
8193my_tab_to_list(_Ts,'$end_of_table', Acc) -> lists:reverse(Acc);
8194my_tab_to_list(Ts,Key, Acc) ->
8195    my_tab_to_list(Ts,ets:next(Ts,Key),[ets:lookup(Ts, Key)| Acc]).
8196
8197
8198wait_for_memory_deallocations() ->
8199    try
8200	erts_debug:set_internal_state(wait, thread_progress),
8201	erts_debug:set_internal_state(wait, deallocations)
8202    catch
8203	error:undef ->
8204	    erts_debug:set_internal_state(available_internal_state, true),
8205	    wait_for_memory_deallocations();
8206        error:badarg ->
8207            %% The emulator we run on does not have the wait internal state
8208            %% so we just sleep some time instead...
8209            timer:sleep(100)
8210    end.
8211
8212etsmem() ->
8213    etsmem(get_etsmem(), 1).
8214
8215etsmem(PrevEtsMem, Try) when Try < 10 ->
8216    case get_etsmem() of
8217        PrevEtsMem ->
8218            PrevEtsMem;
8219        EtsMem ->
8220            io:format("etsmem(): Change in attempt ~p~n~nbefore:~n~p~n~nafter:~n~p~n~n",
8221                      [Try, PrevEtsMem, EtsMem]),
8222            etsmem(EtsMem, Try+1)
8223    end;
8224etsmem(_, _) ->
8225    ct:fail("Failed to get a stable/consistent memory snapshot").
8226
8227get_etsmem() ->
8228    AllTabsExceptions = [logger, code],
8229    %% The logger table is excluded from the AllTabs list
8230    %% below because it uses decentralized counters to keep
8231    %% track of the size and the memory counters. This cause
8232    %% ets:info(T,size) and ets:info(T,memory) to trigger
8233    %% allocations and frees that may change the amount of
8234    %% memory that is allocated for ETS.
8235    %%
8236    %% The code table is excluded from the list below
8237    %% because the amount of memory allocated for it may
8238    %% change if the tested code loads a new module.
8239    AllTabs =
8240        lists:sort(
8241          [begin
8242               try ets:info(T, decentralized_counters) of
8243                   true ->
8244                       ct:fail("Background ETS table (~p) that "
8245                               "uses decentralized counters (Add exception?)",
8246                               [ets:info(T,name)]);
8247                   _ -> ok
8248               catch _:_ ->
8249                       ok
8250               end,
8251               {T,
8252                ets:info(T,name),
8253                ets:info(T,size),
8254                ets:info(T,memory),
8255                ets:info(T,type)}
8256           end
8257           || T <- ets:all(),
8258              not lists:member(ets:info(T, name), AllTabsExceptions)]),
8259    wait_for_memory_deallocations(),
8260    EtsAllocSize = erts_debug:alloc_blocks_size(ets_alloc),
8261    ErlangMemoryEts = try erlang:memory(ets)
8262                      catch error:notsup -> notsup end,
8263    FlxCtrMemUsage = try erts_debug:get_internal_state(flxctr_memory_usage)
8264                     catch error:badarg -> notsup end,
8265    Mem = {ErlangMemoryEts, EtsAllocSize, FlxCtrMemUsage},
8266    {Mem, AllTabs}.
8267
8268verify_etsmem(MI) ->
8269    wait_for_test_procs(),
8270    verify_etsmem(MI, 1).
8271
8272verify_etsmem({MemInfo,AllTabs}, Try) ->
8273    case etsmem() of
8274	{MemInfo,_} ->
8275	    io:format("Ets mem info: ~p", [MemInfo]),
8276	    case {MemInfo, Try} of
8277		{{ErlMem,EtsAlloc},_} when ErlMem == notsup; EtsAlloc == undefined ->
8278		    %% Use 'erl +Mea max' to do more complete memory leak testing.
8279		    {comment,"Incomplete or no mem leak testing"};
8280		{_, 1} ->
8281                    ok;
8282                _ ->
8283                    {comment, "Transient memory discrepancy"}
8284	    end;
8285
8286	{MemInfo2, AllTabs2} ->
8287	    io:format("#Expected: ~p", [MemInfo]),
8288	    io:format("#Actual:   ~p", [MemInfo2]),
8289	    io:format("#Changed tables before: ~p\n",[AllTabs -- AllTabs2]),
8290	    io:format("#Changed tables after: ~p\n", [AllTabs2 -- AllTabs]),
8291            case Try < 2 of
8292                true ->
8293                    io:format("\n#This discrepancy could be caused by an "
8294                              "inconsistent memory \"snapshot\""
8295                              "\n#Try again...\n", []),
8296                    verify_etsmem({MemInfo, AllTabs}, Try+1);
8297                false ->
8298                    ct:fail("Failed memory check")
8299            end
8300    end.
8301
8302
8303start_loopers(N, Prio, Fun, State) ->
8304    lists:map(fun (_) ->
8305		      my_spawn_opt(fun () -> looper(Fun, State) end,
8306				   [{priority, Prio}, link])
8307	      end,
8308	      lists:seq(1, N)).
8309
8310stop_loopers(Loopers) ->
8311    lists:foreach(fun (P) ->
8312			  unlink(P),
8313			  exit(P, bang)
8314		  end,
8315		  Loopers),
8316    ok.
8317
8318looper(Fun, State) ->
8319    looper(Fun, Fun(State)).
8320
8321spawn_logger(Procs) ->
8322    receive
8323	{new_test_proc, Proc} ->
8324	    spawn_logger([Proc|Procs]);
8325	{sync_test_procs, Kill, From} ->
8326	    lists:foreach(fun (Proc) when From == Proc ->
8327				  ok;
8328			      (Proc) ->
8329				  Mon = erlang:monitor(process, Proc),
8330				  ok = receive
8331				      {'DOWN', Mon, _, _, _} ->
8332					  ok
8333				  after 0 ->
8334					  case Kill of
8335					      true -> exit(Proc, kill);
8336					      _ -> ok
8337					  end,
8338					  receive
8339					      {'DOWN', Mon, _, _, _} ->
8340						  ok
8341                                          after 5000 ->
8342						  io:format("Waiting for 'DOWN' from ~w, status=~w\n"
8343                                                            "info = ~p\n", [Proc,
8344                                                                            pid_status(Proc),
8345                                                                            process_info(Proc)]),
8346                                                  timeout
8347					  end
8348				  end
8349			  end, Procs),
8350	    From ! test_procs_synced,
8351	    spawn_logger([From])
8352    end.
8353
8354pid_status(Pid) ->
8355    try
8356	erts_debug:get_internal_state({process_status, Pid})
8357    catch
8358	error:undef ->
8359	    erts_debug:set_internal_state(available_internal_state, true),
8360	    pid_status(Pid)
8361    end.
8362
8363start_spawn_logger() ->
8364    case whereis(ets_test_spawn_logger) of
8365	Pid when is_pid(Pid) -> true;
8366	_ -> register(ets_test_spawn_logger,
8367		      spawn_opt(fun () -> spawn_logger([]) end,
8368				[{priority, max}]))
8369    end.
8370
8371%% restart_spawn_logger() ->
8372%%     stop_spawn_logger(),
8373%%     start_spawn_logger().
8374
8375stop_spawn_logger() ->
8376    Mon = erlang:monitor(process, ets_test_spawn_logger),
8377    (catch exit(whereis(ets_test_spawn_logger), kill)),
8378    receive {'DOWN', Mon, _, _, _} -> ok end.
8379
8380wait_for_test_procs() ->
8381    wait_for_test_procs(false).
8382
8383wait_for_test_procs(Kill) ->
8384    ets_test_spawn_logger ! {sync_test_procs, Kill, self()},
8385    receive test_procs_synced -> ok end.
8386
8387log_test_proc(Proc) when is_pid(Proc) ->
8388    ets_test_spawn_logger ! {new_test_proc, Proc},
8389    Proc.
8390
8391my_spawn(Fun) -> log_test_proc(spawn(Fun)).
8392
8393my_spawn_link(Fun) -> log_test_proc(spawn_link(Fun)).
8394
8395my_spawn_opt(Fun,Opts) ->
8396    case spawn_opt(Fun,Opts) of
8397	Pid when is_pid(Pid) -> log_test_proc(Pid);
8398	{Pid, _} = Res when is_pid(Pid) -> log_test_proc(Pid), Res
8399    end.
8400
8401my_spawn_monitor(Fun) ->
8402    Res = spawn_monitor(Fun),
8403    {Pid, _} = Res,
8404    log_test_proc(Pid),
8405    Res.
8406
8407repeat(_Fun, 0) ->
8408    ok;
8409repeat(Fun, N) ->
8410    Fun(),
8411    repeat(Fun, N-1).
8412
8413repeat_while(Fun) ->
8414    case Fun() of
8415	true -> repeat_while(Fun);
8416	false -> false
8417    end.
8418
8419repeat_while(Fun, Arg0) ->
8420    case Fun(Arg0) of
8421	{true,Arg1} -> repeat_while(Fun,Arg1);
8422	{false,Ret} -> Ret
8423    end.
8424
8425%% Some (but not all) permutations of List
8426repeat_for_permutations(Fun, List) ->
8427    repeat_for_permutations(Fun, List, length(List)-1).
8428repeat_for_permutations(Fun, List, 0) ->
8429    Fun(List);
8430repeat_for_permutations(Fun, List, N) ->
8431    {A,B} = lists:split(N, List),
8432    L1 = B++A,
8433    L2 = lists:reverse(L1),
8434    L3 = B++lists:reverse(A),
8435    L4 = lists:reverse(B)++A,
8436    Fun(L1), Fun(L2), Fun(L3), Fun(L4),
8437    repeat_for_permutations(Fun, List, N-1).
8438
8439receive_any() ->
8440    receive M ->
8441	    io:format("Process ~p got msg ~p\n", [self(),M]),
8442	    M
8443    end.
8444
8445receive_any_spinning() ->
8446    receive_any_spinning(1000000).
8447receive_any_spinning(Loops) ->
8448    receive_any_spinning(Loops,Loops,1).
8449receive_any_spinning(Loops,0,Tries) ->
8450    receive M ->
8451	    io:format("Spinning process ~p got msg ~p after ~p tries\n", [self(),M,Tries]),
8452	    M
8453    after 0 ->
8454	    receive_any_spinning(Loops, Loops, Tries+1)
8455    end;
8456receive_any_spinning(Loops, N, Tries) when N>0 ->
8457    receive_any_spinning(Loops, N-1, Tries).
8458
8459
8460
8461spawn_monitor_with_pid(Pid, Fun) when is_pid(Pid) ->
8462    spawn_monitor_with_pid(Pid, Fun, 10).
8463
8464spawn_monitor_with_pid(_, _, 0) ->
8465    failed;
8466spawn_monitor_with_pid(Pid, Fun, N) ->
8467    case my_spawn(fun()-> case self() of
8468			      Pid -> Fun();
8469			      _ -> die
8470			  end
8471		  end) of
8472	Pid ->
8473	    {Pid, erlang:monitor(process, Pid)};
8474	_Other ->
8475	    spawn_monitor_with_pid(Pid,Fun,N-1)
8476    end.
8477
8478
8479only_if_smp(Func) ->
8480    only_if_smp(2, Func).
8481only_if_smp(Schedulers, Func) ->
8482    case erlang:system_info(schedulers_online) of
8483	N when N < Schedulers -> {skip,"Too few schedulers online"};
8484	_ -> Func()
8485    end.
8486
8487%% Copy-paste from emulator/test/binary_SUITE.erl
8488test_terms(Test_Func, Mode) ->
8489    garbage_collect(),
8490    Pib0 = process_info(self(),binary),
8491
8492    Test_Func(atom),
8493    Test_Func(''),
8494    Test_Func('a'),
8495    Test_Func('ab'),
8496    Test_Func('abc'),
8497    Test_Func('abcd'),
8498    Test_Func('abcde'),
8499    Test_Func('abcdef'),
8500    Test_Func('abcdefg'),
8501    Test_Func('abcdefgh'),
8502
8503    Test_Func(fun() -> ok end),
8504    X = id([a,{b,c},c]),
8505    Y = id({x,y,z}),
8506    Z = id(1 bsl 8*257),
8507    Test_Func(fun() -> X end),
8508    Test_Func(fun() -> {X,Y} end),
8509    Test_Func([fun() -> {X,Y,Z} end,
8510	       fun() -> {Z,X,Y} end,
8511	       fun() -> {Y,Z,X} end]),
8512
8513    Test_Func({trace_ts,{even_bigger,{some_data,fun() -> ok end}},{1,2,3}}),
8514    Test_Func({trace_ts,{even_bigger,{some_data,<<1,2,3,4,5,6,7,8,9,10>>}},
8515	       {1,2,3}}),
8516
8517    Test_Func(1),
8518    Test_Func(42),
8519    Test_Func(-23),
8520    Test_Func(256),
8521    Test_Func(25555),
8522    Test_Func(-3333),
8523
8524    Test_Func(1.0),
8525
8526    Test_Func(183749783987483978498378478393874),
8527    Test_Func(-37894183749783987483978498378478393874),
8528    Very_Big = very_big_num(),
8529    Test_Func(Very_Big),
8530    Test_Func(-Very_Big+1),
8531
8532    Test_Func([]),
8533    Test_Func("abcdef"),
8534    Test_Func([a, b, 1, 2]),
8535    Test_Func([a|b]),
8536
8537    Test_Func({}),
8538    Test_Func({1}),
8539    Test_Func({a, b}),
8540    Test_Func({a, b, c}),
8541    Test_Func(list_to_tuple(lists:seq(0, 255))),
8542    Test_Func(list_to_tuple(lists:seq(0, 256))),
8543
8544    Test_Func(make_ref()),
8545    Test_Func([make_ref(), make_ref()]),
8546
8547    Test_Func(make_port()),
8548
8549    Test_Func(make_pid()),
8550    Test_Func(make_ext_pid()),
8551    Test_Func(make_ext_port()),
8552    Test_Func(make_ext_ref()),
8553
8554    Bin0 = list_to_binary(lists:seq(0, 14)),
8555    Test_Func(Bin0),
8556    Bin1 = list_to_binary(lists:seq(0, ?heap_binary_size)),
8557    Test_Func(Bin1),
8558    Bin2 = list_to_binary(lists:seq(0, ?heap_binary_size+1)),
8559    Test_Func(Bin2),
8560    Bin3 = list_to_binary(lists:seq(0, 255)),
8561    garbage_collect(),
8562    Pib = process_info(self(),binary),
8563    Test_Func(Bin3),
8564    garbage_collect(),
8565    case Mode of
8566	strict -> Pib = process_info(self(),binary);
8567	skip_refc_check -> ok
8568    end,
8569
8570    Test_Func(make_unaligned_sub_binary(Bin0)),
8571    Test_Func(make_unaligned_sub_binary(Bin1)),
8572    Test_Func(make_unaligned_sub_binary(Bin2)),
8573    Test_Func(make_unaligned_sub_binary(Bin3)),
8574
8575    Test_Func(make_sub_binary(lists:seq(42, 43))),
8576    Test_Func(make_sub_binary([42,43,44])),
8577    Test_Func(make_sub_binary([42,43,44,45])),
8578    Test_Func(make_sub_binary([42,43,44,45,46])),
8579    Test_Func(make_sub_binary([42,43,44,45,46,47])),
8580    Test_Func(make_sub_binary([42,43,44,45,46,47,48])),
8581    Test_Func(make_sub_binary(lists:seq(42, 49))),
8582    Test_Func(make_sub_binary(lists:seq(0, 14))),
8583    Test_Func(make_sub_binary(lists:seq(0, ?heap_binary_size))),
8584    Test_Func(make_sub_binary(lists:seq(0, ?heap_binary_size+1))),
8585    Test_Func(make_sub_binary(lists:seq(0, 255))),
8586
8587    Test_Func(make_unaligned_sub_binary(lists:seq(42, 43))),
8588    Test_Func(make_unaligned_sub_binary([42,43,44])),
8589    Test_Func(make_unaligned_sub_binary([42,43,44,45])),
8590    Test_Func(make_unaligned_sub_binary([42,43,44,45,46])),
8591    Test_Func(make_unaligned_sub_binary([42,43,44,45,46,47])),
8592    Test_Func(make_unaligned_sub_binary([42,43,44,45,46,47,48])),
8593    Test_Func(make_unaligned_sub_binary(lists:seq(42, 49))),
8594    Test_Func(make_unaligned_sub_binary(lists:seq(0, 14))),
8595    Test_Func(make_unaligned_sub_binary(lists:seq(0, ?heap_binary_size))),
8596    Test_Func(make_unaligned_sub_binary(lists:seq(0, ?heap_binary_size+1))),
8597    Test_Func(make_unaligned_sub_binary(lists:seq(0, 255))),
8598
8599    %% Bit level binaries.
8600    Test_Func(<<1:1>>),
8601    Test_Func(<<2:2>>),
8602    Test_Func(<<42:10>>),
8603    Test_Func(list_to_bitstring([<<5:6>>|lists:seq(0, 255)])),
8604
8605    Test_Func(F = fun(A) -> 42*A end),
8606    Test_Func(lists:duplicate(32, F)),
8607
8608    Test_Func(FF = fun binary_SUITE:all/1),
8609    Test_Func(lists:duplicate(32, FF)),
8610
8611    garbage_collect(),
8612    case Mode of
8613	strict -> Pib0 = process_info(self(),binary);
8614	skip_refc_check -> ok
8615    end,
8616    ok.
8617
8618error_info(_Config) ->
8619    Ms = [{{'$1','$2','$3'},[],['$$']}],
8620    BagTab = fun(_Type) -> ets:new(table, [set,bag,private]) end,
8621    OneKeyTab = fun(Type) ->
8622                        T = ets:new(table, [Type, private]),
8623                        true = ets:insert(T, {one,two,3}),
8624                        T
8625                end,
8626    Set = fun(_Type) -> ets:new(table, [set, private]) end,
8627    OrderedSet = fun(_Type) -> ets:new(table, [ordered_set, private]) end,
8628    NamedTable = fun(Type) -> ets:new('$named_table', [Type, named_table, private]) end,
8629    UnownedTable = fun(Type) ->
8630                           Parent = self(),
8631                           spawn_link(fun() ->
8632                                              T = ets:new(table, [Type, public]),
8633                                              Parent ! T,
8634                                              receive ok -> ok end
8635                                      end),
8636                           receive T -> T end
8637                   end,
8638
8639    L = [{delete, ['$Tab']},
8640         {delete, ['$Tab', no_key], [no_fail]},
8641         {delete_all_objects, ['$Tab'], [renamed]},
8642         {delete_object, ['$Tab', bad_object]},
8643         {delete_object, ['$Tab', {tag,non_existing}], [no_fail]},
8644
8645         {file2tab, 1},                          %Not BIF.
8646         {file2tab, 2},                          %Not BIF.
8647
8648         {first, ['$Tab']},
8649
8650         {foldl, 3},                            %Not BIF.
8651         {foldr, 3},                            %Not BIF.
8652
8653         {from_dets, 2},                        %Not BIF.
8654
8655         {fun2ms, 1},                           %Not BIF.
8656
8657         {give_away, ['$Tab', not_a_pid, bad_pid]},
8658         {give_away, ['$Tab', '$Self', already_owner], [{error_term,owner}]},
8659         {give_away, ['$Tab', '$Living', living_process], [only_bad_table]},
8660         {give_away, ['$Tab', '$Dead', dead_process]},
8661
8662         {give_away, [UnownedTable, '$Living', gift_data], [{error_term,not_owner}]},
8663
8664         {i, 1},                                %Not BIF.
8665         {i, 2},                                %Not BIF.
8666         {i, 3},                                %Not BIF.
8667
8668         {info, ['$Tab']},
8669         {info, ['$Tab', invalid_item]},
8670
8671         {init_table, 2},                       %Not BIF.
8672
8673         {insert, ['$Tab', bad_object]},
8674         {insert, ['$Tab', {}]},
8675         {insert, ['$Tab', [a,{a,b,c}]]},
8676         {insert, ['$Tab', [a|b]]},
8677         {insert, ['$Tab', {a,b,c}], [no_fail]},
8678         {insert, ['$Tab', [{a,b,c}]], [no_fail]},
8679
8680         {insert_new, ['$Tab', bad_object]},
8681         {insert_new, ['$Tab', {a,b,c}], [no_fail]},
8682         {insert_new, ['$Tab', [a,{a,b,c}]]},
8683         {insert_new, ['$Tab', [a|b]]},
8684
8685         {internal_delete_all, 2},              %Internal function.
8686         {internal_select_delete, 2},           %Internal function.
8687
8688         {is_compiled_ms, [bad_ms], [no_fail, no_table]},
8689
8690         {last, ['$Tab']},
8691
8692         {lookup, ['$Tab', no_key], [no_fail]},
8693
8694         {lookup_element, ['$Tab', no_key, 0]},
8695         {lookup_element, ['$Tab', no_key, 1], [{error_term,badkey}]},
8696         {lookup_element, ['$Tab', no_key, bad_pos]},
8697
8698         {lookup_element, [OneKeyTab, one, 4]},
8699
8700         {match, [bad_continuation], [no_table]},
8701
8702         {match, ['$Tab', <<1,2,3>>], [no_fail]},
8703         {match, ['$Tab', <<1,2,3>>, 0]},
8704         {match, ['$Tab', <<1,2,3>>, bad_limit]},
8705         {match_delete, ['$Tab', <<1,2,3>>], [no_fail,renamed]},
8706
8707         {match_object, [bad_continuation], [no_table]},
8708
8709         {match_object, ['$Tab', <<1,2,3>>], [no_fail]},
8710         {match_object, ['$Tab', <<1,2,3>>, bad_limit]},
8711
8712         {match_spec_compile, [bad_match_spec], [no_table]},
8713         {match_spec_run, 2},                   %Not BIF.
8714         {match_spec_run_r, 3},                 %Internal BIF.
8715
8716         {member, ['$Tab', no_key], [no_fail]},
8717
8718         {new, [name, not_list], [no_table]},
8719         {new, [name, [a|b]], [no_table]},
8720         {new, [name, [a,b]], [no_table]},
8721         {new, [{bad,name}, [a,b]], [no_table]},
8722
8723         %% For a set, ets:next/2 and ets:prev/2 fails if the key does
8724         %% not exist.
8725         {next, [Set, no_key]},
8726         {prev, [Set, no_key]},
8727
8728         %% For an ordered set, ets:next/2 and ets:prev/2 succeeds
8729         %% even if the key does not exist.
8730         {next, [OrderedSet, no_key], [no_fail]},
8731         {prev, [OrderedSet, no_key], [no_fail]},
8732
8733         {rename, ['$Tab', {bad,name}]},
8734         {rename, [NamedTable, '$named_table']},
8735         {rename, [NamedTable, {bad,name}]},
8736
8737         {repair_continuation, 2},              %Not BIF.
8738
8739         {safe_fixtable, ['$Tab', true], [no_fail]},
8740         {safe_fixtable, ['$Tab', not_boolean]},
8741
8742         {select, [bad_continuation], [no_table]},
8743
8744         {select, ['$Tab', Ms], [no_fail]},
8745         {select, ['$Tab', bad_match_spec]},
8746         {select, ['$Tab', Ms, bad_limit]},
8747         {select, ['$Tab', Ms, 0]},
8748         {select, ['$Tab', bad_match_spec, bad_limit]},
8749         {select, ['$Tab', bad_match_spec, 1]},
8750
8751         {select_count, ['$Tab', Ms], [no_fail]},
8752         {select_count, ['$Tab', bad_match_spec]},
8753
8754         {select_delete, ['$Tab', Ms], [no_fail,renamed]},
8755         {select_delete, ['$Tab', bad_match_spec], [renamed]},
8756
8757         {select_replace, ['$Tab', [{{'$1','$2','$3'},[],[{{'$1','$3','$2'}}]}]], [no_fail]},
8758         {select_replace, ['$Tab', [{{'$1','$2','$3'},[],[{{'key_destroyed'}}]}]]},
8759         {select_replace, ['$Tab', bad_match_spec]},
8760
8761         {select_replace, [BagTab, [{{'$1','$2','$3'},[],[{{'$1','$3','$2'}}]}]], [{error_term,table_type}]},
8762
8763         {select_reverse, [bad_continuation], [no_table]},
8764
8765         {select_reverse, ['$Tab', Ms], [no_fail]},
8766         {select_reverse, ['$Tab', bad_match_spec]},
8767
8768         {select_reverse, ['$Tab', Ms, 0]},
8769         {select_reverse, ['$Tab', Ms, bad_limit]},
8770         {select_reverse, ['$Tab', bad_match_spec, bad_limit]},
8771
8772         {setopts, ['$Tab', bad_opts]},
8773
8774         {slot, ['$Tab', -1]},
8775         {slot, ['$Tab', not_an_integer]},
8776
8777         {tab2file, 2},                         %Not BIF.
8778         {tab2file, 3},                         %Not BIF.
8779         {tab2list, 1},                         %Not BIF.
8780         {tabfile_info, 1},                     %Not BIF.
8781         {table, 1},                            %Not BIF.
8782         {table, 2},                            %Not BIF.
8783
8784         {take, ['$Tab', no_key], [no_fail]},
8785
8786         {test_ms, 2},                          %Not BIF.
8787         {to_dets, 2},                          %Not BIF.
8788
8789         {update_counter, ['$Tab', no_key, 1], [{error_term,badkey}]},
8790         {update_counter, ['$Tab', no_key, bad_increment], [{error_term,badkey}]},
8791         {update_counter, ['$Tab', no_key, {1, 42}], [{error_term,badkey}]},
8792         {update_counter, ['$Tab', no_key, {1, bad_increment}], [{error_term,badkey}]},
8793
8794         {update_counter, [OneKeyTab, one, {2, 1}]},
8795         {update_counter, [OneKeyTab, one, {2, bad_increment}]},
8796         {update_counter, [OneKeyTab, one, {3, bad_increment}]},
8797         {update_counter, [OneKeyTab, one, {4, 1}], [{error_term,position}]},
8798         {update_counter, [OneKeyTab, one, {4, bad_increment}]},
8799
8800         {update_counter, [BagTab, bag_key, 1], [{error_term,table_type}]},
8801         {update_counter, [BagTab, bag_key, bad_increment], [{error_term,table_type}]},
8802
8803         {update_counter, ['$Tab', key, 2, {key,0}], [no_fail]},
8804         {update_counter, ['$Tab', key, {1,42}, {key,0}], [{error_term,keypos}]},
8805         {update_counter, ['$Tab', key, 2, {key,not_integer}]},
8806         {update_counter, ['$Tab', key, 3, {key,whatever}]},
8807
8808         {update_counter, ['$Tab', no_key, 1, default]},
8809         {update_counter, ['$Tab', no_key, bad_increment, {tag,0}]},
8810         {update_counter, ['$Tab', no_key, {1, bad_increment}, {tag,0}]},
8811         {update_counter, ['$Tab', no_key, {1, 42}, {tag,0}], [{error_term,keypos}]},
8812         {update_counter, ['$Tab', no_key, {2, 42}, {tag,not_integer}]},
8813         {update_counter, ['$Tab', no_key, {3, 42}, {tag,not_integer}], [{error_term,position}]},
8814
8815         {update_counter, [OneKeyTab, one, {2, 1}, {tag,val}]},
8816         {update_counter, [OneKeyTab, one, {2, bad_increment}, {tag,val}]},
8817         {update_counter, [OneKeyTab, one, {3, bad_increment}, {tag,val}]},
8818         {update_counter, [OneKeyTab, one, {4, 1}, {tag,val}], [{error_term,position}]},
8819         {update_counter, [OneKeyTab, one, {4, bad_increment}, {tag,val}]},
8820
8821         {update_element, ['$Tab', no_key, {2, new}], [no_fail]},
8822         {update_element, [BagTab, no_key, {2, bagged}]},
8823         {update_element, [OneKeyTab, one, not_tuple]},
8824         {update_element, [OneKeyTab, one, {0, new}]},
8825         {update_element, [OneKeyTab, one, {1, new}], [{error_term,keypos}]},
8826         {update_element, [OneKeyTab, one, {4, new}]},
8827
8828         {whereis, [{bad,name}], [no_table]}
8829        ],
8830    put(errors, []),
8831    eval_ets_bif_errors(L),
8832    io:nl(),
8833    case lists:sort(get(errors)) of
8834        [] ->
8835            ok;
8836        [_|_]=Errors ->
8837            io:format("~p\n", [Errors]),
8838            ct:fail({length(Errors),errors})
8839    end.
8840
8841eval_ets_bif_errors(L0) ->
8842    L1 = lists:foldl(fun({_,A}, Acc) when is_integer(A) -> Acc;
8843                        ({F,A}, Acc) -> [{F,A,[]}|Acc];
8844                        ({F,A,Opts}, Acc) -> [{F,A,Opts}|Acc]
8845                     end, [], L0),
8846    Tests = ordsets:from_list([{F,length(A)} || {F,A,_} <- L1] ++
8847                                  [{F,A} || {F,A} <- L0, is_integer(A)]),
8848    Bifs0 = [{F,A} || {F,A} <- ets:module_info(exports),
8849                      A =/= 0,
8850                      F =/= module_info],
8851    Bifs = ordsets:from_list(Bifs0),
8852    NYI = [{F,lists:duplicate(A, '*'),nyi} || {F,A} <- Bifs -- Tests],
8853    L = lists:sort(NYI ++ L1),
8854
8855    spawn(fun() ->
8856                  true = register(living, self()),
8857                  Ref = make_ref(),
8858                  receive
8859                      Ref ->
8860                          ok
8861                  end
8862          end),
8863
8864    do_eval_ets_bif_errors(L).
8865
8866do_eval_ets_bif_errors([H|T]) ->
8867    case H of
8868        {F, Args} ->
8869            eval_ets_bif_errors(F, Args, []);
8870        {_, Args, nyi} ->
8871            case lists:all(fun(A) -> A =:= '*' end, Args) of
8872                true ->
8873                    store_error(nyi, H, error);
8874                false ->
8875                    store_error(bad_nyi, H, error)
8876            end;
8877        {F, Args, Opts} when is_list(Opts) ->
8878            case lists:member(no_table, Opts) of
8879                true ->
8880                    ets_eval_bif_errors_once(F, Args, Opts);
8881                false ->
8882                    eval_ets_bif_errors(F, Args, Opts)
8883            end
8884    end,
8885    do_eval_ets_bif_errors(T);
8886do_eval_ets_bif_errors([]) ->
8887    ok.
8888
8889ets_eval_bif_errors_once(F, Args, Opts) ->
8890    MFA = {ets,F,Args},
8891    io:format("\n\n*** ets:~p/~p", [F,length(Args)]),
8892
8893    NoFail = lists:member(no_fail, Opts),
8894    case ets_apply(F, Args, Opts) of
8895        {error,none} ->
8896            ok;
8897        {error,Info} ->
8898            store_error(wrong_failure_reason, MFA, Info);
8899        ok when NoFail ->
8900            ok;
8901        ok when not NoFail ->
8902            %% This ETS function was supposed to fail.
8903            store_error(expected_failure, MFA, ok)
8904    end.
8905
8906eval_ets_bif_errors(F, Args0, Opts) ->
8907    MFA = {ets,F,Args0},
8908    io:format("\n\n*** ets:~p/~p", [F,length(Args0)]),
8909
8910    %% Test the ETS function with a valid table argument.
8911    %% Test both for sets and ordered sets.
8912    _ = eval_ets_valid_tid(F, Args0, Opts, set),
8913    Args = eval_ets_valid_tid(F, Args0, Opts, ordered_set),
8914
8915    %% Replace the table id with a plain ref to provoke a type error.
8916    BadArgs = eval_expand_bad_args(Args),
8917    case ets_apply(F, BadArgs, Opts) of
8918        {error,type} ->
8919            ok;
8920        BadIdResult ->
8921            store_error(bad_table_id, MFA, BadIdResult)
8922    end.
8923
8924eval_ets_valid_tid(F, Args0, Opts, Type) ->
8925    MFA = {ets,F,Args0},
8926    Args = eval_expand_args(Args0, Type),
8927    case should_apply(Args, Opts) of
8928        false ->
8929            %% Applying this function will never fail.
8930            ok;
8931        true ->
8932            NoFail = lists:member(no_fail, Opts),
8933            ErrorTerm = proplists:get_value(error_term, Opts, none),
8934            case ets_apply(F, Args, Opts) of
8935                {error,ErrorTerm} when not NoFail ->
8936                    ok;
8937                {error,Info} when not NoFail ->
8938                    store_error(wrong_failure_reason, MFA, Info);
8939                {error,Info} when NoFail ->
8940                    store_error(expected_success, MFA, Info);
8941                ok when NoFail ->
8942                    ok;
8943                ok when not NoFail ->
8944                    %% This ETS function was supposed to fail.
8945                    store_error(expected_failure, MFA, ok)
8946            end
8947    end,
8948
8949    %% Test the ETS function from another process to provoke an error
8950    %% because of missing access rights. (The table is private.)
8951    {Pid,Ref} = spawn_monitor(fun() -> exit(ets_apply(F, Args, Opts)) end),
8952    receive
8953        {'DOWN',Ref,process,Pid,Result} ->
8954            case Result of
8955                {error,access} ->
8956                    ok;
8957                {error,not_owner} when F =:= give_away ->
8958                    ok;
8959                {error,none} when F =:= info ->
8960                    ok;
8961                ok when F =:= info ->
8962                    ok;
8963                Other ->
8964                    store_error(access, MFA, Other)
8965            end
8966    end,
8967
8968    %% Delete the ETS table.
8969    eval_delete_tab(Args),
8970    case ets_apply(F, Args, Opts) of
8971        {error,id} ->
8972            ok;
8973        ok when F =:= info ->
8974            %% ets:info/1,2 returns `undefined` instead of failing if the
8975            %% table has been deleted.
8976            ok;
8977        DeadTableResult ->
8978            store_error(dead_table, MFA, DeadTableResult)
8979    end,
8980
8981    Args.
8982
8983should_apply([_], _Opts) ->
8984    %% An ETS function with a single argument can't fail if
8985    %% the argument is valid.
8986    false;
8987should_apply([_,_|_], Opts) ->
8988    %% Applying the function on a valid table would have side effects
8989    %% that would cause problems down the line (e.g. successfully
8990    %% giving away a table).
8991    not lists:member(only_bad_table, Opts).
8992
8993store_error(What, MFA, Wrong) ->
8994    put(errors, [{What,MFA,Wrong}|get(errors)]).
8995
8996eval_expand_args(Args, Type) ->
8997    [expand_arg(A, Type) || A <- Args].
8998
8999expand_arg('$Tab', Type) -> ets:new(table, [Type, private]);
9000expand_arg('$Self', _Type) -> self();
9001expand_arg('$Living', _Type) -> whereis(living);
9002expand_arg('$Dead', _Type) ->
9003    {Pid,Ref} = spawn_monitor(fun() -> ok end),
9004    receive
9005        {'DOWN',Ref,process,Pid,normal} -> Pid
9006    end;
9007expand_arg(Fun, Type) when is_function(Fun, 1) -> Fun(Type);
9008expand_arg(Arg, _Type) -> Arg.
9009
9010eval_delete_tab(['$named_table'=H|_]) ->
9011    ets:delete(H);
9012eval_delete_tab([H|_]) when is_reference(H) ->
9013    ets:delete(H);
9014eval_delete_tab([_|T]) ->
9015    eval_delete_tab(T).
9016
9017eval_expand_bad_args(['$named_table'|T]) ->
9018    [make_ref()|T];
9019eval_expand_bad_args([H|T]) when is_reference(H) ->
9020    [make_ref()|T];
9021eval_expand_bad_args([H|T]) ->
9022    [H|eval_expand_bad_args(T)].
9023
9024ets_apply(F, Args, Opts) ->
9025    try
9026        apply(ets, F, Args),
9027        io:format("\nets:~p(~s) succeeded", [F,ets_format_args(Args)])
9028    catch
9029        C:R:Stk ->
9030            SF = fun(Mod, _, _) -> Mod =:= test_server end,
9031            Str = erl_error:format_exception(C, R, Stk, #{stack_trim_fun => SF}),
9032            BinStr = iolist_to_binary(Str),
9033            io:format("\nets:~p(~s)\n~ts", [F,ets_format_args(Args),BinStr]),
9034
9035            {ets,ActualF,ActualArgs,Info} = hd(Stk),
9036
9037            RE = <<"[*][*][*] argument \\d+:">>,
9038            case re:run(BinStr, RE, [{capture, none}]) of
9039                match ->
9040                    ok;
9041                nomatch ->
9042                    store_error(no_explanation, {ets,F,Args}, Info)
9043            end,
9044
9045            case {ActualF,ActualArgs} of
9046                {F,Args} ->
9047                    ok;
9048                _ ->
9049                    case lists:member(renamed, Opts) of
9050                        true ->
9051                            ok;
9052                        false ->
9053                            store_error(renamed, {ets,F,length(Args)}, {ActualF,ActualArgs})
9054                    end
9055            end,
9056            [{error_info, ErrorInfoMap}] = Info,
9057            Cause = maps:get(cause, ErrorInfoMap, none),
9058            {error,Cause}
9059    end.
9060
9061ets_format_args(Args) ->
9062    lists:join(", ", [io_lib:format("~p", [A]) || A <- Args]).
9063
9064%%%
9065%%% Common utility functions.
9066%%%
9067
9068id(I) -> I.
9069
9070very_big_num() ->
9071    very_big_num(33, 1).
9072
9073very_big_num(Left, Result) when Left > 0 ->
9074    very_big_num(Left-1, Result*256);
9075very_big_num(0, Result) ->
9076    Result.
9077
9078make_port() ->
9079    hd(erlang:ports()).
9080
9081make_pid() ->
9082    spawn_link(fun sleeper/0).
9083
9084sleeper() ->
9085    receive after infinity -> ok end.
9086
9087make_ext_pid() ->
9088    {Pid, _, _} = get(externals),
9089    Pid.
9090
9091make_ext_port() ->
9092    {_, Port, _} = get(externals),
9093    Port.
9094make_ext_ref() ->
9095    {_, _, Ref} = get(externals),
9096    Ref.
9097
9098init_externals() ->
9099    case get(externals) of
9100	undefined ->
9101	    OtherNode = {gurka@sallad, 1},
9102	    Res = {mk_pid(OtherNode, 7645, 8123),
9103		   mk_port(OtherNode, 187489773),
9104		   mk_ref(OtherNode, [262143, 1293964255, 3291964278])},
9105	    put(externals, Res);
9106
9107	{_,_,_} -> ok
9108    end.
9109
9110%%
9111%% Node container constructor functions
9112%%
9113
9114-define(VERSION_MAGIC,       131).
9115-define(PORT_EXT,            102).
9116-define(PID_EXT,             103).
9117-define(NEW_REFERENCE_EXT,   114).
9118
9119uint32_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 32 ->
9120    [(Uint bsr 24) band 16#ff,
9121     (Uint bsr 16) band 16#ff,
9122     (Uint bsr 8) band 16#ff,
9123     Uint band 16#ff];
9124uint32_be(Uint) ->
9125    exit({badarg, uint32_be, [Uint]}).
9126
9127uint16_be(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 16 ->
9128    [(Uint bsr 8) band 16#ff,
9129     Uint band 16#ff];
9130uint16_be(Uint) ->
9131    exit({badarg, uint16_be, [Uint]}).
9132
9133uint8(Uint) when is_integer(Uint), 0 =< Uint, Uint < 1 bsl 8 ->
9134    Uint band 16#ff;
9135uint8(Uint) ->
9136    exit({badarg, uint8, [Uint]}).
9137
9138mk_pid({NodeName, Creation}, Number, Serial) when is_atom(NodeName) ->
9139    <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName),
9140    mk_pid({NodeNameExt, Creation}, Number, Serial);
9141mk_pid({NodeNameExt, Creation}, Number, Serial) ->
9142    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
9143					      ?PID_EXT,
9144					      NodeNameExt,
9145					      uint32_be(Number),
9146					      uint32_be(Serial),
9147					      uint8(Creation)])) of
9148	Pid when is_pid(Pid) ->
9149	    Pid;
9150	{'EXIT', {badarg, _}} ->
9151	    exit({badarg, mk_pid, [{NodeNameExt, Creation}, Number, Serial]});
9152	Other ->
9153	    exit({unexpected_binary_to_term_result, Other})
9154    end.
9155
9156mk_port({NodeName, Creation}, Number) when is_atom(NodeName) ->
9157    <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName),
9158    mk_port({NodeNameExt, Creation}, Number);
9159mk_port({NodeNameExt, Creation}, Number) ->
9160    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
9161					      ?PORT_EXT,
9162					      NodeNameExt,
9163					      uint32_be(Number),
9164					      uint8(Creation)])) of
9165	Port when is_port(Port) ->
9166	    Port;
9167	{'EXIT', {badarg, _}} ->
9168	    exit({badarg, mk_port, [{NodeNameExt, Creation}, Number]});
9169	Other ->
9170	    exit({unexpected_binary_to_term_result, Other})
9171    end.
9172
9173mk_ref({NodeName, Creation}, Numbers) when is_atom(NodeName),
9174					   is_integer(Creation),
9175					   is_list(Numbers) ->
9176    <<?VERSION_MAGIC, NodeNameExt/binary>> = term_to_binary(NodeName),
9177    mk_ref({NodeNameExt, Creation}, Numbers);
9178mk_ref({NodeNameExt, Creation}, Numbers) when is_binary(NodeNameExt),
9179					      is_integer(Creation),
9180					      is_list(Numbers) ->
9181    case catch binary_to_term(list_to_binary([?VERSION_MAGIC,
9182					      ?NEW_REFERENCE_EXT,
9183					      uint16_be(length(Numbers)),
9184					      NodeNameExt,
9185					      uint8(Creation),
9186					      lists:map(fun (N) ->
9187								uint32_be(N)
9188							end,
9189							Numbers)])) of
9190	Ref when is_reference(Ref) ->
9191	    Ref;
9192	{'EXIT', {badarg, _}} ->
9193	    exit({badarg, mk_ref, [{NodeNameExt, Creation}, Numbers]});
9194	Other ->
9195	    exit({unexpected_binary_to_term_result, Other})
9196    end.
9197
9198
9199make_sub_binary(Bin) when is_binary(Bin) ->
9200    {_,B} = split_binary(list_to_binary([0,1,3,Bin]), 3),
9201    B;
9202make_sub_binary(List) ->
9203    make_sub_binary(list_to_binary(List)).
9204
9205make_unaligned_sub_binary(Bin0) when is_binary(Bin0) ->
9206    Bin1 = <<0:3,Bin0/binary,31:5>>,
9207    Sz = size(Bin0),
9208    <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
9209    Bin;
9210make_unaligned_sub_binary(List) ->
9211    make_unaligned_sub_binary(list_to_binary(List)).
9212
9213%% Repeat test function with different combination of table options
9214%%
9215repeat_for_opts(F) ->
9216    repeat_for_opts(F, [write_concurrency, read_concurrency, compressed]).
9217
9218repeat_for_opts_all_table_types(F) ->
9219    repeat_for_opts(F, [all_types, write_concurrency, read_concurrency, compressed]).
9220
9221repeat_for_opts_all_non_stim_table_types(F) ->
9222    repeat_for_opts(F, [all_non_stim_types, write_concurrency, read_concurrency, compressed]).
9223
9224repeat_for_opts_all_set_table_types(F) ->
9225    repeat_for_opts(F, [set_types, write_concurrency, read_concurrency, compressed]).
9226
9227repeat_for_all_set_table_types(F) ->
9228    repeat_for_opts(F, [set_types]).
9229
9230repeat_for_all_ord_set_table_types(F) ->
9231    repeat_for_opts(F, [ord_set_types]).
9232
9233repeat_for_all_non_stim_set_table_types(F) ->
9234    repeat_for_opts(F, [all_non_stim_set_types]).
9235
9236repeat_for_opts_all_ord_set_table_types(F) ->
9237    repeat_for_opts(F, [ord_set_types, write_concurrency, read_concurrency, compressed]).
9238
9239repeat_for_opts(F, OptGenList) when is_function(F, 1) ->
9240    repeat_for_opts(F, OptGenList, []).
9241
9242repeat_for_opts(F, [], Acc) ->
9243    lists:foldl(fun(Opts, RV_Acc) ->
9244			OptList = lists:filter(fun(E) -> E =/= void end, Opts),
9245                        case is_redundant_opts_combo(OptList) of
9246                            true ->
9247                                %%io:format("Ignoring redundant options ~p\n",[OptList]),
9248                                ok;
9249                            false ->
9250                                io:format("Calling with options ~p\n",[OptList]),
9251                                RV = F(OptList),
9252                                case RV_Acc of
9253                                    {comment,_} -> RV_Acc;
9254                                    _ -> case RV of
9255                                             {comment,_} -> RV;
9256                                             _ -> [RV | RV_Acc]
9257                                         end
9258                                end
9259                        end
9260                end, [], Acc);
9261repeat_for_opts(F, [OptList | Tail], []) when is_list(OptList) ->
9262    repeat_for_opts(F, Tail, [[Opt] || Opt <- OptList]);
9263repeat_for_opts(F, [OptList | Tail], AccList) when is_list(OptList) ->
9264    repeat_for_opts(F, Tail, [[Opt|Acc] || Opt <- OptList, Acc <- AccList]);
9265repeat_for_opts(F, [Atom | Tail], AccList) when is_atom(Atom) ->
9266    repeat_for_opts(F, [repeat_for_opts_atom2list(Atom) | Tail ], AccList).
9267
9268repeat_for_opts_atom2list(set_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set];
9269repeat_for_opts_atom2list(ord_set_types) -> [ordered_set,stim_cat_ord_set,cat_ord_set];
9270repeat_for_opts_atom2list(all_types) -> [set,ordered_set,stim_cat_ord_set,cat_ord_set,bag,duplicate_bag];
9271repeat_for_opts_atom2list(all_non_stim_types) -> [set,ordered_set,cat_ord_set,bag,duplicate_bag];
9272repeat_for_opts_atom2list(all_non_stim_set_types) -> [set,ordered_set,cat_ord_set];
9273repeat_for_opts_atom2list(write_concurrency) -> [{write_concurrency,false},{write_concurrency,true}];
9274repeat_for_opts_atom2list(read_concurrency) -> [{read_concurrency,false},{read_concurrency,true}];
9275repeat_for_opts_atom2list(compressed) -> [void,compressed].
9276
9277is_redundant_opts_combo(Opts) ->
9278    (lists:member(stim_cat_ord_set, Opts) orelse
9279     lists:member(cat_ord_set, Opts))
9280        andalso
9281    (lists:member({write_concurrency, false}, Opts) orelse
9282     lists:member(private, Opts) orelse
9283     lists:member(protected, Opts)).
9284
9285%% Add fake table option with info about key range.
9286%% Will be consumed by ets_new and used for stim_cat_ord_set.
9287key_range(Opts, KeyRange) ->
9288    [{key_range, KeyRange} | Opts].
9289
9290ets_new(Name, Opts0) ->
9291    {KeyRange, Opts1} = case lists:keytake(key_range, 1, Opts0) of
9292                            {value, {key_range, KR}, Rest1} ->
9293                                {KR, Rest1};
9294                            false ->
9295                                {1000*1000, Opts0}
9296                        end,
9297    ets_new(Name, Opts1, KeyRange).
9298
9299ets_new(Name, Opts, KeyRange) ->
9300    ets_new(Name, Opts, KeyRange, fun id/1).
9301
9302ets_new(Name, Opts0, KeyRange, KeyFun) ->
9303    {_Smp, CATree, Stimulate, RevOpts} =
9304        lists:foldl(fun(cat_ord_set, {Smp, false, false, Lacc}) ->
9305                            {Smp, Smp, false, [ordered_set | Lacc]};
9306                       (stim_cat_ord_set, {Smp, false, false, Lacc}) ->
9307                            {Smp, Smp, Smp, [ordered_set | Lacc]};
9308                       (Other, {Smp, CAT, STIM, Lacc}) ->
9309                            {Smp, CAT, STIM, [Other | Lacc]}
9310                    end,
9311                    {erlang:system_info(schedulers) > 1,false, false, []},
9312                    Opts0),
9313    Opts = lists:reverse(RevOpts),
9314    EtsNewHelper =
9315        fun (UseOpts) ->
9316                case get(ets_new_opts) of
9317                    UseOpts ->
9318                        silence; %% suppress identical table opts spam
9319                    _ ->
9320                        put(ets_new_opts, UseOpts),
9321                        io:format("ets:new(~p, ~p)~n", [Name, UseOpts])
9322                end,
9323                ets:new(Name, UseOpts)
9324        end,
9325    case CATree andalso
9326        (not lists:member({write_concurrency, false}, Opts)) andalso
9327        (not lists:member(private, Opts)) andalso
9328        (not lists:member(protected, Opts)) of
9329        true ->
9330            NewOpts1 =
9331                case lists:member({write_concurrency, true}, Opts) of
9332                    true -> Opts;
9333                    false -> [{write_concurrency, true}|Opts]
9334                end,
9335            NewOpts2 =
9336                case lists:member(public, NewOpts1) of
9337                    true -> NewOpts1;
9338                    false -> [public|NewOpts1]
9339                end,
9340            T = EtsNewHelper(NewOpts2),
9341            case Stimulate of
9342                false -> ok;
9343                true -> stimulate_contention(T, KeyRange, KeyFun)
9344            end,
9345            T;
9346        false ->
9347            EtsNewHelper(Opts)
9348    end.
9349
9350% The purpose of this function is to stimulate fine grained locking in
9351% tables of types ordered_set with the write_concurrency options
9352% turned on. The erts_debug feature 'ets_force_split' is used to easier
9353% generate a routing tree with fine grained locking without having to
9354% provoke lots of actual lock contentions.
9355stimulate_contention(Tid, KeyRange, KeyFun) ->
9356    T = case Tid of
9357            A when is_atom(A) -> ets:whereis(A);
9358            _ -> Tid
9359        end,
9360    erts_debug:set_internal_state(ets_force_split, {T, true}),
9361    Num = case KeyRange > 50 of
9362              true -> 50;
9363              false -> KeyRange
9364          end,
9365    Seed = rand:uniform(KeyRange),
9366    %%io:format("prefill_table: Seed = ~p\n", [Seed]),
9367    RState = unique_rand_start(KeyRange, Seed),
9368    stim_inserter_loop(T, RState, Num, KeyFun),
9369    Num = ets:info(T, size),
9370    ets:match_delete(T, {'$1','$1','$1'}),
9371    0 = ets:info(T, size),
9372    erts_debug:set_internal_state(ets_force_split, {T, false}),
9373    case ets:info(T,stats) of
9374        {0, _, _} ->
9375            io:format("No routing nodes in table?\n"
9376                      "Debug feature 'ets_force_split' does not seem to work.\n", []),
9377            ct:fail("No ets_force_split?");
9378        Stats ->
9379            io:format("stimulated ordered_set: ~p\n", [Stats])
9380    end.
9381
9382stim_inserter_loop(_, _, 0, _) ->
9383    ok;
9384stim_inserter_loop(T, RS0, N, KeyFun) ->
9385    {K, RS1} = unique_rand_next(RS0),
9386    Key = KeyFun(K),
9387    ets:insert(T, {Key, Key, Key}),
9388    stim_inserter_loop(T, RS1, N-1, KeyFun).
9389
9390do_tc(Do, Report) ->
9391    T1 = erlang:monotonic_time(),
9392    Do(),
9393    T2 = erlang:monotonic_time(),
9394    Elapsed = erlang:convert_time_unit(T2 - T1, native, millisecond),
9395    Report(Elapsed).
9396
9397syrup_factor() ->
9398    case erlang:system_info(build_type) of
9399        valgrind -> 20;
9400        _ -> 1
9401    end.
9402
9403
9404%%
9405%% This is a pseudo random number generator for UNIQUE integers.
9406%% All integers between 1 and Max will be generated before it repeat itself.
9407%% It's a variant of this one using quadratic residues by Jeff Preshing:
9408%% http://preshing.com/20121224/how-to-generate-a-sequence-of-unique-random-integers/
9409%%
9410unique_rand_start(Max, Seed) ->
9411    L = lists:dropwhile(fun(P) -> P < Max end,
9412                        primes_3mod4()),
9413    [P | _] = case L of
9414                      [] ->
9415                          error("Random range too large");
9416                      _ ->
9417                          L
9418                  end,
9419    3 = P rem 4,
9420    {0, {Max, P, Seed}}.
9421
9422unique_rand_next({N, {Max, P, Seed}=Const}) ->
9423    case dquad(P, N, Seed) + 1 of
9424        RND when RND > Max ->  % Too large, skip
9425            unique_rand_next({N+1, Const});
9426        RND ->
9427            {RND, {N+1, Const}}
9428    end.
9429
9430%% A one-to-one relation between all integers 0 =< X < Prime
9431%% if Prime rem 4 == 3.
9432quad(Prime, X) ->
9433    Rem = X*X rem Prime,
9434    case 2*X < Prime of
9435        true ->
9436            Rem;
9437        false ->
9438            Prime - Rem
9439    end.
9440
9441dquad(Prime, X, Seed) ->
9442    quad(Prime, (quad(Prime, X) + Seed) rem Prime).
9443
9444%% Primes where P rem 4 == 3.
9445primes_3mod4() ->
9446    [103, 211, 503, 1019, 2003, 5003, 10007, 20011, 50023,
9447     100003, 200003, 500083, 1000003, 2000003, 5000011,
9448     10000019, 20000003, 50000047, 100000007].
9449