From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/stdlib/test/dets_SUITE.erl | 4136 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4136 insertions(+) create mode 100644 lib/stdlib/test/dets_SUITE.erl (limited to 'lib/stdlib/test/dets_SUITE.erl') diff --git a/lib/stdlib/test/dets_SUITE.erl b/lib/stdlib/test/dets_SUITE.erl new file mode 100644 index 0000000000..760e610e00 --- /dev/null +++ b/lib/stdlib/test/dets_SUITE.erl @@ -0,0 +1,4136 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(dets_SUITE). + +%-define(debug, true). + +-ifdef(debug). +-define(format(S, A), io:format(S, A)). +-define(line, put(line, ?LINE), ). +-define(config(X,Y), foo). +-define(t, test_server). +-define(privdir(_), "./dets_SUITE_priv"). +-define(datadir(_), "./dets_SUITE_data"). +-else. +-include("test_server.hrl"). +-define(format(S, A), ok). +-define(privdir(Conf), ?config(priv_dir, Conf)). +-define(datadir(Conf), ?config(data_dir, Conf)). +-endif. + +-export([all/1, not_run/1, newly_started/1, basic_v8/1, basic_v9/1, + open_v8/1, open_v9/1, sets_v8/1, sets_v9/1, bags_v8/1, + bags_v9/1, duplicate_bags_v8/1, duplicate_bags_v9/1, + access_v8/1, access_v9/1, dirty_mark/1, dirty_mark2/1, + bag_next_v8/1, bag_next_v9/1, oldbugs_v8/1, oldbugs_v9/1, + unsafe_assumptions/1, truncated_segment_array_v8/1, + truncated_segment_array_v9/1, open_file_v8/1, open_file_v9/1, + init_table_v8/1, init_table_v9/1, repair_v8/1, repair_v9/1, + hash_v8b_v8c/1, phash/1, fold_v8/1, fold_v9/1, fixtable_v8/1, + fixtable_v9/1, match_v8/1, match_v9/1, select_v8/1, + select_v9/1, update_counter/1, badarg/1, cache_sets_v8/1, + cache_sets_v9/1, cache_bags_v8/1, cache_bags_v9/1, + cache_duplicate_bags_v8/1, cache_duplicate_bags_v9/1, + otp_4208/1, otp_4989/1, many_clients/1, otp_4906/1, otp_5402/1, + simultaneous_open/1, insert_new/1, repair_continuation/1, + otp_5487/1, otp_6206/1, otp_6359/1, otp_4738/1, otp_7146/1, + otp_8070/1]). + +-export([dets_dirty_loop/0]). + +-export([histogram/1, sum_histogram/1, ave_histogram/1]). + +-export([init_per_testcase/2, fin_per_testcase/2]). + +%% Internal export. +-export([client/2]). + +-import(lists, + [append/1, delete/2, duplicate/2, filter/2, foreach/2, keysearch/3, + last/1, map/2, member/2, reverse/1, seq/2, sort/1, usort/1]). + +-include_lib("kernel/include/file.hrl"). + +-define(DETS_SERVER, dets). + +%% HEADSZ taken from dets_v8.erl and dets_v9.erl. +-define(HEADSZ_v8, 40). +-define(HEADSZ_v9, (56+28*4+16)). +-define(NO_KEYS_POS_v9, 36). +-define(CLOSED_PROPERLY_POS, 8). + +-define(NOT_PROPERLY_CLOSED,0). +-define(CLOSED_PROPERLY,1). + +init_per_testcase(_Case, Config) -> + Dog=?t:timetrap(?t:minutes(15)), + [{watchdog, Dog}|Config]. + +fin_per_testcase(_Case, _Config) -> + Dog=?config(watchdog, _Config), + test_server:timetrap_cancel(Dog), + ok. + +all(suite) -> + case os:type() of + vxworks -> + [not_run]; + _ -> + {req,[stdlib], + [basic_v8, basic_v9, open_v8, open_v9, sets_v8, sets_v9, + bags_v8, bags_v9, duplicate_bags_v8, duplicate_bags_v9, + newly_started, open_file_v8, open_file_v9, + init_table_v8, init_table_v9, repair_v8, repair_v9, + access_v8, access_v9, oldbugs_v8, oldbugs_v9, + unsafe_assumptions, truncated_segment_array_v8, + truncated_segment_array_v9, dirty_mark, dirty_mark2, + bag_next_v8, bag_next_v9, hash_v8b_v8c, phash, fold_v8, + fold_v9, fixtable_v8, fixtable_v9, match_v8, match_v9, + select_v8, select_v9, update_counter, badarg, + cache_sets_v8, cache_sets_v9, cache_bags_v8, + cache_bags_v9, cache_duplicate_bags_v8, + cache_duplicate_bags_v9, otp_4208, otp_4989, many_clients, + otp_4906, otp_5402, simultaneous_open, insert_new, + repair_continuation, otp_5487, otp_6206, otp_6359, otp_4738, + otp_7146, otp_8070]} + end. + +not_run(suite) -> []; +not_run(Conf) when is_list(Conf) -> + {comment, "Not runnable VxWorks/NFS"}. + +newly_started(doc) -> + ["OTP-3621"]; +newly_started(suite) -> + []; +newly_started(Config) when is_list(Config) -> + ?line true = is_alive(), + ?line {ok, Node} = test_server:start_node(slave1, slave, []), + ?line [] = rpc:call(Node, dets, all, []), + ?line test_server:stop_node(Node), + ok. + +basic_v8(doc) -> + ["Basic test case."]; +basic_v8(suite) -> + []; +basic_v8(Config) when is_list(Config) -> + basic(Config, 8). + +basic_v9(doc) -> + ["Basic test case."]; +basic_v9(suite) -> + []; +basic_v9(Config) when is_list(Config) -> + basic(Config, 9). + +basic(Config, Version) -> + ?line Tab = dets_basic_test, + ?line FName = filename(Tab, Config), + + P0 = pps(), + ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,Version}]), + ?line ok = dets:insert(Tab,{mazda,japan}), + ?line ok = dets:insert(Tab,{toyota,japan}), + ?line ok = dets:insert(Tab,{suzuki,japan}), + ?line ok = dets:insert(Tab,{honda,japan}), + ?line ok = dets:insert(Tab,{renault,france}), + ?line ok = dets:insert(Tab,{citroen,france}), + ?line ok = dets:insert(Tab,{opel,germany}), + ?line ok = dets:insert(Tab,{saab,sweden}), + ?line ok = dets:insert(Tab,{volvo,sweden}), + ?line [{opel,germany}] = dets:lookup(Tab,opel), + ?line Japs = dets:traverse(Tab, fun(Obj) -> + case Obj of + {_, japan} -> {continue, Obj}; + _ -> continue + end + end), + ?line 4 = length(Japs), + ?line ok = dets:close(Tab), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + + +open_v8(doc) -> + []; +open_v8(suite) -> + []; +open_v8(Config) when is_list(Config) -> + open(Config, 8). + +open_v9(doc) -> + []; +open_v9(suite) -> + []; +open_v9(Config) when is_list(Config) -> + open(Config, 9). + +open(Config, Version) -> + %% Running this test twice means that the Dets server is restarted + %% twice. dets_sup specifies a maximum of 4 restarts in an hour. + %% If this becomes a problem, one should consider running this + %% test on a slave node. + + ?line {Sets, Bags, Dups} = args(Config), + + ?line All = Sets ++ Bags ++ Dups, + ?line delete_files(All), + + ?line Data = make_data(1), + + P0 = pps(), + ?line Tabs = open_files(1, All, Version), + ?line initialize(Tabs, Data), + ?line check(Tabs, Data), + + ?line foreach(fun(Tab) -> ok = dets:close(Tab) end, Tabs), + %% Now reopen the files + ?format("Reopening closed files \n", []), + ?line Tabs = open_files(1, All, Version), + ?format("Checking contents of reopened files \n", []), + ?line check(Tabs, Data), + %% crash the dets server + + ?format("Crashing dets server \n", []), + process_flag(trap_exit, true), + Procs = [whereis(?DETS_SERVER) | map(fun(Tab) -> dets:info(Tab, pid) end, + Tabs)], + foreach(fun(Pid) -> exit(Pid, kill) end, Procs), + timer:sleep(100), + c:flush(), %% flush all the EXIT sigs + timer:sleep(200), + + %% Now reopen the files again + ?format("Reopening crashed files \n", []), + ?line open_files(1, All, Version), + ?format("Checking contents of repaired files \n", []), + ?line check(Tabs, Data), + + ?line close_all(Tabs), + + ?line delete_files(All), + P1 = pps(), + {Ports0, Procs0} = P0, + {Ports1, Procs1} = P1, + ?line true = Ports1 =:= Ports0, + %% The dets_server process has been restarted: + ?line [_] = Procs0 -- Procs1, + ?line [_] = Procs1 -- Procs0, + ok. + +check(Tabs, Data) -> + foreach(fun(Tab) -> + ?line Kp = dets:info(Tab, keypos), + ?format("checking ~p~n", [Tab]), + foreach(fun(Item) -> + case dets:lookup(Tab, k(Kp,Item)) of + [Item] -> ok; + _Other -> bad(Tab,Item) + end + end, Data) + end, Tabs), + ok. + +k(Kp, Obj) -> element(Kp, Obj). + +bad(_Tab, _Item) -> + ?format("Can't find item ~p in ~p ~n", [_Item, _Tab]), + exit(badtab). + +sets_v8(doc) -> + ["Performs traversal and match testing on set type dets tables."]; +sets_v8(suite) -> + []; +sets_v8(Config) when is_list(Config) -> + sets(Config, 8). + +sets_v9(doc) -> + ["Performs traversal and match testing on set type dets tables."]; +sets_v9(suite) -> + []; +sets_v9(Config) when is_list(Config) -> + sets(Config, 9). + +sets(Config, Version) -> + ?line {Sets, _, _} = args(Config), + + ?line Data = make_data(1), + ?line delete_files(Sets), + P0 = pps(), + ?line Tabs = open_files(1, Sets, Version), + Bigger = [{17,q,w,w}, {48,q,w,w,w,w,w,w}], % 48 requires a bigger buddy + ?line initialize(Tabs, Data++Bigger++Data), % overwrite + ?line Len = length(Data), + ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs), + ?line size_test(Len, Tabs), + ?line no_keys_test(Tabs), + ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> + Len = dets:info(Tab, size) end, + Tabs), + ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs), + ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs), + + ?line close_all(Tabs), + ?line delete_files(Sets), + ?line check_pps(P0), + ok. + +bags_v8(doc) -> + ["Performs traversal and match testing on bag type dets tables."]; +bags_v8(suite) -> + []; +bags_v8(Config) when is_list(Config) -> + bags(Config, 8). + +bags_v9(doc) -> + ["Performs traversal and match testing on bag type dets tables."]; +bags_v9(suite) -> + []; +bags_v9(Config) when is_list(Config) -> + bags(Config, 9). + +bags(Config, Version) -> + {_, Bags, _} = args(Config), + ?line Data = make_data(1, bag), %% gives twice as many objects + ?line delete_files(Bags), + P0 = pps(), + ?line Tabs = open_files(1, Bags, Version), + ?line initialize(Tabs, Data++Data), + ?line Len = length(Data), + ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs), + ?line size_test(Len, Tabs), + ?line no_keys_test(Tabs), + ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> + Len = dets:info(Tab, size) end, + Tabs), + ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs), + ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs), + ?line close_all(Tabs), + ?line delete_files(Bags), + ?line check_pps(P0), + ok. + + +duplicate_bags_v8(doc) -> + ["Performs traversal and match testing on duplicate_bag type dets tables."]; +duplicate_bags_v8(suite) -> + []; +duplicate_bags_v8(Config) when is_list(Config) -> + duplicate_bags(Config, 8). + +duplicate_bags_v9(doc) -> + ["Performs traversal and match testing on duplicate_bag type dets tables."]; +duplicate_bags_v9(suite) -> + []; +duplicate_bags_v9(Config) when is_list(Config) -> + duplicate_bags(Config, 9). + +duplicate_bags(Config, Version) when is_list(Config) -> + {_, _, Dups} = args(Config), + ?line Data = make_data(1, duplicate_bag), %% gives twice as many objects + ?line delete_files(Dups), + P0 = pps(), + ?line Tabs = open_files(1, Dups, Version), + ?line initialize(Tabs, Data), + ?line Len = length(Data), + ?line foreach(fun(Tab) -> trav_test(Data, Len, Tab) end, Tabs), + ?line size_test(Len, Tabs), + ?line no_keys_test(Tabs), + ?line foreach(fun(Tab) -> del_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> del_obj_test(Tab) end, Tabs), + ?line initialize(Tabs, Data), + ?line foreach(fun(Tab) -> + Len = dets:info(Tab, size) end, + Tabs), + ?line foreach(fun(Tab) -> match_test(Data, Tab) end, Tabs), + ?line foreach(fun(Tab) -> match_del_test(Tab) end, Tabs), + ?line close_all(Tabs), + ?line delete_files(Dups), + ?line check_pps(P0), + ok. + + +access_v8(doc) -> + []; +access_v8(suite) -> + []; +access_v8(Config) when is_list(Config) -> + access(Config, 8). + +access_v9(doc) -> + []; +access_v9(suite) -> + []; +access_v9(Config) when is_list(Config) -> + access(Config, 9). + +access(Config, Version) -> + Args_acc = [[{ram_file, true}, {access, read}], + [{access, read}]], + Args = [[{ram_file, true}], + []], + + ?line {Args_acc_1, _, _} = zip_filename(Args_acc, [], [], Config), + ?line delete_files(Args_acc_1), + ?line {Args_1, _, _} = zip_filename(Args, [], [], Config), + + P0 = pps(), + ?line {error, {file_error,_,enoent}} = dets:open_file('1', hd(Args_acc_1)), + + ?line Tabs = open_files(1, Args_1, Version), + ?line close_all(Tabs), + ?line Tabs = open_files(1, Args_acc_1, Version), + + ?line foreach(fun(Tab) -> + {error, {access_mode,_}} = dets:insert(Tab, {1,2}), + [] = dets:lookup(Tab, 11), + '$end_of_table' = dets:first(Tab), + {error, {access_mode,_}} = dets:delete(Tab, 22) + end, Tabs), + ?line close_all(Tabs), + ?line delete_files(Args_acc_1), + ?line check_pps(P0), + ok. + + +dirty_mark(doc) -> + ["Test that the table is not marked dirty if not written"]; +dirty_mark(suite) -> + []; +dirty_mark(Config) when is_list(Config) -> + ?line true = is_alive(), + ?line Tab = dets_dirty_mark_test, + ?line FName = filename(Tab, Config), + P0 = pps(), + ?line dets:open_file(Tab,[{file, FName}]), + ?line dets:insert(Tab,{mazda,japan}), + ?line dets:insert(Tab,{toyota,japan}), + ?line dets:insert(Tab,{suzuki,japan}), + ?line dets:insert(Tab,{honda,japan}), + ?line dets:insert(Tab,{renault,france}), + ?line dets:insert(Tab,{citroen,france}), + ?line dets:insert(Tab,{opel,germany}), + ?line dets:insert(Tab,{saab,sweden}), + ?line dets:insert(Tab,{volvo,sweden}), + ?line [{opel,germany}] = dets:lookup(Tab,opel), + ?line ok = dets:close(Tab), + ?line Call = fun(P,A) -> + P ! {self(), A}, + receive + {P, Ans} -> + Ans + after 5000 -> + exit(other_process_dead) + end + end, + ?line {ok, Node} = test_server:start_node(dets_dirty_mark, + slave, + [{linked, false}, + {args, "-pa " ++ + filename:dirname + (code:which(?MODULE))}]), + ?line ok = ensure_node(20, Node), + %% io:format("~p~n",[rpc:call(Node, code, get_path, [])]), + %% io:format("~p~n",[rpc:call(Node, file, get_cwd, [])]), + %% io:format("~p~n",[Config]), + ?line Pid = rpc:call(Node,erlang, spawn, + [?MODULE, dets_dirty_loop, []]), + ?line {ok, Tab} = Call(Pid, [open, Tab, [{file, FName}]]), + ?line [{opel,germany}] = Call(Pid, [read,Tab,opel]), + ?line test_server:stop_node(Node), + ?line {ok, Tab} = dets:open_file(Tab,[{file, FName}, + {repair,false}]), + ?line ok = dets:close(Tab), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + +dirty_mark2(doc) -> + ["Test that the table is flushed when auto_save is in effect"]; +dirty_mark2(suite) -> + []; +dirty_mark2(Config) when is_list(Config) -> + ?line true = is_alive(), + ?line Tab = dets_dirty_mark2_test, + ?line FName = filename(Tab, Config), + P0 = pps(), + ?line dets:open_file(Tab,[{file, FName}]), + ?line dets:insert(Tab,{toyota,japan}), + ?line dets:insert(Tab,{suzuki,japan}), + ?line dets:insert(Tab,{honda,japan}), + ?line dets:insert(Tab,{renault,france}), + ?line dets:insert(Tab,{citroen,france}), + ?line dets:insert(Tab,{opel,germany}), + ?line dets:insert(Tab,{saab,sweden}), + ?line dets:insert(Tab,{volvo,sweden}), + ?line [{opel,germany}] = dets:lookup(Tab,opel), + ?line ok = dets:close(Tab), + ?line Call = fun(P,A) -> + P ! {self(), A}, + receive + {P, Ans} -> + Ans + after 5000 -> + exit(other_process_dead) + end + end, + ?line {ok, Node} = test_server:start_node(dets_dirty_mark2, + slave, + [{linked, false}, + {args, "-pa " ++ + filename:dirname + (code:which(?MODULE))}]), + ?line ok = ensure_node(20, Node), + ?line Pid = rpc:call(Node,erlang, spawn, + [?MODULE, dets_dirty_loop, []]), + ?line {ok, Tab} = Call(Pid, [open, Tab, [{file, FName},{auto_save,1000}]]), + ?line ok = Call(Pid, [write,Tab,{mazda,japan}]), + ?line timer:sleep(2100), + %% Read something, just to give auto save time to finish. + ?line [{opel,germany}] = Call(Pid, [read,Tab,opel]), + ?line test_server:stop_node(Node), + ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}, {repair,false}]), + ?line ok = dets:close(Tab), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + +dets_dirty_loop() -> + receive + {From, [open, Name, Args]} -> + Ret = dets:open_file(Name, Args), + From ! {self(), Ret}, + dets_dirty_loop(); + {From, [read, Name, Key]} -> + Ret = dets:lookup(Name, Key), + From ! {self(), Ret}, + dets_dirty_loop(); + {From, [write, Name, Value]} -> + Ret = dets:insert(Name, Value), + From ! {self(), Ret}, + dets_dirty_loop() + end. + + +bag_next_v8(suite) -> + []; +bag_next_v8(doc) -> + ["Check that bags and next work as expected."]; +bag_next_v8(Config) when is_list(Config) -> + bag_next(Config, 8). + +bag_next_v9(suite) -> + []; +bag_next_v9(doc) -> + ["Check that bags and next work as expected."]; +bag_next_v9(Config) when is_list(Config) -> + ?line Tab = dets_bag_next_test, + ?line FName = filename(Tab, Config), + + %% first and next crash upon error + ?line dets:open_file(Tab,[{file, FName}, {type, bag},{version,9}]), + ?line ok = dets:insert(Tab, [{1,1},{2,2},{3,3},{4,4}]), + ?line FirstKey = dets:first(Tab), + ?line NextKey = dets:next(Tab, FirstKey), + ?line [FirstObj | _] = dets:lookup(Tab, FirstKey), + ?line [NextObj | _] = dets:lookup(Tab, NextKey), + ?line {ok, FirstPos} = dets:where(Tab, FirstObj), + ?line {ok, NextPos} = dets:where(Tab, NextObj), + crash(FName, NextPos+12), + ?line {'EXIT',BadObject1} = (catch dets:next(Tab, FirstKey)), + ?line bad_object(BadObject1, FName), + crash(FName, FirstPos+12), + ?line {'EXIT',BadObject2} = (catch dets:first(Tab)), + ?line bad_object(BadObject2, FName), + ?line dets:close(Tab), + ?line file:delete(FName), + + bag_next(Config, 9). + +bag_next(Config, Version) -> + ?line Tab = dets_bag_next_test, + ?line FName = filename(Tab, Config), + P0 = pps(), + ?line dets:open_file(Tab,[{file, FName}, {type, bag},{version,Version}]), + ?line dets:insert(Tab,{698,hopp}), + ?line dets:insert(Tab,{186,hopp}), + ?line dets:insert(Tab,{hej,hopp}), + ?line dets:insert(Tab,{186,plopp}), + Loop = fun(N, Last, Self) -> + case N of + 0 -> + exit({unterminated_first_next_sequence, N, Last}); + _ -> + case Last of + '$end_of_table' -> + ok; + _ -> + Self(N-1, dets:next(Tab,Last), Self) + end + end + end, + ?line ok = Loop(4,dets:first(Tab),Loop), + ?line dets:close(Tab), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + +oldbugs_v8(doc) -> + []; +oldbugs_v8(suite) -> + []; +oldbugs_v8(Config) when is_list(Config) -> + oldbugs(Config, 8). + +oldbugs_v9(doc) -> + []; +oldbugs_v9(suite) -> + []; +oldbugs_v9(Config) when is_list(Config) -> + oldbugs(Config, 9). + +oldbugs(Config, Version) -> + FName = filename(dets_suite_oldbugs_test, Config), + P0 = pps(), + ?line {ok, ob} = dets:open_file(ob, [{version, Version}, + {type, bag}, {file, FName}]), + ?line ok = dets:insert(ob, {1, 2}), + ?line ok = dets:insert(ob, {1,3}), + ?line ok = dets:insert(ob, {1, 2}), + ?line 2 = dets:info(ob, size), %% assertion + ?line ok = dets:close(ob), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + +unsafe_assumptions(suite) -> []; +unsafe_assumptions(doc) -> + "Tests that shrinking an object and then expanding it works."; +unsafe_assumptions(Config) when is_list(Config) -> + FName = filename(dets_suite_unsafe_assumptions_test, Config), + ?line file:delete(FName), + P0 = pps(), + ?line {ok, a} = dets:open_file(a, [{version,8},{file, FName}]), + O0 = {2,false}, + O1 = {1, false}, + O2 = {1, true}, + O3 = {1, duplicate(20,false)}, + O4 = {1, duplicate(25,false)}, % same 2-log as O3 + ?line ok = dets:insert(a, O1), + ?line ok = dets:insert(a, O0), + ?line true = [O1,O0] =:= sort(get_all_objects(a)), + ?line true = [O1,O0] =:= sort(get_all_objects_fast(a)), + ?line ok = dets:insert(a, O2), + ?line true = [O2,O0] =:= sort(get_all_objects(a)), + ?line true = [O2,O0] =:= sort(get_all_objects_fast(a)), + ?line ok = dets:insert(a, O3), + ?line true = [O3,O0] =:= sort(get_all_objects(a)), + ?line true = [O3,O0] =:= sort(get_all_objects_fast(a)), + ?line ok = dets:insert(a, O4), + ?line true = [O4,O0] =:= sort(get_all_objects(a)), + ?line true = [O4,O0] =:= sort(get_all_objects_fast(a)), + ?line ok = dets:close(a), + ?line file:delete(FName), + ?line check_pps(P0), + ok. + +truncated_segment_array_v8(suite) -> []; +truncated_segment_array_v8(doc) -> + "Tests that a file where the segment array has been truncated " + "is possible to repair."; +truncated_segment_array_v8(Config) when is_list(Config) -> + trunc_seg_array(Config, 8). + +truncated_segment_array_v9(suite) -> []; +truncated_segment_array_v9(doc) -> + "Tests that a file where the segment array has been truncated " + "is possible to repair."; +truncated_segment_array_v9(Config) when is_list(Config) -> + trunc_seg_array(Config, 9). + +trunc_seg_array(Config, V) -> + TabRef = dets_suite_truncated_segment_array_test, + Fname = filename(TabRef, Config), + %% Create file that needs to be repaired + ?line file:delete(Fname), + P0 = pps(), + ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]), + ?line ok = dets:close(TabRef), + + %% Truncate the file + ?line HeadSize = headsz(V), + ?line truncate(Fname, HeadSize + 10), + + %% Open the truncated file + ?line io:format("Expect repair:~n"), + ?line {ok, TabRef} = dets:open_file(TabRef, + [{file, Fname}, {repair, true}]), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + ?line check_pps(P0), + ok. + +open_file_v8(doc) -> + ["open_file/1 test case."]; +open_file_v8(suite) -> + []; +open_file_v8(Config) when is_list(Config) -> + open_1(Config, 8). + +open_file_v9(doc) -> + ["open_file/1 test case."]; +open_file_v9(suite) -> + []; +open_file_v9(Config) when is_list(Config) -> + T = open_v9, + Fname = filename(T, Config), + ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}]), + ?line 9 = dets:info(T, version), + ?line true = [self()] =:= dets:info(T, users), + ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}]), + ?line {error,incompatible_arguments} = + dets:open_file(T, [{file,Fname},{version,8}]), + ?line true = [self(),self()] =:= dets:info(T, users), + ?line ok = dets:close(T), + ?line true = [self()] =:= dets:info(T, users), + ?line ok = dets:close(T), + ?line undefined = ets:info(T, users), + ?line file:delete(Fname), + + open_1(Config, 9). + +open_1(Config, V) -> + TabRef = open_file_1_test, + Fname = filename(TabRef, Config), + ?line file:delete(Fname), + + P0 = pps(), + ?line {error,{file_error,Fname,enoent}} = dets:open_file(Fname), + + ?line ok = file:write_file(Fname, duplicate(100,65)), + ?line {error,{not_a_dets_file,Fname}} = dets:open_file(Fname), + ?line file:delete(Fname), + + HeadSize = headsz(V), + ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]), + ?line ok = dets:close(TabRef), + ?line truncate(Fname, HeadSize + 10), + ?line true = dets:is_dets_file(Fname), + ?line io:format("Expect repair:~n"), + ?line {ok, Ref} = dets:open_file(Fname), % repairing + ?line ok = dets:close(Ref), + ?line file:delete(Fname), + + %% truncated file header, invalid type + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = ins(TabRef, 3000), + ?line ok = dets:close(TabRef), + TypePos = 12, + crash(Fname, TypePos), + ?line {error, {invalid_type_code,Fname}} = dets:open_file(Fname), + ?line truncate(Fname, HeadSize - 10), + ?line {error, {tooshort,Fname}} = dets:open_file(Fname), + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {error,{file_error,{foo,bar},_}} = dets:is_dets_file({foo,bar}), + ?line check_pps(P0), + ok. + +init_table_v8(doc) -> + ["initialize_table/2 and from_ets/2 test case."]; +init_table_v8(suite) -> + []; +init_table_v8(Config) when is_list(Config) -> + init_table(Config, 8). + +init_table_v9(doc) -> + ["initialize_table/2 and from_ets/2 test case."]; +init_table_v9(suite) -> + []; +init_table_v9(Config) when is_list(Config) -> + %% Objects are returned in "time order". + T = init_table_v9, + Fname = filename(T, Config), + ?line file:delete(Fname), + L = [{1,a},{2,b},{1,c},{2,c},{1,c},{2,a},{1,b}], + Input = init([L]), + ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}, + {type,duplicate_bag}]), + ?line ok = dets:init_table(T, Input), + ?line [{1,a},{1,c},{1,c},{1,b}] = dets:lookup(T, 1), + ?line [{2,b},{2,c},{2,a}] = dets:lookup(T, 2), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + init_table(Config, 9), + fast_init_table(Config). + +init_table(Config, V) -> + TabRef = init_table_test, + Fname = filename(TabRef, Config), + ?line file:delete(Fname), + P0 = pps(), + + Args = [{file,Fname},{version,V},{auto_save,120000}], + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', _} = + (catch dets:init_table(TabRef, fun(foo) -> bar end)), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', _} = (catch dets:init_table(TabRef, fun() -> foo end)), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', {badarg, _}} = (catch dets:init_table(TabRef, nofun)), + ?line {'EXIT', {badarg, _}} = + (catch dets:init_table(TabRef, fun(_X) -> end_of_input end, + [{foo,bar}])), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line away = (catch dets:init_table(TabRef, fun(_) -> throw(away) end)), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {error, {init_fun, fopp}} = + dets:init_table(TabRef, fun(read) -> fopp end), + dets:close(TabRef), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line dets:safe_fixtable(TabRef, true), + ?line {error, {fixed_table, TabRef}} = dets:init_table(TabRef, init([])), + ?line dets:safe_fixtable(TabRef, false), + ?line ET = ets:new(foo,[]), + ?line ok = dets:from_ets(TabRef, ET), + ?line [] = get_all_objects(TabRef), + ?line [] = get_all_objects_fast(TabRef), + ?line true = ets:insert(ET, {1,a}), + ?line true = ets:insert(ET, {2,b}), + ?line ok = dets:from_ets(TabRef, ET), + ?line [{1,a},{2,b}] = sort(get_all_objects(TabRef)), + ?line [{1,a},{2,b}] = sort(get_all_objects_fast(TabRef)), + ?line true = ets:delete(ET), + ?line 120000 = dets:info(TabRef, auto_save), + ?line ok = dets:close(TabRef), + + ?line {ok, _} = dets:open_file(TabRef, [{access,read} | Args]), + ?line {error, {access_mode, Fname}} = dets:init_table(TabRef, init([])), + ?line ok = dets:close(TabRef), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {error, invalid_objects_list} = + (catch dets:init_table(TabRef, init([[{1,2},bad,{3,4}]]))), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + L1 = [[{1,a},{2,b}],[],[{3,c}],[{4,d}],[]], + bulk_init(L1, set, 4, Config, V), + L2 = [[{1,a},{2,b}],[],[{2,q},{3,c}],[{4,d}],[{4,e},{2,q}]], + bulk_init(L2, set, 4, Config, V), + bulk_init(L2, bag, 6, Config, V), + bulk_init(L2, duplicate_bag, 7, Config, V), + bulk_init(L1, set, 4, 512, Config, V), + bulk_init([], set, 0, 10000, Config, V), + file:delete(Fname), + + %% Initiate a file that contains a lot of objects. + ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,10000} | Args]), + ?line ok = ins(TabRef, 6000), + Fun = init_fun(0, 10000), + ?line ok = dets:init_table(TabRef, Fun,{format,term}), + ?line All = sort(get_all_objects(TabRef)), + ?line FAll = get_all_objects_fast(TabRef), + ?line true = All =:= sort(FAll), + ?line true = length(All) =:= 10000, + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,4000} | Args]), + ?line ok = ins(TabRef, 6000), + ?line FileSize1 = dets:info(TabRef, file_size), + Fun2 = init_fun(0, 4000), + ?line ok = dets:init_table(TabRef, Fun2), + ?line FileSize2 = dets:info(TabRef, file_size), + ?line ok = dets:close(TabRef), + ?line true = FileSize1 > FileSize2, + ?line file:delete(Fname), + + ?line check_pps(P0), + ok. + +bulk_init(Ls, Type, N, Config, V) -> + bulk_init(Ls, Type, N, 256, Config, V). + +bulk_init(Ls, Type, N, Est, Config, V) -> + T = init_table_test, + Fname = filename(T, Config), + ?line file:delete(Fname), + Input = init(Ls), + Args = [{ram_file,false}, {type,Type},{keypos,1},{file,Fname}, + {estimated_no_objects, Est},{version,V}], + ?line {ok, T} = dets:open_file(T, Args), + ?line ok = dets:init_table(T, Input), + ?line All = sort(get_all_objects(T)), + ?line FAll = get_all_objects_fast(T), + ?line true = All =:= sort(FAll), + ?line true = length(All) =:= N, + ?line true = dets:info(T, size) =:= N, + ?line ok = dets:close(T), + + ?line {ok, T} = dets:open_file(T, Args), + ?line All2 = sort(get_all_objects(T)), + ?line FAll2 = get_all_objects_fast(T), + ?line true = All =:= All2, + ?line true = All =:= sort(FAll2), + ?line ok = dets:close(T), + ?line file:delete(Fname). + +init(L) -> + fun(close) -> + ok; + (read) when [] =:= L -> + end_of_input; + (read) -> + [E | Es] = L, + {E, init(Es)} + end. + +init_fun(I, N) -> + fun(read) when I =:= N -> + end_of_input; + (read) -> + {NewN, Items} = items(I, N, 1000, []), + {Items, init_fun(NewN, N)}; + (close) -> + ignored + end. + +fast_init_table(Config) -> + V = 9, + TabRef = init_table_test, + Fname = filename(TabRef, Config), + ?line file:delete(Fname), + P0 = pps(), + + Args = [{file,Fname},{version,V},{auto_save,120000}], + + Source = init_table_test_source, + SourceFname = filename(Source, Config), + ?line file:delete(SourceFname), + SourceArgs = [{file,SourceFname},{version,V},{auto_save,120000}], + + ?line {ok, Source} = dets:open_file(Source, SourceArgs), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', _} = + (catch dets:init_table(TabRef, fun(foo) -> bar end, {format,bchunk})), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', _} = (catch dets:init_table(TabRef, fun() -> foo end, + {format,bchunk})), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', {badarg, _}} = + (catch dets:init_table(TabRef, nofun, {format,bchunk})), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line away = (catch dets:init_table(TabRef, fun(_) -> throw(away) end, + {format,bchunk})), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {error, {init_fun, fopp}} = + dets:init_table(TabRef, fun(read) -> fopp end, {format,bchunk}), + dets:close(TabRef), + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line dets:safe_fixtable(TabRef, true), + ?line {error, {fixed_table, TabRef}} = + dets:init_table(TabRef, init([]), {format,bchunk}), + ?line dets:safe_fixtable(TabRef, false), + ?line ok = dets:close(TabRef), + + ?line {ok, _} = dets:open_file(TabRef, [{access,read} | Args]), + ?line {error, {access_mode, Fname}} = + dets:init_table(TabRef, init([]), {format,bchunk}), + ?line ok = dets:close(TabRef), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {error, {init_fun,{1,2}}} = + dets:init_table(TabRef, init([[{1,2},bad,{3,4}]]), {format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {error, {init_fun, end_of_input}} = + dets:init_table(TabRef, init([]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line {'EXIT', {badarg, _}} = + (catch dets:init_table(TabRef, init([]),{format,foppla})), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line ok = ins(TabRef, 100), + + ?line [BParms | Objs] = collect_bchunk(TabRef, init_bchunk(TabRef)), + ?line Parms = binary_to_term(BParms), + ?line {error, {init_fun, <<"foobar">>}} = + dets:init_table(TabRef, init([[<<"foobar">>]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line Parms1 = setelement(1, Parms, foobar), + BParms1 = term_to_binary(Parms1), + ?line {error, {init_fun, BParms1}} = + dets:init_table(TabRef, init([[BParms1 | Objs]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + [{Sz1,No1} | NoColls17] = element(tuple_size(Parms), Parms), + Parms2 = setelement(tuple_size(Parms), Parms, [{Sz1,No1+1} | NoColls17]), + BParms2 = term_to_binary(Parms2), + ?line {error, invalid_objects_list} = + dets:init_table(TabRef, init([[BParms2 | Objs]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line [{LSize1,Slot1,Obj1} | ObjsRest] = Objs, + + ?line BadSize = byte_size(Obj1)-1, + ?line <> = Obj1, + ?line BadObjs = [{LSize1,Slot1,BadSizeObj} | ObjsRest], + ?line {error, invalid_objects_list} = + dets:init_table(TabRef, init([[BParms | BadObjs]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line <> = list_to_binary(lists:duplicate(16,Obj1)), + ?line BigObj = <<(Size*16):32,BigObj0/binary>>, + ?line BadColl = [BParms, {LSize1+4,Slot1,BigObj} | ObjsRest], + ?line {error, invalid_objects_list} = + dets:init_table(TabRef, init([BadColl]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, Args), + BadObj = <<"foobar">>, + ?line {error, invalid_objects_list} = + dets:init_table(TabRef, init([[BParms, BadObj]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(TabRef, [{type,bag} | Args]), + ?line {error, {init_fun, _}} = + dets:init_table(TabRef, init([[BParms]]),{format,bchunk}), + ?line _ = dets:close(TabRef), + ?line file:delete(Fname), + + ?line ok = dets:close(Source), + ?line file:delete(SourceFname), + + L1 = [{1,a},{2,b},{3,c},{4,d}], + fast_bulk_init(L1, set, 4, 4, Config, V), + L2 = [{1,a},{2,b},{2,q},{3,c},{4,d},{4,e},{2,q}], + fast_bulk_init(L2, set, 4, 4, Config, V), + fast_bulk_init(L2, bag, 6, 4, Config, V), + fast_bulk_init(L2, duplicate_bag, 7, 4, Config, V), + fast_bulk_init(L1, set, 4, 4, 512, Config, V), + fast_bulk_init([], set, 0, 0, 10000, Config, V), + file:delete(Fname), + + %% Initiate a file that contains a lot of objects. + ?line {ok, _} = dets:open_file(Source, [{min_no_slots,10000} | SourceArgs]), + Fun1 = init_fun(0, 10000), + ?line ok = dets:init_table(Source, Fun1, {format,term}), + + ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,10000} | Args]), + ?line ok = ins(TabRef, 6000), + Fun2 = init_bchunk(Source), + ?line true = + dets:is_compatible_bchunk_format(TabRef, + dets:info(Source, bchunk_format)), + ?line false = dets:is_compatible_bchunk_format(TabRef, <<"foobar">>), + ?line ok = dets:init_table(TabRef, Fun2, {format, bchunk}), + ?line ok = dets:close(Source), + ?line file:delete(SourceFname), + ?line All = sort(get_all_objects(TabRef)), + ?line FAll = get_all_objects_fast(TabRef), + ?line true = All =:= sort(FAll), + ?line true = length(All) =:= 10000, + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% Initiate inserts fewer objects than the table contains. + ?line {ok, _} = dets:open_file(Source, [{min_no_slots,1000} | SourceArgs]), + ?line ok = ins(Source, 4000), + + ?line {ok, _} = dets:open_file(TabRef, [{min_no_slots,1000} | Args]), + ?line ok = ins(TabRef, 6000), + ?line FileSize1 = dets:info(TabRef, file_size), + Fun4 = init_bchunk(Source), + ?line ok = dets:init_table(TabRef, Fun4, {format, bchunk}), + ?line ok = dets:close(Source), + ?line file:delete(SourceFname), + ?line FileSize2 = dets:info(TabRef, file_size), + ?line All_2 = sort(get_all_objects(TabRef)), + ?line FAll_2 = get_all_objects_fast(TabRef), + ?line true = All_2 =:= sort(FAll_2), + ?line true = length(All_2) =:= 4000, + ?line ok = dets:close(TabRef), + ?line true = FileSize1 > FileSize2, + + %% Bchunk and fixed table. + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line NoItems = dets:info(TabRef, no_objects), + ?line AllObjects1 = sort(get_all_objects_fast(TabRef)), + ?line dets:safe_fixtable(TabRef, true), + ?line true = dets:info(TabRef, fixed), + ?line Cont1 = init_bchunk(TabRef), + ?line NoDel = + dets:select_delete(TabRef, [{{'_',{item,'_','_'}},[],[true]}]), + ?line true = (NoDel > 0), + ?line AllObjects2 = sort(get_all_objects_fast(TabRef)), + ?line true = dets:info(TabRef, fixed), + ?line Cont2 = init_bchunk(TabRef), + ?line NoItems2 = dets:info(TabRef, no_objects), + ?line true = (NoItems =:= NoItems2 + NoDel), + ?line NoDel2 = dets:select_delete(TabRef, [{'_',[],[true]}]), + ?line true = (NoDel2 > 0), + ?line AllObjects3 = sort(get_all_objects_fast(TabRef)), + ?line NoItems3 = dets:info(TabRef, no_objects), + ?line true = (NoItems3 =:= 0), + ?line true = dets:info(TabRef, fixed), + ?line true = (NoItems2 =:= NoItems3 + NoDel2), + ?line Cont3 = init_bchunk(TabRef), + + ?line BinColl1 = collect_bchunk(TabRef, Cont1), + ?line BinColl2 = collect_bchunk(TabRef, Cont2), + ?line BinColl3 = collect_bchunk(TabRef, Cont3), + ?line dets:safe_fixtable(TabRef, false), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% Now check that the above collected binaries are correct. + ?line {ok, _} = dets:open_file(TabRef, Args), + ?line ok = dets:init_table(TabRef, init([BinColl1]),{format,bchunk}), + ?line true = (AllObjects1 =:= sort(get_all_objects_fast(TabRef))), + ?line true = (length(AllObjects1) =:= dets:info(TabRef, no_objects)), + ?line ok = dets:init_table(TabRef, init([BinColl2]),{format,bchunk}), + ?line true = (AllObjects2 =:= sort(get_all_objects_fast(TabRef))), + ?line true = (length(AllObjects2) =:= dets:info(TabRef, no_objects)), + ?line ok = dets:init_table(TabRef, init([BinColl3]),{format,bchunk}), + ?line true = (AllObjects3 =:= sort(get_all_objects_fast(TabRef))), + ?line true = (length(AllObjects3) =:= dets:info(TabRef, no_objects)), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + ?line check_pps(P0), + ok. + +fast_bulk_init(L, Type, N, NoKeys, Config, V) -> + fast_bulk_init(L, Type, N, NoKeys, 256, Config, V). + +fast_bulk_init(L, Type, N, NoKeys, Est, Config, V) -> + T = init_table_test, + Fname = filename(T, Config), + ?line file:delete(Fname), + + Args0 = [{ram_file,false}, {type,Type},{keypos,1}, + {estimated_no_objects, Est},{version,V}], + Args = [{file,Fname} | Args0], + S = init_table_test_source, + SFname = filename(S, Config), + ?line file:delete(SFname), + SArgs = [{file,SFname} | Args0], + + ?line {ok, S} = dets:open_file(S, SArgs), + ?line ok = dets:insert(S, L), + + Input = init_bchunk(S), + ?line {ok, T} = dets:open_file(T, Args), + ?line ok = dets:init_table(T, Input, [{format,bchunk}]), + ?line All = sort(get_all_objects(T)), + ?line FAll = get_all_objects_fast(T), + ?line true = All =:= sort(FAll), + ?line true = length(All) =:= N, + ?line true = dets:info(T, size) =:= N, + ?line true = dets:info(T, no_keys) =:= NoKeys, + ?line ok = dets:close(T), + + ?line {ok, T} = dets:open_file(T, Args), + ?line All2 = sort(get_all_objects(T)), + ?line FAll2 = get_all_objects_fast(T), + ?line true = All =:= All2, + ?line true = All =:= sort(FAll2), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + ?line ok = dets:close(S), + ?line file:delete(SFname), + ok. + +init_bchunk(T) -> + Start = dets:bchunk(T, start), + init_bchunk(T, Start). + +init_bchunk(Tab, State) -> + fun(read) when State =:= '$end_of_table' -> + end_of_input; + (read) when element(1, State) =:= error -> + State; + (read) -> + {Cont, Objs} = State, + {Objs, init_bchunk(Tab, dets:bchunk(Tab, Cont))}; + (close) -> + ok + end. + +collect_bchunk(Tab, Fun) -> + collect_bchunk(Tab, Fun, []). + +collect_bchunk(Tab, Fun, L) -> + case Fun(read) of + end_of_input -> + lists:append(lists:reverse(L)); + {Objs, Fun2} when is_list(Objs) -> + collect_bchunk(Tab, Fun2, [Objs | L]); + Error -> + Error + end. + +items(I, N, C, L) when I =:= N; C =:= 0 -> + {I, L}; +items(I, N, C, L) -> + items(I+1, N, C-1, [{I, item(I)} | L]). + +repair_v8(doc) -> + ["open_file and repair."]; +repair_v8(suite) -> + []; +repair_v8(Config) when is_list(Config) -> + repair(Config, 8). + +repair_v9(doc) -> + ["open_file and repair."]; +repair_v9(suite) -> + []; +repair_v9(Config) when is_list(Config) -> + %% Convert from format 9 to format 8. + T = convert_98, + Fname = filename(T, Config), + ?line file:delete(Fname), + ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,9}, + {type,duplicate_bag}]), + ?line 9 = dets:info(T, version), + ?line true = is_binary(dets:info(T, bchunk_format)), + ?line ok = dets:insert(T, [{1,a},{2,b},{1,c},{2,c},{1,c},{2,a},{1,b}]), + ?line dets:close(T), + ?line {error, {version_mismatch, _}} = + dets:open_file(T, [{file,Fname},{version,8},{type,duplicate_bag}]), + ?line {ok, _} = dets:open_file(T, [{file,Fname},{version,8}, + {type,duplicate_bag},{repair,force}]), + ?line 8 = dets:info(T, version), + ?line true = undefined =:= dets:info(T, bchunk_format), + ?line [{1,a},{1,b},{1,c},{1,c}] = sort(dets:lookup(T, 1)), + ?line [{2,a},{2,b},{2,c}] = sort(dets:lookup(T, 2)), + ?line 7 = dets:info(T, no_objects), + ?line no_keys_test(T), + ?line _ = histogram(T, silent), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + %% The short lived format 9(a). + %% Not very throughly tested here. + A9 = a9, + ?line Version9aS = filename:join(?datadir(Config), "version_9a.dets"), + ?line Version9aT = filename('v9a.dets', Config), + ?line {ok, _} = file:copy(Version9aS, Version9aT), + ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT}]), + ?line undefined = dets:info(A9, bchunk_format), + ?line [{1,a},{2,b},{3,c}] = sort(dets:match_object(A9, '_')), + ?line ok = dets:insert(A9, {4,d}), + ?line ok = dets:close(A9), + ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT}]), + ?line {error, old_version} = dets:bchunk(A9, start), + ?line ok = dets:close(A9), + ?line io:format("Expect forced repair:~n"), + ?line {ok, A9} = dets:open_file(A9, [{file,Version9aT},{repair,force}]), + ?line {_, _} = dets:bchunk(A9, start), + ?line ok = dets:close(A9), + ?line file:delete(Version9aT), + + repair(Config, 9). + +repair(Config, V) -> + TabRef = repair_test, + Fname = filename(TabRef, Config), + ?line file:delete(Fname), + HeadSize = headsz(V), + + P0 = pps(), + ?line {'EXIT', {badarg, _}} = + (catch dets:open_file(TabRef, [{min_no_slots,1000}, + {max_no_slots,500}])), + ?line {error,{file_error,hoppla,enoent}} = dets:file_info(hoppla), + ?line {error,{file_error,Fname,enoent}} = + dets:open_file(TabRef, [{file, Fname}, {access, read}]), + + %% compacting, and some kind of test that free lists are saved OK on file + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line 0 = dets:info(TabRef, size), + ?line ok = ins(TabRef, 30000), + ?line ok = del(TabRef, 30000, 3), + ?line ok = dets:close(TabRef), + ?line {error, {access_mode,Fname}} = + dets:open_file(foo, [{file,Fname},{repair,force},{access,read}]), + ?line {ok, Ref3} = dets:open_file(Fname), % no repair! + ?line 20000 = dets:info(Ref3, size), + ?line 20000 = dets:foldl(fun(_, N) -> N+1 end, 0, Ref3), + ?line 20000 = count_objects_quite_fast(Ref3), % actually a test of match + ?line no_keys_test(Ref3), + ?line ok = dets:close(Ref3), + if + V =:= 8 -> + ?line {ok, TabRef} = dets:open_file(TabRef, + [{file, Fname},{version,V},{access,read}]), + ?line ok = dets:close(TabRef), + ?line io:format("Expect compacting repair:~n"), + ?line {ok, TabRef} = dets:open_file(TabRef, + [{file, Fname},{version,V}]), + ?line 20000 = dets:info(TabRef, size), + ?line _ = histogram(TabRef, silent), + ?line ok = dets:close(TabRef); + true -> + ok + end, + ?line {error,{keypos_mismatch,Fname}} = + dets:open_file(TabRef, [{file, Fname},{keypos,17}]), + ?line {error,{type_mismatch,Fname}} = + dets:open_file(TabRef, [{file, Fname},{type,duplicate_bag}]), + + %% make one of the temporary files unwritable + TmpFile = if + V =:= 8 -> + Fname ++ ".TMP.10000"; + true -> Fname ++ ".TMP.1" + end, + ?line file:delete(TmpFile), + ?line {ok, TmpFd} = file:open(TmpFile, [read,write]), + ?line ok = file:close(TmpFd), + ?line unwritable(TmpFile), + ?line {error,{file_error,TmpFile,eacces}} = dets:fsck(Fname, V), + ?line {ok, _} = dets:open_file(TabRef, + [{repair,false},{file, Fname},{version,V}]), + ?line 20000 = length(get_all_objects(TabRef)), + ?line _ = histogram(TabRef, silent), + ?line 20000 = length(get_all_objects_fast(TabRef)), + ?line ok = dets:close(TabRef), + ?line writable(TmpFile), + ?line file:delete(TmpFile), + + ?line truncate(Fname, HeadSize + 10), + ?line {error,{not_closed, Fname}} = + dets:open_file(TabRef, [{file, Fname}, {access, read}]), + ?line {error,{not_closed, Fname}} = + dets:open_file(TabRef, [{file, Fname}, {access, read}, + {repair,force}]), + ?line {error,{needs_repair, Fname}} = + dets:open_file(TabRef, [{file, Fname}, {repair, false}]), + ?line file:delete(Fname), + + %% truncated file header + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = ins(TabRef, 100), + ?line ok = dets:close(TabRef), + ?line truncate(Fname, HeadSize - 10), + %% a new file is created ('tooshort') + ?line {ok, TabRef} = dets:open_file(TabRef, + [{file,Fname},{version,V}, + {min_no_slots,1000}, + {max_no_slots,1000000}]), + case dets:info(TabRef, no_slots) of + undefined -> ok; + {Min1,Slot1,Max1} -> + ?line true = Min1 =< Slot1, true = Slot1 =< Max1, + ?line true = 1000 < Min1, true = 1000+256 > Min1, + ?line true = 1000000 < Max1, true = (1 bsl 20)+256 > Max1 + end, + ?line 0 = dets:info(TabRef, size), + ?line no_keys_test(TabRef), + ?line _ = histogram(TabRef, silent), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% version bump (v8) + ?line Version7S = filename:join(?datadir(Config), "version_r2d.dets"), + ?line Version7T = filename('v2.dets', Config), + ?line {ok, _} = file:copy(Version7S, Version7T), + ?line {error,{version_bump, Version7T}} = dets:open_file(Version7T), + ?line {error,{version_bump, Version7T}} = + dets:open_file(Version7T, [{file,Version7T},{repair,false}]), + ?line {error,{version_bump, Version7T}} = + dets:open_file(Version7T, [{file, Version7T}, {access, read}]), + ?line io:format("Expect upgrade:~n"), + ?line {ok, _} = dets:open_file(Version7T, + [{file, Version7T},{version, V}]), + ?line [{1,a},{2,b}] = sort(get_all_objects(Version7T)), + ?line [{1,a},{2,b}] = sort(get_all_objects_fast(Version7T)), + Phash = if + V =:= 8 -> phash; + true -> phash2 + end, + ?line Phash = dets:info(Version7T, hash), + ?line _ = histogram(Version7T, silent), + ?line ok = dets:close(Version7T), + ?line {ok, _} = dets:open_file(Version7T, [{file, Version7T}]), + ?line Phash = dets:info(Version7T, hash), + ?line ok = dets:close(Version7T), + ?line file:delete(Version7T), + + %% converting free lists + ?line Version8aS = filename:join(?datadir(Config), "version_r3b02.dets"), + ?line Version8aT = filename('v3.dets', Config), + ?line {ok, _} = file:copy(Version8aS, Version8aT), + %% min_no_slots and max_no_slots are ignored - no repair is taking place + ?line {ok, _} = dets:open_file(version_8a, + [{file, Version8aT},{min_no_slots,1000}, + {max_no_slots,100000}]), + ?line [{1,b},{2,a},{a,1},{b,2}] = sort(get_all_objects(version_8a)), + ?line [{1,b},{2,a},{a,1},{b,2}] = sort(get_all_objects_fast(version_8a)), + ?line ok = ins(version_8a, 1000), + ?line 1002 = dets:info(version_8a, size), + ?line no_keys_test(version_8a), + ?line All8a = sort(get_all_objects(version_8a)), + ?line 1002 = length(All8a), + ?line FAll8a = sort(get_all_objects_fast(version_8a)), + ?line true = sort(All8a) =:= sort(FAll8a), + ?line ok = del(version_8a, 300, 3), + ?line 902 = dets:info(version_8a, size), + ?line no_keys_test(version_8a), + ?line All8a2 = sort(get_all_objects(version_8a)), + ?line 902 = length(All8a2), + ?line FAll8a2 = sort(get_all_objects_fast(version_8a)), + ?line true = sort(All8a2) =:= sort(FAll8a2), + ?line _ = histogram(version_8a, silent), + ?line ok = dets:close(version_8a), + ?line file:delete(Version8aT), + + %% will fail unless the slots are properly sorted when repairing (v8) + BArgs = [{file, Fname},{type,duplicate_bag}, + {delayed_write,{3000,10000}},{version,V}], + ?line {ok, TabRef} = dets:open_file(TabRef, BArgs), + Seq = seq(1, 500), + Small = map(fun(X) -> {X,X} end, Seq), + Big = map(fun(X) -> erlang:make_tuple(20, X) end, Seq), + ?line ok = dets:insert(TabRef, Small), + ?line ok = dets:insert(TabRef, Big), + ?line ok = dets:insert(TabRef, Small), + ?line ok = dets:insert(TabRef, Big), + ?line All = sort(safe_get_all_objects(TabRef)), + ?line ok = dets:close(TabRef), + ?line io:format("Expect forced repair:~n"), + ?line {ok, _} = + dets:open_file(TabRef, [{repair,force},{min_no_slots,2000} | BArgs]), + if + V =:= 9 -> + ?line {MinNoSlots,_,MaxNoSlots} = dets:info(TabRef, no_slots), + ?line ok = dets:close(TabRef), + ?line io:format("Expect compaction:~n"), + ?line {ok, _} = + dets:open_file(TabRef, [{repair,force}, + {min_no_slots,MinNoSlots}, + {max_no_slots,MaxNoSlots} | BArgs]); + true -> + ok + end, + ?line All2 = get_all_objects(TabRef), + ?line true = All =:= sort(All2), + ?line FAll2 = get_all_objects_fast(TabRef), + ?line true = All =:= sort(FAll2), + ?line true = length(All) =:= dets:info(TabRef, size), + ?line no_keys_test(TabRef), + Fun = fun(X) -> 4 = length(dets:lookup(TabRef, X)) end, + ?line foreach(Fun, Seq), + ?line _ = histogram(TabRef, silent), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% object bigger than segments, the "hole" is taken care of + ?line {ok, TabRef} = dets:open_file(TabRef, [{file, Fname},{version,V}]), + Tuple = erlang:make_tuple(1000, foobar), % > 2 kB + ?line ok = dets:insert(TabRef, Tuple), + %% at least one full segment (objects smaller than 2 kB): + ?line ins(TabRef, 2000), + ?line ok = dets:close(TabRef), + + if + V =:= 8 -> + %% first estimated number of objects is wrong, repair once more + ?line {ok, Fd} = file:open(Fname, read_write), + NoPos = HeadSize - 8, % no_objects + ?line file:pwrite(Fd, NoPos, <<0:32>>), % NoItems + ok = file:close(Fd), + ?line dets:fsck(Fname, V), + ?line {ok, _} = + dets:open_file(TabRef, + [{repair,false},{file, Fname},{version,V}]), + ?line 2001 = length(get_all_objects(TabRef)), + ?line _ = histogram(TabRef, silent), + ?line 2001 = length(get_all_objects_fast(TabRef)), + ?line ok = dets:close(TabRef); + true -> + ok + end, + + ?line {ok, _} = + dets:open_file(TabRef, + [{repair,false},{file, Fname},{version,V}]), + ?line {ok, ObjPos} = dets:where(TabRef, {66,{item,number,66}}), + ?line ok = dets:close(TabRef), + %% Damaged object. + Pos = 12, % v9: compaction fails, proper repair follows + crash(Fname, ObjPos+Pos), + ?line io:format( + "Expect forced repair (possibly after attempted compaction):~n"), + ?line {ok, _} = + dets:open_file(TabRef, [{repair,force},{file, Fname},{version,V}]), + ?line true = dets:info(TabRef, size) < 2001, + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% The file is smaller than the padded object. + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = dets:insert(TabRef, Tuple), + ?line ok = dets:close(TabRef), + ?line io:format("Expect forced repair or compaction:~n"), + ?line {ok, _} = + dets:open_file(TabRef, [{repair,force},{file, Fname},{version,V}]), + ?line true = 1 =:= dets:info(TabRef, size), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% Damaged free lists. + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = ins(TabRef, 300), + ?line ok = dets:sync(TabRef), + ?line ok = del(TabRef, 300, 3), + %% FileSize is approximately where the free lists will be written. + ?line FileSize = dets:info(TabRef, memory), + ?line ok = dets:close(TabRef), + crash(Fname, FileSize+20), + ?line {error, {bad_freelists, Fname}} = + dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line file:delete(Fname), + + %% File not closed, opening with read and read_write access tried. + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = ins(TabRef, 300), + ?line ok = dets:close(TabRef), + ?line crash(Fname, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED), + ?line {error, {not_closed, Fname}} = + dets:open_file(foo, [{file,Fname},{version,V},{repair,force}, + {access,read}]), + ?line {error, {not_closed, Fname}} = + dets:open_file(foo, [{file,Fname},{version,V},{repair,true}, + {access,read}]), + ?line io:format("Expect repair:~n"), + ?line {ok, TabRef} = + dets:open_file(TabRef, [{file,Fname},{version,V},{repair,true}, + {access,read_write}]), + ?line ok = dets:close(TabRef), + ?line crash(Fname, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED), + ?line io:format("Expect forced repair:~n"), + ?line {ok, TabRef} = + dets:open_file(TabRef, [{file,Fname},{version,V},{repair,force}, + {access,read_write}]), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + %% The size of an object is huge. + ?line {ok, TabRef} = dets:open_file(TabRef, [{file,Fname},{version,V}]), + ?line ok = dets:insert(TabRef, [{1,2,3},{2,3,4}]), + ?line {ok, ObjPos2} = dets:where(TabRef, {1,2,3}), + ?line ok = dets:close(TabRef), + ObjPos3 = if + V =:= 8 -> ObjPos2 + 4; + V =:= 9 -> ObjPos2 + end, + crash(Fname, ObjPos3, 255), + ?line io:format("Expect forced repair:~n"), + ?line {ok, TabRef} = + dets:open_file(TabRef, [{file,Fname},{version,V},{repair,force}]), + ?line ok = dets:close(TabRef), + ?line file:delete(Fname), + + ?line check_pps(P0), + ok. + +hash_v8b_v8c(doc) -> + ["Test the use of different hashing algorithms in v8b and v8c of the " + "Dets file format."]; +hash_v8b_v8c(suite) -> + []; +hash_v8b_v8c(Config) when is_list(Config) -> + ?line Source = + filename:join(?datadir(Config), "dets_test_v8b.dets"), + %% Little endian version of old file (there is an endianess bug in + %% the old hash). This is all about version 8 of the dets file format. + + P0 = pps(), + ?line SourceLE = + filename:join(?datadir(Config), + "dets_test_v8b_little_endian.dets"), + ?line Target1 = filename('oldhash1.dets', Config), + ?line Target1LE = filename('oldhash1le.dets', Config), + ?line Target2 = filename('oldhash2.dets', Config), + ?line {ok, Bin} = file:read_file(Source), + ?line {ok, BinLE} = file:read_file(SourceLE), + ?line ok = file:write_file(Target1,Bin), + ?line ok = file:write_file(Target1LE,BinLE), + ?line ok = file:write_file(Target2,Bin), + ?line {ok, d1} = dets:open_file(d1,[{file,Target1}]), + ?line {ok, d1le} = dets:open_file(d1le,[{file,Target1LE}]), + ?line {ok, d2} = dets:open_file(d2,[{file,Target2},{repair,force}, + {version,8}]), + ?line FF = fun(N,_F,_T) when N > 16#FFFFFFFFFFFFFFFF -> + ok; + (N,F,T) -> + V = integer_to_list(N), + case dets:lookup(T,N) of + [{N,V}] -> + F(N*2,F,T); + _Error -> + exit({failed,{lookup,T,N}}) + end + end, + ?line Mess = case (catch FF(1,FF,d1)) of + {'EXIT', {failed, {lookup,_,_}}} -> + ?line ok = dets:close(d1), + ?line FF(1,FF,d1le), + ?line hash = dets:info(d1le,hash), + ?line dets:insert(d1le,{33333333333,hejsan}), + ?line [{33333333333,hejsan}] = + dets:lookup(d1le,33333333333), + ?line ok = dets:close(d1le), + ?line {ok, d1le} = dets:open_file(d1le, + [{file,Target1LE}]), + ?line [{33333333333,hejsan}] = + dets:lookup(d1le,33333333333), + ?line FF(1,FF,d1le), + ?line ok = dets:close(d1le), + "Seems to be a little endian machine"; + {'EXIT', Fault} -> + exit(Fault); + _ -> + ?line ok = dets:close(d1le), + ?line hash = dets:info(d1,hash), + ?line dets:insert(d1,{33333333333,hejsan}), + ?line [{33333333333,hejsan}] = + dets:lookup(d1,33333333333), + ?line ok = dets:close(d1), + ?line {ok, d1} = dets:open_file(d1,[{file,Target1}]), + ?line [{33333333333,hejsan}] = + dets:lookup(d1,33333333333), + ?line FF(1,FF,d1), + ?line ok = dets:close(d1), + "Seems to be a big endian machine" + end, + ?line FF(1,FF,d2), + ?line phash = dets:info(d2,hash), + ?line ok = dets:close(d2), + ?line file:delete(Target1), + ?line file:delete(Target1LE), + ?line file:delete(Target2), + ?line check_pps(P0), + {comment, Mess}. + +phash(doc) -> + ["Test version 9(b) with erlang:phash/2 as hash function."]; +phash(suite) -> + []; +phash(Config) when is_list(Config) -> + T = phash, + Phash_v9bS = filename:join(?datadir(Config), "version_9b_phash.dat"), + Fname = filename('v9b.dets', Config), + ?line {ok, _} = file:copy(Phash_v9bS, Fname), + + %% Deleting all objects changes the hash function. + %% A feature... (it's for free) + ?line {ok, T} = dets:open_file(T, [{file, Fname}]), + ?line phash = dets:info(T, hash), + ?line dets:delete_all_objects(T), + ?line phash2 = dets:info(T, hash), + ?line [] = get_all_objects(T), + ?line [] = get_all_objects_fast(T), + ?line ok = dets:close(T), + + %% The hash function is kept when compacting a table. + ?line {ok, _} = file:copy(Phash_v9bS, Fname), + ?line io:format("Expect compaction:~n"), + ?line {ok, T} = dets:open_file(T, [{file, Fname},{repair,force}]), + ?line phash = dets:info(T, hash), + ?line [{1,a},{2,b},{3,c},{4,d},{5,e}] = + lists:sort(dets:lookup_keys(T, [1,2,3,4,5])), + ?line ok = dets:close(T), + + %% The hash function is updated when repairing a table (no cost). + ?line {ok, _} = file:copy(Phash_v9bS, Fname), + crash(Fname, ?CLOSED_PROPERLY_POS+3, 0), + ?line io:format("Expect repair:~n"), + ?line {ok, T} = dets:open_file(T, [{file, Fname}]), + ?line phash2 = dets:info(T, hash), + ?line [{1,a},{2,b},{3,c},{4,d},{5,e}] = + lists:sort(dets:lookup_keys(T, [1,2,3,4,5])), + ?line ok = dets:close(T), + + %% One cannot use the bchunk format when copying between a phash + %% table and a phash2 table. (There is no test for the case an R9 + %% (or later) node (using phash2) copies a table to an R8 node + %% (using phash).) See also the comment on HASH_PARMS in dets_v9.erl. + ?line {ok, _} = file:copy(Phash_v9bS, Fname), + ?line {ok, T} = dets:open_file(T, [{file, Fname}]), + ?line Type = dets:info(T, type), + ?line KeyPos = dets:info(T, keypos), + Input = init_bchunk(T), + T2 = phash_table, + Fname2 = filename(T2, Config), + Args = [{type,Type},{keypos,KeyPos},{version,9},{file,Fname2}], + ?line {ok, T2} = dets:open_file(T2, Args), + ?line {error, {init_fun, _}} = + dets:init_table(T2, Input, {format,bchunk}), + ?line _ = dets:close(T2), + ?line ok = dets:close(T), + ?line file:delete(Fname2), + + ?line file:delete(Fname), + ok. + +fold_v8(doc) -> + ["foldl, foldr, to_ets"]; +fold_v8(suite) -> + []; +fold_v8(Config) when is_list(Config) -> + fold(Config, 8). + +fold_v9(doc) -> + ["foldl, foldr, to_ets"]; +fold_v9(suite) -> + []; +fold_v9(Config) when is_list(Config) -> + fold(Config, 9). + +fold(Config, Version) -> + T = test_table, + N = 100, + ?line Fname = filename(T, Config), + ?line file:delete(Fname), + P0 = pps(), + + Args = [{version, Version}, {file,Fname}, {estimated_no_objects, N}], + ?line {ok, _} = dets:open_file(T, Args), + + ?line ok = ins(T, N), + + ?line Ets = ets:new(to_ets, [public]), + ?line dets:to_ets(T, Ets), + ?line true = N =:= ets:info(Ets, size), + ?line ets:delete(Ets), + + ?line Ets2 = ets:new(to_ets, [private]), + ?line dets:to_ets(T, Ets2), + ?line true = N =:= ets:info(Ets2, size), + ?line ets:delete(Ets2), + + ?line {'EXIT', {badarg, _}} = (catch dets:to_ets(T, not_an_ets_table)), + + F0 = fun(X, A) -> [X | A] end, + ?line true = N =:= length(dets:foldl(F0, [], T)), + ?line true = N =:= length(dets:foldr(F0, [], T)), + + F1 = fun(_X, _A) -> throw(away) end, + ?line away = (catch dets:foldl(F1, [], T)), + ?line away = (catch dets:foldr(F1, [], T)), + + F2 = fun(X, A) -> X + A end, + ?line {'EXIT', _} = (catch dets:foldl(F2, [], T)), + ?line {'EXIT', _} = (catch dets:foldr(F2, [], T)), + + F3 = fun(_X) -> throw(away) end, + ?line away = (catch dets:traverse(T, F3)), + + F4 = fun(X) -> X + 17 end, + ?line {'EXIT', _} = (catch dets:traverse(T, F4)), + + ?line F5 = fun(_X) -> done end, + ?line done = dets:traverse(T, F5), + + ?line {ok, ObjPos} = dets:where(T, {66,{item,number,66}}), + ?line ok = dets:close(T), + + %% Damaged object. + Pos = if + Version =:= 8 -> 12; + Version =:= 9 -> 8 + end, + crash(Fname, ObjPos+Pos), + ?line {ok, _} = dets:open_file(T, Args), + ?line io:format("Expect corrupt table:~n"), + ?line BadObject1 = dets:foldl(F0, [], T), + ?line bad_object(BadObject1, Fname), + ?line BadObject2 = dets:close(T), + ?line bad_object(BadObject2, Fname), + + ?line file:delete(Fname), + ?line check_pps(P0), + ok. + +fixtable_v8(doc) -> + ["Add objects to a fixed table."]; +fixtable_v8(suite) -> + []; +fixtable_v8(Config) when is_list(Config) -> + fixtable(Config, 8). + +fixtable_v9(doc) -> + ["Add objects to a fixed table."]; +fixtable_v9(suite) -> + []; +fixtable_v9(Config) when is_list(Config) -> + fixtable(Config, 9). + +fixtable(Config, Version) when is_list(Config) -> + T = fixtable, + ?line Fname = filename(fixtable, Config), + ?line file:delete(Fname), + Args = [{version,Version},{file,Fname}], + P0 = pps(), + ?line {ok, _} = dets:open_file(T, Args), + + %% badarg + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:safe_fixtable(no_table,true)), + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[T,undefined]}|_]}} = + (catch dets:safe_fixtable(T,undefined)), + + %% The table is not allowed to grow while the elements are inserted: + + ?line ok = ins(T, 500), + ?line dets:safe_fixtable(T, false), + %% Now the table can grow. At the same time as elements are inserted, + %% the table tries to catch up with the previously inserted elements. + ?line ok = ins(T, 1000), + ?line 1000 = dets:info(T, size), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(T, [{type, duplicate_bag} | Args]), + %% In a fixed table, delete and re-insert an object. + ?line ok = dets:insert(T, {1, a, b}), + ?line dets:safe_fixtable(T, true), + ?line ok = dets:match_delete(T, {1, a, b}), + ?line ok = dets:insert(T, {1, a, b}), + ?line dets:safe_fixtable(T, false), + ?line 1 = length(dets:match_object(T, '_')), + + ?line ok = dets:match_delete(T, '_'), + %% In a fixed table, delete and insert a smaller object. + ?line ok = dets:insert(T, {1, duplicate(100, e)}), + ?line dets:safe_fixtable(T, true), + ?line ok = dets:match_delete(T, {1, '_'}), + ?line ok = dets:insert(T, {1, a, b}), + ?line dets:safe_fixtable(T, false), + ?line 1 = length(dets:match_object(T, '_')), + + ?line ok = dets:delete_all_objects(T), + %% Like the last one, but one extra object. + ?line ok = dets:insert(T, {1, duplicate(100, e)}), + ?line ok = dets:insert(T, {2, duplicate(100, e)}), + ?line dets:safe_fixtable(T, true), + ?line ok = dets:match_delete(T, {1, '_'}), + ?line ok = dets:insert(T, {1, a, b}), + ?line dets:safe_fixtable(T, false), + ?line 2 = length(dets:match_object(T, '_')), + ?line dets:safe_fixtable(T, true), + ?line ok = dets:delete_all_objects(T), + ?line true = dets:info(T, fixed), + ?line 0 = length(dets:match_object(T, '_')), + + ?line ok = dets:close(T), + ?line file:delete(Fname), + ?line check_pps(P0), + ok. + +match_v8(doc) -> + ["Matching objects of a fixed table."]; +match_v8(suite) -> + []; +match_v8(Config) when is_list(Config) -> + match(Config, 8). + +match_v9(doc) -> + ["Matching objects of a fixed table."]; +match_v9(suite) -> + []; +match_v9(Config) when is_list(Config) -> + match(Config, 9). + +match(Config, Version) -> + T = match, + ?line Fname = filename(match, Config), + ?line file:delete(Fname), + P0 = pps(), + + Args = [{version, Version}, {file,Fname}, {type, duplicate_bag}, + {estimated_no_objects,550}], + ?line {ok, _} = dets:open_file(T, Args), + ?line ok = dets:insert(T, {1, a, b}), + ?line ok = dets:insert(T, {1, b, a}), + ?line ok = dets:insert(T, {2, a, b}), + ?line ok = dets:insert(T, {2, b, a}), + + %% match, badarg + MSpec = [{'_',[],['$_']}], + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:match(no_table, '_')), + ?line {'EXIT', {badarg, [{dets,match,[T,'_',not_a_number]}|_]}} = + (catch dets:match(T, '_', not_a_number)), + ?line {EC1, _} = dets:select(T, MSpec, 1), + ?line {'EXIT', {badarg, [{dets,match,[EC1]}|_]}} = + (catch dets:match(EC1)), + + %% match_object, badarg + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:match_object(no_table, '_')), + ?line {'EXIT', {badarg, [{dets,match_object,[T,'_',not_a_number]}|_]}} = + (catch dets:match_object(T, '_', not_a_number)), + ?line {EC2, _} = dets:select(T, MSpec, 1), + ?line {'EXIT', {badarg, [{dets,match_object,[EC2]}|_]}} = + (catch dets:match_object(EC2)), + + dets:safe_fixtable(T, true), + ?line {[_, _], C1} = dets:match_object(T, '_', 2), + ?line {[_, _], C2} = dets:match_object(C1), + ?line '$end_of_table' = dets:match_object(C2), + ?line {[_, _], C3} = dets:match_object(T, {1, '_', '_'}, 100), + ?line '$end_of_table' = dets:match_object(C3), + ?line '$end_of_table' = dets:match_object(T, {'_'}, default), + ?line dets:safe_fixtable(T, false), + + ?line dets:safe_fixtable(T, true), + ?line {[_, _], C30} = dets:match(T, '$1', 2), + ?line {[_, _], C31} = dets:match(C30), + ?line '$end_of_table' = dets:match(C31), + ?line {[_, _], C32} = dets:match(T, {1, '$1', '_'}, 100), + ?line '$end_of_table' = dets:match(C32), + ?line '$end_of_table' = dets:match(T, {'_'}, default), + ?line dets:safe_fixtable(T, false), + ?line [[1],[1],[2],[2]] = sort(dets:match(T, {'$1','_','_'})), + + %% delete and insert while chunking + %% (this case almost worthless after changes in OTP-5232) + ?line ok = dets:match_delete(T, '_'), + L500 = seq(1, 500), + Fun = fun(X) -> ok = dets:insert(T, {X, a, b, c, d}) end, + ?line foreach(Fun, L500), + %% Select one object DI in L3 below to be deleted. + ?line {_, TmpCont} = dets:match_object(T, '_', 200), + ?line {_, TmpCont1} = dets:match_object(TmpCont), + ?line {TTL, _} = dets:match_object(TmpCont1), + ?line DI = if Version =:= 8 -> last(TTL); Version =:= 9 -> hd(TTL) end, + ?line dets:safe_fixtable(T, true), + ?line {L1, C20} = dets:match_object(T, '_', 200), + ?line true = 200 =< length(L1), + ?line ok = dets:match_delete(T, {'2','_','_'}), % no match + ?line ok = dets:match_delete(T, DI), % last object + Tiny = {1050}, + ?line ok = dets:insert(T, Tiny), + ?line true = member(Tiny, dets:match_object(T, '_')), + ?line {_L2, C21} = dets:match_object(C20), + ?line {_L3, _C22} = dets:match_object(C21), + %% It used to be that Tiny was not visible here, but since the + %% scanning of files was changed to inspect the free lists every + %% now and then it may very well be visible here. + %% ?line false = member(Tiny, _L3), + %% DI used to visible here, but the above mentioned modification + %% has changed that; it may or may not be visible. + %% ?line true = member(DI, _L3), + ?line dets:safe_fixtable(T, false), + ?line true = dets:member(T, 1050), + ?line true = member(Tiny, dets:match_object(T, '_')), + ?line false = member(DI, dets:match_object(T, '_')), + + ?line ok = dets:close(T), + ?line file:delete(Fname), + + N = 100, + ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]), + ?line ok = ins(T, N), + Obj = {66,{item,number,66}}, + Spec = {'_','_'}, + ?line {ok, ObjPos} = dets:where(T, Obj), + ?line ok = dets:close(T), + %% Damaged object. + crash(Fname, ObjPos+12), + ?line {ok, _} = dets:open_file(T, Args), + ?line io:format("Expect corrupt table:~n"), + ?line case ins(T, N) of + ok -> + ?line bad_object(dets:sync(T), Fname); + Else -> + ?line bad_object(Else, Fname) + end, + ?line io:format("Expect corrupt table:~n"), + ?line bad_object(dets:match(T, Spec), Fname), + ?line io:format("Expect corrupt table:~n"), + ?line bad_object(dets:match_delete(T, Spec), Fname), + ?line bad_object(dets:close(T), Fname), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]), + ?line ok = ins(T, N), + ?line {ok, ObjPos2} = dets:where(T, Obj), + ?line ok = dets:close(T), + + %% Damaged size of object. + %% In v8, there is a next pointer before the size. + CrashPos = if Version =:= 8 -> 5; Version =:= 9 -> 1 end, + crash(Fname, ObjPos2+CrashPos), + ?line {ok, _} = dets:open_file(T, Args), + ?line io:format("Expect corrupt table:~n"), + ?line case ins(T, N) of + ok -> + ?line bad_object(dets:sync(T), Fname); + Else2 -> + ?line bad_object(Else2, Fname) + end, + %% Just echoes... + ?line bad_object(dets:match(T, Spec), Fname), + ?line bad_object(dets:match_delete(T, Spec), Fname), + ?line bad_object(dets:close(T), Fname), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(T, [{estimated_no_objects,N} | Args]), + ?line ok = ins(T, N), + ?line {ok, ObjPos3} = dets:where(T, Obj), + ?line ok = dets:close(T), + + %% match_delete finds an error + CrashPos3 = if Version =:= 8 -> 12; Version =:= 9 -> 16 end, + crash(Fname, ObjPos3+CrashPos3), + ?line {ok, _} = dets:open_file(T, Args), + ?line bad_object(dets:match_delete(T, Spec), Fname), + ?line bad_object(dets:close(T), Fname), + ?line file:delete(Fname), + + %% The key is not fixed, but not all objects with the key are removed. + ?line {ok, _} = dets:open_file(T, Args), + ?line ok = dets:insert(T, [{1,a},{1,b},{1,c},{1,a},{1,b},{1,c}]), + ?line 6 = dets:info(T, size), + ?line ok = dets:match_delete(T, {'_',a}), + ?line 4 = dets:info(T, size), + ?line [{1,b},{1,b},{1,c},{1,c}] = + sort(dets:match_object(T,{'_','_'})), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + ?line check_pps(P0), + ok. + +select_v8(doc) -> + ["Selecting objects of a fixed table."]; +select_v8(suite) -> + []; +select_v8(Config) when is_list(Config) -> + select(Config, 8). + +select_v9(doc) -> + ["Selecting objects of a fixed table."]; +select_v9(suite) -> + []; +select_v9(Config) when is_list(Config) -> + select(Config, 9). + +select(Config, Version) -> + T = select, + ?line Fname = filename(select, Config), + ?line file:delete(Fname), + P0 = pps(), + + ?line Args = [{version,Version}, {file,Fname}, {type, duplicate_bag}, + {estimated_no_objects,550}], + ?line {ok, _} = dets:open_file(T, Args), + ?line ok = dets:insert(T, {1, a, b}), + ?line ok = dets:insert(T, {1, b, a}), + ?line ok = dets:insert(T, {2, a, b}), + ?line ok = dets:insert(T, {2, b, a}), + ?line ok = dets:insert(T, {3, a, b}), + ?line ok = dets:insert(T, {3, b, a}), + + %% badarg + MSpec = [{'_',[],['$_']}], + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:select(no_table, MSpec)), + ?line {'EXIT', {badarg, [{dets,select,[T,<<17>>]}|_]}} = + (catch dets:select(T, <<17>>)), + ?line {'EXIT', {badarg, [{dets,select,[T,[]]}|_]}} = + (catch dets:select(T, [])), + ?line {'EXIT', {badarg, [{dets,select,[T,MSpec,not_a_number]}|_]}} = + (catch dets:select(T, MSpec, not_a_number)), + ?line {EC, _} = dets:match(T, '_', 1), + ?line {'EXIT', {badarg, [{dets,select,[EC]}|_]}} = + (catch dets:select(EC)), + + AllSpec = [{'_',[],['$_']}], + + ?line dets:safe_fixtable(T, true), + ?line {[_, _], C1} = dets:select(T, AllSpec, 2), + ?line {[_, _], C2} = dets:select(C1), + ?line {[_, _], C2a} = dets:select(C2), + ?line '$end_of_table' = dets:select(C2a), + ?line {[_, _], C3} = dets:select(T, [{{1,'_','_'},[],['$_']}], 100), + ?line '$end_of_table' = dets:select(C3), + ?line '$end_of_table' = dets:select(T, [{{'_'},[],['$_']}], default), + ?line dets:safe_fixtable(T, false), + Sp1 = [{{1,'_','_'},[],['$_']},{{1,'_','_'},[],['$_']}, + {{2,'_','_'},[],['$_']}], + ?line [_,_,_,_] = dets:select(T, Sp1), + Sp2 = [{{1,'_','_'},[],['$_']},{{1,'_','_'},[],['$_']}, + {{'_','_','_'},[],['$_']}], + ?line [_,_,_,_,_,_] = dets:select(T, Sp2), + + AllDeleteSpec = [{'_',[],[true]}], + %% delete and insert while chunking + %% (this case almost worthless after changes in OTP-5232) + ?line 6 = dets:select_delete(T, AllDeleteSpec), + L500 = seq(1, 500), + Fun = fun(X) -> ok = dets:insert(T, {X, a, b, c, d}) end, + ?line foreach(Fun, L500), + %% Select one object DI in L3 below to be deleted. + ?line {_, TmpCont} = dets:match_object(T, '_', 200), + ?line {_, TmpCont1} = dets:match_object(TmpCont), + ?line {TTL, _} = dets:match_object(TmpCont1), + ?line DI = if Version =:= 8 -> last(TTL); Version =:= 9 -> hd(TTL) end, + ?line dets:safe_fixtable(T, true), + ?line {L1, C20} = dets:select(T, AllSpec, 200), + ?line true = 200 =< length(L1), + ?line 0 = dets:select_delete(T, [{{2,'_','_'},[],[true]}]), + ?line 1 = dets:select_delete(T, [{DI,[],[true]}]), % last object + Tiny = {1050}, + ?line ok = dets:insert(T, Tiny), + ?line true = member(Tiny, dets:select(T, AllSpec)), + ?line {_L2, C21} = dets:select(C20), + ?line {_L3, _C22} = dets:select(C21), + %% It used to be that Tiny was not visible here, but since the + %% scanning of files was changed to inspect the free lists every + %% now and then it may very well be visible here. + %% ?line false = member(Tiny, _L3), + %% DI used to visible here, but the above mentioned modification + %% has changed that; it may or may not be visible. + %% ?line true = member(DI, _L3), + ?line true = dets:member(T, 1050), + ?line true = member(Tiny, dets:select(T, AllSpec)), + ?line false = member(DI, dets:select(T, AllSpec)), + ?line dets:safe_fixtable(T, false), + ?line true = dets:member(T, 1050), + ?line true = member(Tiny, dets:select(T, AllSpec)), + ?line false = member(DI, dets:select(T, AllSpec)), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + %% The key is not fixed, but not all objects with the key are removed. + ?line {ok, _} = dets:open_file(T, Args), + ?line ok = dets:insert(T, [{1,a},{1,b},{1,c},{1,a},{1,b},{1,c}]), + ?line 6 = dets:info(T, size), + ?line 2 = dets:select_delete(T, [{{'_',a},[],[true]}]), + ?line 4 = dets:info(T, size), + ?line [{1,b},{1,b},{1,c},{1,c}] = sort(dets:select(T, AllSpec)), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + ?line check_pps(P0), + ok. + +update_counter(doc) -> + ["Test update_counter/1."]; +update_counter(suite) -> + []; +update_counter(Config) when is_list(Config) -> + T = update_counter, + ?line Fname = filename(select, Config), + ?line file:delete(Fname), + P0 = pps(), + + ?line {'EXIT', {badarg, [{dets,update_counter,[no_table,1,1]}|_]}} = + (catch dets:update_counter(no_table, 1, 1)), + + Args = [{file,Fname},{keypos,2}], + ?line {ok, _} = dets:open_file(T, [{type,set} | Args]), + ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)), + ?line ok = dets:insert(T, {1,a}), + ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)), + ?line ok = dets:insert(T, {0,1}), + ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)), + ?line ok = dets:insert(T, {0,1,0}), + ?line 1 = dets:update_counter(T, 1, 1), + ?line 2 = dets:update_counter(T, 1, 1), + ?line 6 = dets:update_counter(T, 1, {3,4}), + ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, {0,3})), + ?line ok = dets:close(T), + ?line file:delete(Fname), + + ?line {ok, _} = dets:open_file(T, [{type,bag} | Args]), + ?line ok = dets:insert(T, {0,1,0}), + ?line {'EXIT', {badarg, _}} = (catch dets:update_counter(T, 1, 1)), + ?line ok = dets:close(T), + ?line file:delete(Fname), + ?line check_pps(P0), + + ok. + +badarg(doc) -> + ["Call some functions with bad arguments."]; +badarg(suite) -> + []; +badarg(Config) when is_list(Config) -> + T = badarg, + ?line Fname = filename(select, Config), + ?line file:delete(Fname), + P0 = pps(), + + Args = [{file,Fname},{keypos,3}], + ?line {ok, _} = dets:open_file(T, [{type,set} | Args]), + % ?line dets:verbose(), + + %% badargs are tested in match, select and fixtable too. + + %% open + ?line {'EXIT', {badarg, [{dets,open_file,[{a,tuple},[]]}|_]}} = + (catch dets:open_file({a,tuple},[])), + ?line {'EXIT', {badarg, [{dets,open_file,[{a,tuple}]}|_]}} = + (catch dets:open_file({a,tuple})), + ?line {'EXIT', {badarg, [{dets,open_file,[file,[foo]]}|_]}} = + (catch dets:open_file(file,[foo])), + ?line {'EXIT', {badarg,[{dets,open_file,[{hej,san},[{type,set}|3]]}|_]}} = + (catch dets:open_file({hej,san},[{type,set}|3])), + + %% insert + ?line {'EXIT', {badarg, [{dets,insert,[no_table,{1,2}]}|_]}} = + (catch dets:insert(no_table, {1,2})), + ?line {'EXIT', {badarg, [{dets,insert,[no_table,[{1,2}]]}|_]}} = + (catch dets:insert(no_table, [{1,2}])), + ?line {'EXIT', {badarg, [{dets,insert,[T,{1,2}]}|_]}} = + (catch dets:insert(T, {1,2})), + ?line {'EXIT', {badarg, [{dets,insert,[T,[{1,2}]]}|_]}} = + (catch dets:insert(T, [{1,2}])), + ?line {'EXIT', {badarg, [{dets,insert,[T,[{1,2,3}|3]]}|_]}} = + (catch dets:insert(T, [{1,2,3} | 3])), + + %% lookup{_keys} + ?line {'EXIT', {badarg, [{dets,lookup_keys,[badarg,[]]}|_]}} = + (catch dets:lookup_keys(T, [])), + ?line {'EXIT', {badarg, [{dets,lookup,[no_table,1]}|_]}} = + (catch dets:lookup(no_table, 1)), + ?line {'EXIT', {badarg, [{dets,lookup_keys,[T,[1|2]]}|_]}} = + (catch dets:lookup_keys(T, [1 | 2])), + + %% member + ?line {'EXIT', {badarg, [{dets,member,[no_table,1]}|_]}} = + (catch dets:member(no_table, 1)), + + %% sync + ?line {'EXIT', {badarg, [{dets,sync,[no_table]}|_]}} = + (catch dets:sync(no_table)), + + %% delete{_keys} + ?line {'EXIT', {badarg, [{dets,delete,[no_table,1]}|_]}} = + (catch dets:delete(no_table, 1)), + + %% delete_object + ?line {'EXIT', {badarg, [{dets,delete_object,[no_table,{1,2,3}]}|_]}} = + (catch dets:delete_object(no_table, {1,2,3})), + ?line {'EXIT', {badarg, [{dets,delete_object,[T,{1,2}]}|_]}} = + (catch dets:delete_object(T, {1,2})), + ?line {'EXIT', {badarg, [{dets,delete_object,[no_table,[{1,2,3}]]}|_]}} = + (catch dets:delete_object(no_table, [{1,2,3}])), + ?line {'EXIT', {badarg, [{dets,delete_object,[T,[{1,2}]]}|_]}} = + (catch dets:delete_object(T, [{1,2}])), + ?line {'EXIT', {badarg, [{dets,delete_object,[T,[{1,2,3}|3]]}|_]}} = + (catch dets:delete_object(T, [{1,2,3} | 3])), + + %% first,next,slot + ?line {'EXIT', {badarg, [{dets,first,[no_table]}|_]}} = + (catch dets:first(no_table)), + ?line {'EXIT', {badarg, [{dets,next,[no_table,1]}|_]}} = + (catch dets:next(no_table, 1)), + ?line {'EXIT', {badarg, [{dets,slot,[no_table,0]}|_]}} = + (catch dets:slot(no_table, 0)), + + %% info + ?line undefined = dets:info(no_table), + ?line undefined = dets:info(no_table, foo), + ?line undefined = dets:info(T, foo), + + %% match_delete + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:match_delete(no_table, '_')), + + %% delete_all_objects + ?line {'EXIT', {badarg, [{dets,delete_all_objects,[no_table]}|_]}} = + (catch dets:delete_all_objects(no_table)), + + %% select_delete + MSpec = [{'_',[],['$_']}], + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:select_delete(no_table, MSpec)), + ?line {'EXIT', {badarg, [{dets,select_delete,[T, <<17>>]}|_]}} = + (catch dets:select_delete(T, <<17>>)), + + %% traverse, fold + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:traverse(no_table, fun(_) -> continue end)), + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:foldl(fun(_, A) -> A end, [], no_table)), + ?line {'EXIT', {badarg, [{dets,safe_fixtable,[no_table,true]}|_]}} = + (catch dets:foldr(fun(_, A) -> A end, [], no_table)), + + %% close + ?line ok = dets:close(T), + ?line {error, not_owner} = dets:close(T), + ?line {error, not_owner} = dets:close(T), + + %% init_table + ?line {'EXIT', {badarg,[{dets,init_table,[no_table,_,[]]}|_]}} = + (catch dets:init_table(no_table, fun(X) -> X end)), + ?line {'EXIT', {badarg,[{dets,init_table,[no_table,_,[]]}|_]}} = + (catch dets:init_table(no_table, fun(X) -> X end, [])), + + %% from_ets + Ets = ets:new(ets,[]), + ?line {'EXIT', {badarg,[{dets,from_ets,[no_table,_]}|_]}} = + (catch dets:from_ets(no_table, Ets)), + ets:delete(Ets), + + ?line {ok, T} = dets:open_file(T, Args), + ?line {error,incompatible_arguments} = + dets:open_file(T, [{type,bag} | Args]), + ?line ok = dets:close(T), + + file:delete(Fname), + ?line check_pps(P0), + ok. + +cache_sets_v8(doc) -> + ["Test the write cache for sets."]; +cache_sets_v8(suite) -> + []; +cache_sets_v8(Config) when is_list(Config) -> + cache_sets(Config, 8). + +cache_sets_v9(doc) -> + ["Test the write cache for sets."]; +cache_sets_v9(suite) -> + []; +cache_sets_v9(Config) when is_list(Config) -> + cache_sets(Config, 9). + +cache_sets(Config, Version) -> + Small = 2, + cache_sets(Config, {0,0}, false, Small, Version), + cache_sets(Config, {0,0}, true, Small, Version), + cache_sets(Config, {5000,5000}, false, Small, Version), + cache_sets(Config, {5000,5000}, true, Small, Version), + %% Objects of size greater than 2 kB. + Big = 1200, + cache_sets(Config, {0,0}, false, Big, Version), + cache_sets(Config, {0,0}, true, Big, Version), + cache_sets(Config, {5000,5000}, false, Big, Version), + cache_sets(Config, {5000,5000}, true, Big, Version), + ok. + +cache_sets(Config, DelayedWrite, Extra, Sz, Version) -> + %% Extra = bool(). Insert tuples until the tested key is not alone. + %% Sz = integer(). Size of the inserted tuples. + + T = cache, + ?line Fname = filename(cache, Config), + ?line file:delete(Fname), + P0 = pps(), + + ?line {ok, _} = + dets:open_file(T,[{version, Version}, {file,Fname}, {type,set}, + {delayed_write, DelayedWrite}]), + + Dups = 1, + {Key, OtherKeys} = + if + Extra -> + %% Insert enough to get three keys in some slot. + ?line dets:safe_fixtable(T, true), + insert_objs(T, 1, Sz, Dups); + true -> + {1,[]} + end, + Tuple = erlang:make_tuple(Sz, Key), + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + + %% The values of keys in the same slot as Key are checked. + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + + ?line ok = dets:insert(T, Tuple), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:insert(T, [Tuple,Tuple]), + %% If no delay, the cache gets filled immediately, and written. + ?line [Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]), + ?line true = dets:member(T, Key), + + %% If delay, this happens without file access. + ?line ok = dets:delete(T,Key), + ?line ok = dets:insert(T,Tuple), + ?line ok = dets:insert(T,Tuple), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:sync(T), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + + %% Key's objects are is on file only, + %% key 'toto' in the cache (if there is one). + ?line ok = dets:delete(T,toto), + ?line ok = dets:insert(T,[{toto,b},{toto,b}]), + ?line true = sort([Tuple,{toto,b}]) =:= + sort(dets:lookup_keys(T, [Key,toto])), + ?line true = dets:member(T, toto), + + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + ?line false = dets:member(T, Key), + ?line Size = dets:info(T, size), + + %% No object with the key on the file. + %% Delete, add one object. + Size1 = Size + 2, + del_and_ins(key, T, Size1, Tuple, Key, 1), + del_and_ins(object, T, Size1, Tuple, Key, 1), + del_and_ins(both, T, Size1, Tuple, Key, 1), + + %% One object with the key on the file. + %% Delete it, add one object. + Size2 = Size + 2, + del_and_ins(key, T, Size2, Tuple, Key, 1), + del_and_ins(object, T, Size2, Tuple, Key, 1), + del_and_ins(both, T, Size2, Tuple, Key, 1), + + %% Overwrite an old objekt with exactly the same size. + Element = case element(2, Tuple) of + 255 -> 254; + E -> E + 1 + end, + Tuple2 = setelement(2, Tuple, Element), + ?line ok = dets:sync(T), + ?line ok = dets:insert(T, Tuple2), + ?line [Tuple2] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:sync(T), + ?line [Tuple2] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + + ?line ok = dets:insert(T, {3,a}), + ?line ok = dets:insert(T, {3,b}), + ?line ok = dets:delete_object(T, {3,c}), + ?line ok = dets:delete_object(T, {3,d}), + ?line [{3,b}] = dets:lookup(T, 3), + + ?line ok = dets:delete(T, 3), + ?line ok = dets:delete_object(T, {3,c}), + ?line ok = dets:delete_object(T, {3,d}), + ?line [] = dets:lookup(T, 3), + + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + if + Extra -> + %% Let the table grow a while, if it needs to. + ?line All1 = get_all_objects(T), + ?line dets:safe_fixtable(T, false), + ?line timer:sleep(1000), + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + ?line dets:safe_fixtable(T, true), + ?line All2 = get_all_objects(T), + ?line FAll2 = get_all_objects_fast(T), + ?line true = sort(All2) =:= sort(FAll2), + case symdiff(All1, All2) of + {[],[]} -> ok; + {X,Y} -> + NoBad = length(X) + length(Y), + test_server:fail({sets,DelayedWrite,Extra,Sz,NoBad}) + end; + true -> + ok + end, + ?line ok = dets:close(T), + + file:delete(Fname), + ?line check_pps(P0), + ok. + +cache_bags_v8(doc) -> + ["Test the write cache for bags."]; +cache_bags_v8(suite) -> + []; +cache_bags_v8(Config) when is_list(Config) -> + cache_bags(Config, 8). + +cache_bags_v9(doc) -> + ["Test the write cache for bags."]; +cache_bags_v9(suite) -> + []; +cache_bags_v9(Config) when is_list(Config) -> + cache_bags(Config, 9). + +cache_bags(Config, Version) -> + Small = 2, + cache_bags(Config, {0,0}, false, Small, Version), + cache_bags(Config, {0,0}, true, Small, Version), + cache_bags(Config, {5000,5000}, false, Small, Version), + cache_bags(Config, {5000,5000}, true, Small, Version), + %% Objects of size greater than 2 kB. + Big = 1200, + cache_bags(Config, {0,0}, false, Big, Version), + cache_bags(Config, {0,0}, true, Big, Version), + cache_bags(Config, {5000,5000}, false, Big, Version), + cache_bags(Config, {5000,5000}, true, Big, Version), + ok. + +cache_bags(Config, DelayedWrite, Extra, Sz, Version) -> + %% Extra = bool(). Insert tuples until the tested key is not alone. + %% Sz = integer(). Size of the inserted tuples. + + T = cache, + ?line Fname = filename(cache, Config), + ?line file:delete(Fname), + P0 = pps(), + + ?line {ok, _} = + dets:open_file(T,[{version, Version}, {file,Fname}, {type,bag}, + {delayed_write, DelayedWrite}]), + + Dups = 1, + {Key, OtherKeys} = + if + Extra -> + %% Insert enough to get three keys in some slot. + ?line dets:safe_fixtable(T, true), + insert_objs(T, 1, Sz, Dups); + true -> + {1,[]} + end, + Tuple = erlang:make_tuple(Sz, Key), + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + + %% The values of keys in the same slot as Key are checked. + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + + ?line ok = dets:insert(T, Tuple), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:insert(T, [Tuple,Tuple]), + %% If no delay, the cache gets filled immediately, and written. + ?line [Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]), + ?line true = dets:member(T, Key), + + %% If delay, this happens without file access. + %% (This is no longer true; cache lookup has been simplified.) + ?line ok = dets:delete(T,Key), + ?line ok = dets:insert(T,Tuple), + ?line ok = dets:insert(T,Tuple), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:sync(T), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + + %% Key's objects are is on file only, + %% key toto in the cache (if there is one). + ?line ok = dets:delete(T,toto), + ?line false = dets:member(T, toto), + ?line ok = dets:insert(T,[{toto,b},{toto,b}]), + ?line true = sort([Tuple,{toto,b}]) =:= + sort(dets:lookup_keys(T, [Key,toto])), + ?line true = dets:member(T, toto), + + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + ?line Size = dets:info(T, size), + + %% No object with the key on the file. + %% Delete, add one object. + Size1 = Size + 2, + del_and_ins(key, T, Size1, Tuple, Key, 1), + del_and_ins(object, T, Size1, Tuple, Key, 1), + del_and_ins(both, T, Size1, Tuple, Key, 1), + + %% One object with the key on the file. + %% Delete it, add one object. + Size2 = Size + 2, + del_and_ins(key, T, Size2, Tuple, Key, 1), + del_and_ins(object, T, Size2, Tuple, Key, 1), + del_and_ins(both, T, Size2, Tuple, Key, 1), + + %% Overwrite an objekt on file with the same object. + ?line ok = dets:insert(T, Tuple), + ?line ok = dets:sync(T), + ?line [Tuple2] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:insert(T, Tuple), + ?line ok = dets:sync(T), + ?line [Tuple2] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + + %% A mix of insert and delete. + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + ?line ok = dets:delete(T, Key), + ?line ok = dets:insert(T, {Key,foo}), + ?line ok = dets:insert(T, {Key,bar}), + ?line [{Key,bar},{Key,foo}] = sort(dets:lookup(T, Key)), + ?line true = dets:member(T, Key), + ?line ok = dets:delete_object(T, {Key,foo}), + ?line ok = dets:insert(T, {Key,kar}), + ?line [{Key,bar},{Key,kar}] = sort(dets:lookup(T, Key)), + ?line true = dets:member(T, Key), + ?line ok = dets:insert(T, [{Key,kar},{Key,kar}]), + ?line [{Key,bar},{Key,kar}] = sort(dets:lookup(T, Key)), + ?line true = dets:member(T, Key), + ?line ok = dets:delete_object(T, {Key,bar}), + ?line ok = dets:delete_object(T, {Key,kar}), + ?line [] = dets:lookup(T, Key), + ?line false = dets:member(T, Key), + ?line ok = dets:sync(T), + ?line [] = dets:lookup(T, Key), + ?line false = dets:member(T, Key), + + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + if + Extra -> + %% Let the table grow for a while, if it needs to. + ?line All1 = get_all_objects(T), + ?line dets:safe_fixtable(T, false), + ?line timer:sleep(1200), + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + ?line dets:safe_fixtable(T, true), + ?line All2 = get_all_objects(T), + ?line FAll2 = get_all_objects_fast(T), + ?line true = sort(All2) =:= sort(FAll2), + case symdiff(All1, All2) of + {[],[]} -> ok; + {X,Y} -> + NoBad = length(X) + length(Y), + test_server:fail({bags,DelayedWrite,Extra,Sz,NoBad}) + end; + true -> + ok + end, + ?line ok = dets:close(T), + file:delete(Fname), + + %% Second object of a key added and looked up simultaneously. + R1 = {index_test,1,2,3,4}, + R2 = {index_test,2,2,13,14}, + R3 = {index_test,1,12,13,14}, + ?line {ok, _} = dets:open_file(T,[{version,Version},{type,bag}, + {keypos,2},{file,Fname}]), + ?line ok = dets:insert(T,R1), + ?line ok = dets:sync(T), + ?line ok = dets:insert(T,R2), + ?line ok = dets:sync(T), + ?line ok = dets:insert(T,R3), + ?line [R1,R3] = sort(dets:lookup(T,1)), + ?line true = dets:member(T, 1), + ?line [R1,R3] = sort(dets:lookup(T,1)), + ?line true = dets:member(T, 1), + ?line ok = dets:close(T), + file:delete(Fname), + + ?line check_pps(P0), + ok. + +cache_duplicate_bags_v8(doc) -> + ["Test the write cache for duplicate bags."]; +cache_duplicate_bags_v8(suite) -> + []; +cache_duplicate_bags_v8(Config) when is_list(Config) -> + cache_duplicate_bags(Config, 8). + +cache_duplicate_bags_v9(doc) -> + ["Test the write cache for duplicate bags."]; +cache_duplicate_bags_v9(suite) -> + []; +cache_duplicate_bags_v9(Config) when is_list(Config) -> + cache_duplicate_bags(Config, 9). + +cache_duplicate_bags(Config, Version) -> + Small = 2, + cache_dup_bags(Config, {0,0}, false, Small, Version), + cache_dup_bags(Config, {0,0}, true, Small, Version), + cache_dup_bags(Config, {5000,5000}, false, Small, Version), + cache_dup_bags(Config, {5000,5000}, true, Small, Version), + %% Objects of size greater than 2 kB. + Big = 1200, + cache_dup_bags(Config, {0,0}, false, Big, Version), + cache_dup_bags(Config, {0,0}, true, Big, Version), + cache_dup_bags(Config, {5000,5000}, false, Big, Version), + cache_dup_bags(Config, {5000,5000}, true, Big, Version). + +cache_dup_bags(Config, DelayedWrite, Extra, Sz, Version) -> + %% Extra = bool(). Insert tuples until the tested key is not alone. + %% Sz = integer(). Size of the inserted tuples. + + T = cache, + ?line Fname = filename(cache, Config), + ?line file:delete(Fname), + P0 = pps(), + + ?line {ok, _} = + dets:open_file(T,[{version, Version}, {file,Fname}, + {type,duplicate_bag}, + {delayed_write, DelayedWrite}]), + + Dups = 2, + {Key, OtherKeys} = + if + Extra -> + %% Insert enough to get three keys in some slot. + ?line dets:safe_fixtable(T, true), + insert_objs(T, 1, Sz, Dups); + true -> + {1,[]} + end, + Tuple = erlang:make_tuple(Sz, Key), + ?line ok = dets:delete(T, Key), + ?line ok = dets:sync(T), + ?line false = dets:member(T, Key), + + %% The values of keys in the same slot as Key are checked. + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + + ?line ok = dets:insert(T, Tuple), + ?line [Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:insert(T, [Tuple,Tuple]), + %% If no delay, the cache gets filled immediately, and written. + ?line [Tuple,Tuple,Tuple] = dets:lookup_keys(T, [Key,a,b,c,d,e,f]), + ?line true = dets:member(T, Key), + + %% If delay, this happens without file access. + %% (This is no longer true; cache lookup has been simplified.) + ?line ok = dets:delete(T,Key), + ?line ok = dets:insert(T,Tuple), + ?line ok = dets:insert(T,Tuple), + ?line [Tuple,Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + ?line ok = dets:sync(T), + ?line [Tuple,Tuple] = dets:lookup(T, Key), + ?line true = dets:member(T, Key), + + %% One object in the cache, one on the file. + ?line ok = dets:delete(T,Key), + ?line ok = dets:insert(T,Tuple), + ?line ok = dets:sync(T), + ?line ok = dets:insert(T,Tuple), + ?line true = dets:member(T, Key), % should not read the file, but it does.. + + %% Key's objects are is on file only, + %% key toto in the cache (if there is one). + ?line ok = dets:delete(T,toto), + ?line ok = dets:insert(T,[{toto,b},{toto,b}]), + ?line true = sort([Tuple,Tuple,{toto,b},{toto,b}]) =:= + sort(dets:lookup_keys(T, [Key,toto])), + ?line true = dets:member(T, toto), + ?line Size = dets:info(T, size), + + %% Two objects with the same key on the file. + %% Delete them, add two objects. + del_and_ins(key, T, Size, Tuple, Key, 2), + + del_and_ins(object, T, Size, Tuple, Key, 2), + del_and_ins(both, T, Size, Tuple, Key, 2), + + %% Two objects with the same key on the file. + %% Delete them, add three objects. + del_and_ins(key, T, Size, Tuple, Key, 3), + del_and_ins(object, T, Size, Tuple, Key, 3), + del_and_ins(both, T, Size, Tuple, Key, 3), + + %% Two objects with the same key on the file. + %% Delete them, add one object. + del_and_ins(key, T, Size, Tuple, Key, 1), + del_and_ins(object, T, Size, Tuple, Key, 1), + del_and_ins(both, T, Size, Tuple, Key, 1), + + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + if + Extra -> + %% Let the table grow for a while, if it needs to. + ?line All1 = get_all_objects(T), + ?line dets:safe_fixtable(T, false), + ?line timer:sleep(1200), + ?line OtherValues = sort(lookup_keys(T, OtherKeys)), + ?line dets:safe_fixtable(T, true), + ?line All2 = get_all_objects(T), + ?line FAll2 = get_all_objects_fast(T), + ?line true = sort(All2) =:= sort(FAll2), + case symdiff(All1, All2) of + {[],[]} -> ok; + {X,Y} -> + NoBad = length(X) + length(Y), + test_server:fail({dup_bags,DelayedWrite,Extra,Sz,NoBad}) + end; + true -> + ok + end, + ?line ok = dets:close(T), + + file:delete(Fname), + ?line check_pps(P0), + ok. + +lookup_keys(_T, []) -> + []; +lookup_keys(T, Keys) -> + dets:lookup_keys(T, Keys). + +del_and_ins(W, T, Size, Obj, Key, N) -> + case W of + object -> + ?line ok = dets:delete_object(T, Obj); + key -> + + ?line ok = dets:delete(T, Key); + both -> + ?line ok = dets:delete(T, Key), + ?line ok = dets:delete_object(T, Obj) + end, + Objs = duplicate(N, Obj), + ?line [] = dets:lookup(T, Key), + ?line ok = dets:insert(T, Objs), + ?line Objs = dets:lookup_keys(T, [snurrespratt,Key]), + ?line true = Size + length(Objs)-2 =:= dets:info(T, size), + ?line Objs = dets:lookup(T, Key). + + +insert_objs(T, N, Sz, Dups) -> + Seq = seq(N,N+255), + L0 = map(fun(I) -> erlang:make_tuple(Sz, I) end, Seq), + L = append(duplicate(Dups, L0)), + ?line ok = dets:insert(T, L), + ?line case search_slot(T, 0) of + false -> + insert_objs(T, N+256, Sz, Dups); + Keys -> + Keys + end. + +search_slot(T, I) -> + ?line case dets:slot(T, I) of + '$end_of_table' -> + false; + Objs -> + case usort(map(fun(X) -> element(1, X) end, Objs)) of + [_, Key, _ | _] = Keys0 -> + Keys = delete(Key, Keys0), + {Key, Keys}; + _ -> + search_slot(T, I+1) + end + end. + +symdiff(L1, L2) -> + {X, _, Y} = + sofs:symmetric_partition(sofs:set(L1), sofs:set(L2)), + {sofs:to_external(X), sofs:to_external(Y)}. + +otp_4208(doc) -> + ["Read only table and traversal caused crash."]; +otp_4208(suite) -> + []; +otp_4208(Config) when is_list(Config) -> + Tab = otp_4208, + ?line FName = filename(Tab, Config), + Expected = sort([{3,ghi,12},{1,abc,10},{4,jkl,13},{2,def,11}]), + + file:delete(FName), + ?line {ok, Tab} = dets:open_file(Tab, [{file,FName}]), + ?line ok = dets:insert(Tab, [{1,abc,10},{2,def,11},{3,ghi,12},{4,jkl,13}]), + ?line Expected = sort(dets:traverse(Tab, fun(X) -> {continue, X} end)), + ?line ok = dets:close(Tab), + + ?line {ok, Tab} = dets:open_file(Tab, [{access, read},{file,FName}]), + ?line Expected = sort(dets:traverse(Tab, fun(X) -> {continue, X} end)), + ?line ok = dets:close(Tab), + file:delete(FName), + + ok. + +otp_4989(doc) -> + ["Read only table and growth."]; +otp_4989(suite) -> + []; +otp_4989(Config) when is_list(Config) -> + Tab = otp_4989, + ?line FName = filename(Tab, Config), + + %% Do exactly as in the error report. + ?line _Ets = ets:new(Tab, [named_table]), + ets_init(Tab, 100000), + ?line {ok, Tab} = + dets:open_file(Tab, [{access, read_write}, {file,FName}, {keypos,2}]), + ?line ok = dets:from_ets(Tab, Tab), + ?line ok = dets:close(Tab), + %% Restore. + ?line {ok, Tab} = + dets:open_file(Tab, [{access, read}, {keypos, 2}, {file, FName}]), + ?line true = ets:delete_all_objects(Tab), + ?line true = ets:from_dets(Tab, Tab), + ?line ok = dets:close(Tab), + ets:delete(Tab), + file:delete(FName), + ok. + +ets_init(_Tab, 0) -> + ok; +ets_init(Tab, N) -> + ets:insert(Tab, {N,N}), + ets_init(Tab, N - 1). + +many_clients(doc) -> + ["Several clients accessing a table simultaneously."]; +many_clients(suite) -> + []; +many_clients(Config) when is_list(Config) -> + Tab = many_clients, + ?line FName = filename(Tab, Config), + + Server = self(), + + ?line file:delete(FName), + P0 = pps(), + ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]), + ?line [P1,P2,P3,P4] = new_clients(4, Tab), + + %% dets:init_table/2 is used for making sure that all processes + %% start sending requests before the Dets process begins to handle + %% them; the requests arrive "in parallel". + + %% Four processes accessing the same table at almost the same time. + + %% One key is read, updated, and read again. + Seq1 = [{P1,[{lookup,1,[{1,a}]}]}, {P2,[{insert,{1,b}}]}, + {P3,[{lookup,1,[{1,b}]}]}, {P4,[{lookup,1,[{1,b}]}]}], + ?line atomic_requests(Server, Tab, [[{1,a}]], Seq1), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Different keys read by different processes + Seq2 = [{P1,[{member,1,true}]}, {P2,[{lookup,2,[{2,b}]}]}, + {P3,[{lookup,1,[{1,a}]}]}, {P4,[{lookup,3,[{3,c}]}]}], + ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq2), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Reading deleted key. + Seq3 = [{P1,[{delete_key,2}]}, {P2,[{lookup,1,[{1,a}]}]}, + {P3,[{lookup,1,[{1,a}]}]}, {P4,[{member,2,false}]}], + ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq3), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Inserting objects. + Seq4 = [{P1,[{insert,[{1,a},{2,b}]}]}, {P2,[{insert,[{2,c},{3,a}]}]}, + {P3,[{insert,[{3,b},{4,d}]}]}, + {P4,[{lookup_keys,[1,2,3,4],[{1,a},{2,c},{3,b},{4,d}]}]}], + ?line atomic_requests(Server, Tab, [], Seq4), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Deleting objects. + Seq5 = [{P1,[{delete_object,{1,a}}]}, {P2,[{delete_object,{1,a}}]}, + {P3,[{delete_object,{3,c}}]}, + {P4,[{lookup_keys,[1,2,3,4],[{2,b}]}]}], + ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq5), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Some request not streamed. + Seq6 = [{P1,[{lookup,1,[{1,a}]}]}, {P2,[{info,size,3}]}, + {P3,[{lookup,1,[{1,a}]}]}, {P4,[{info,size,3}]}], + ?line atomic_requests(Server, Tab, [[{1,a},{2,b},{3,c}]], Seq6), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + %% Some request not streamed. + Seq7 = [{P1,[{insert,[{3,a}]}]}, {P2,[{insert,[{3,b}]}]}, + {P3,[{delete_object,{3,c}}]}, + {P4,[{lookup,3,[{3,b}]}]}], + ?line atomic_requests(Server, Tab, [[{3,c}]], Seq7), + ?line true = get_replies([{P1,ok}, {P2,ok}, {P3,ok}, {P4,ok}]), + + ?line put_requests(Server, [{P1,stop},{P2,stop},{P3,stop},{P4,stop}]), + ?line ok = dets:close(Tab), + ?line file:delete(FName), + + %% Check that errors are handled correctly by the streaming operators. + ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]), + ?line ok = ins(Tab, 100), + Obj = {66,{item,number,66}}, + ?line {ok, ObjPos} = dets:where(Tab, Obj), + ?line ok = dets:close(Tab), + %% Damaged object. + crash(FName, ObjPos+12), + ?line {ok, _} = dets:open_file(Tab,[{file, FName},{version,9}]), + ?line BadObject1 = dets:lookup_keys(Tab, [65,66,67,68,69]), + ?line bad_object(BadObject1, FName), + ?line _Error = dets:close(Tab), + ?line file:delete(FName), + + ?line check_pps(P0), + + ok. + +%% Tab is initiated with the objects in Objs. Objs = [[object()]]. +atomic_requests(Server, Tab, Objs, Req) -> + ok = dets:init_table(Tab, atomic_requests(Server, Objs, Req)). + +atomic_requests(Server, L, Req) -> + fun(close) -> + ok; + (read) when [] =:= L -> + put_requests(Server, Req), + end_of_input; + (read) -> + [E | Es] = L, + {E, atomic_requests(Server, Es, Req)} + end. + +put_requests(Server, L) -> + lists:foreach(fun({Pid,R}) -> Pid ! {Server,R}, timer:sleep(1) end, L). + +get_replies(L) -> + lists:all(fun({Pid,Reply}) -> Reply =:= get_reply(Pid) end, L). + +get_reply(Pid) -> + ?line receive {Pid, Reply} -> Reply end. + +new_clients(0, _Tab) -> + []; +new_clients(N, Tab) -> + [new_client(Tab) | new_clients(N-1, Tab)]. + +new_client(Tab) -> + spawn(?MODULE, client, [self(), Tab]). + +client(S, Tab) -> + receive + {S, stop} -> + exit(normal); + {S, ToDo} -> + ?line Reply = eval(ToDo, Tab), + case Reply of + {error, _} -> io:format("~p: ~p~n", [self(), Reply]); + _ -> ok + end, + S ! {self(), Reply} + end, + client(S, Tab). + +eval([], _Tab) -> + ok; +eval([sync | L], Tab) -> + ?line case dets:sync(Tab) of + ok -> eval(L, Tab); + Error -> {error, {sync,Error}} + end; +eval([{insert,Stuff} | L], Tab) -> + ?line case dets:insert(Tab, Stuff) of + ok -> eval(L, Tab); + Error -> {error, {insert,Stuff,Error}} + end; +eval([{lookup,Key,Expected} | L], Tab) -> + ?line case dets:lookup(Tab, Key) of + Expected -> eval(L, Tab); + Else -> {error, {lookup,Key,Expected,Else}} + end; +eval([{lookup_keys,Keys,Expected} | L], Tab) -> + %% Time order is destroyed... + ?line case dets:lookup_keys(Tab, Keys) of + R when is_list(R) -> + case lists:sort(Expected) =:= lists:sort(R) of + true -> eval(L, Tab); + false -> {error, {lookup_keys,Keys,Expected,R}} + end; + Else -> {error, {lookup_keys,Keys,Expected,Else}} + end; +eval([{member,Key,Expected} | L], Tab) -> + ?line case dets:member(Tab, Key) of + Expected -> eval(L, Tab); + Else -> {error, {member,Key,Expected,Else}} + end; +eval([{delete_key,Key} | L], Tab) -> + ?line case dets:delete(Tab, Key) of + ok -> eval(L, Tab); + Else -> {error, {delete_key,Key,Else}} + end; +eval([{delete_object,Object} | L], Tab) -> + ?line case dets:delete_object(Tab, Object) of + ok -> eval(L, Tab); + Else -> {error, {delete_object,Object,Else}} + end; +eval([{info,Tag,Expected} | L], Tab) -> + ?line case dets:info(Tab, Tag) of + Expected -> eval(L, Tab); + Else -> {error, {info,Tag,Else,Expected}} + end; +eval(Else, _Tab) -> + {error, {bad_request,Else}}. + +otp_4906(doc) -> + ["More than 128k keys caused crash."]; +otp_4906(suite) -> + []; +otp_4906(Config) when is_list(Config) -> + N = 256*512 + 400, + Tab = otp_4906, + ?line FName = filename(Tab, Config), + + file:delete(FName), + ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}]), + ?line ok = ins_small(Tab, 0, N), + ?line ok = dets:close(Tab), + ?line {ok, Tab} = dets:open_file(Tab, [{file, FName}]), + ?line ok = read_4906(Tab, N-1), + ?line ok = dets:close(Tab), + file:delete(FName), + + %% If the (only) process fixing a table updates the table, the + %% process will no longer be punished with a 1 ms delay (hm, the + %% server is delayed, it should be the client...). In this example + %% the writing process *is* delayed. + ?line {ok,Tab} = dets:open_file(Tab, [{file,FName}]), + Parent = self(), + FixPid = spawn_link(fun() -> + dets:safe_fixtable(Tab, true), + receive {Parent, stop} -> ok end + end), + ?line ok = ins_small(Tab, 0, 1000), + FixPid ! {Parent, stop}, + timer:sleep(1), + ?line ok = dets:close(Tab), + file:delete(FName), + ok. + +read_4906(_T, N) when N < 0 -> + ok; +read_4906(T, N) -> + ?line [_] = dets:lookup(T, N), + read_4906(T, N-1). + +ins_small(_T, I, N) when I =:= N -> + ok; +ins_small(T, I, N) -> + ?line ok = dets:insert(T, {I}), + ins_small(T, I+1, N). + +otp_5402(doc) -> + ["Unwritable ramfile caused krasch."]; +otp_5402(suite) -> + []; +otp_5402(Config) when is_list(Config) -> + Tab = otp_5402, + ?line File = filename:join([cannot, write, this, file]), + + %% close + ?line{ok, T} = dets:open_file(Tab, [{ram_file,true}, + {file, File}]), + ?line ok = dets:insert(T, {1,a}), + ?line {error,{file_error,_,_}} = dets:close(T), + + %% sync + ?line {ok, T} = dets:open_file(Tab, [{ram_file,true}, + {file, File}]), + ?line ok = dets:insert(T, {1,a}), + ?line {error,{file_error,_,_}} = dets:sync(T), + ?line {error,{file_error,_,_}} = dets:close(T), + + %% auto_save + ?line {ok, T} = dets:open_file(Tab, [{ram_file,true}, + {auto_save, 2000}, + {file, File}]), + ?line ok = dets:insert(T, {1,a}), + ?line timer:sleep(5000), + ?line {error,{file_error,_,_}} = dets:close(T), + ok. + +simultaneous_open(doc) -> + ["Several clients open and close tables simultaneously."]; +simultaneous_open(suite) -> + []; +simultaneous_open(Config) -> + Tab = sim_open, + File = filename(Tab, Config), + + ?line ok = monit(Tab, File), + ?line ok = kill_while_repairing(Tab, File), + ?line ok = kill_while_init(Tab, File), + ?line ok = open_ro(Tab, File), + ?line ok = open_w(Tab, File, 0, Config), + ?line ok = open_w(Tab, File, 100, Config), + ok. + +%% One process logs and another process closes the log. Before +%% monitors were used, this would make the client never return. +monit(Tab, File) -> + file:delete(File), + {ok, Tab} = dets:open_file(Tab, [{file,File}]), + F1 = fun() -> dets:close(Tab) end, + F2 = fun() -> {'EXIT', {badarg, _}} = do_log(Tab) end, + spawn(F2), + timer:sleep(100), + spawn(F1), + dets:close(Tab), + file:delete(File), + ok. + +do_log(Tab) -> + case catch dets:insert(Tab, {hej,san,sa}) of + ok -> do_log(Tab); + Else -> Else + end. + +%% Kill the Dets process while repair is in progress. +kill_while_repairing(Tab, File) -> + ?line create_opened_log(File), + Delay = 1000, + dets:start(), + Parent = self(), + Ps = processes(), + F = fun() -> + R = (catch dets:open_file(Tab, [{file,File}])), + timer:sleep(Delay), + Parent ! {self(), R} + end, + ?line P1 = spawn(F), % will repair + timer:sleep(100), + ?line P2 = spawn(F), % pending... + ?line P3 = spawn(F), % pending... + ?line DetsPid = find_dets_pid([P1, P2, P3 | Ps]), + exit(DetsPid, kill), + + ?line receive {P1,R1} -> {'EXIT', {dets_process_died, _}} = R1 end, + ?line receive {P2,R2} -> {ok, _} = R2 end, + ?line receive {P3,R3} -> {ok, _} = R3 end, + + timer:sleep(200), + case dets:info(Tab) of + undefined -> + ok; + _Info -> + timer:sleep(5000), + ?line undefined = dets:info(Tab) + end, + + file:delete(File), + ok. + +find_dets_pid(P0) -> + case lists:sort(processes() -- P0) of + [P, _] -> P; + _ -> timer:sleep(100), find_dets_pid(P0) + end. + +%% Kill the Dets process when there are users and an on-going +%% initiailization. +kill_while_init(Tab, File) -> + file:delete(File), + Parent = self(), + F = fun() -> + R = dets:open_file(Tab, [{file,File}]), + Parent ! {self(), R}, + receive {Parent, die} -> ok end, + {error, not_owner} = dets:close(Tab) + end, + ?line P1 = spawn(F), + ?line P2 = spawn(F), + ?line P3 = spawn(F), + IF = fun() -> + R = dets:open_file(Tab, [{file,File}]), + Parent ! {self(), R}, + Fun = fun(_) -> timer:sleep(100000) end, + {'EXIT', {badarg, _}} = (catch dets:init_table(Tab, Fun)), + receive {Parent, die} -> ok end + end, + ?line P4 = spawn(IF), + ?line receive {P1,R1} -> {ok, _} = R1 end, + ?line receive {P2,R2} -> {ok, _} = R2 end, + ?line receive {P3,R3} -> {ok, _} = R3 end, + ?line receive {P4,R4} -> {ok, _} = R4 end, + ?line [DetsPid] = + lists:filter(fun(P) -> dets:pid2name(P) =/= undefined end, + erlang:processes()), + exit(DetsPid, kill), + + timer:sleep(1000), + ?line undefined = dets:info(Tab), + ?line P1 ! {Parent, die}, + ?line P2 ! {Parent, die}, + ?line P3 ! {Parent, die}, + ?line P4 ! {Parent, die}, + + file:delete(File), + timer:sleep(100), + ok. + +open_ro(Tab, File) -> + ?line create_opened_log(File), + Delay = 1000, + Parent = self(), + F = fun() -> + R = dets:open_file(Tab, [{file,File},{access,read}]), + timer:sleep(Delay), + Parent ! {self(), R} + end, + ?line P1 = spawn(F), + ?line P2 = spawn(F), + ?line P3 = spawn(F), + + ?line receive {P1,R1} -> {error,{not_closed,_}} = R1 end, + ?line receive {P2,R2} -> {error,{not_closed,_}} = R2 end, + ?line receive {P3,R3} -> {error,{not_closed,_}} = R3 end, + ok. + +open_w(Tab, File, Delay, Config) -> + ?line create_opened_log(File), + Parent = self(), + F = fun() -> + R = dets:open_file(Tab, [{file,File}]), + timer:sleep(Delay), + Parent ! {self(), R} + end, + ?line Pid1 = spawn(F), + ?line Pid2 = spawn(F), + ?line Pid3 = spawn(F), + ?line undefined = dets:info(Tab), % is repairing now + ?line 0 = qlen(), + + Tab2 = t2, + File2 = filename(Tab2, Config), + ?line file:delete(File2), + ?line {ok,Tab2} = dets:open_file(Tab2, [{file,File2}]), + ?line ok = dets:close(Tab2), + ?line file:delete(File2), + ?line 0 = qlen(), % still repairing + + ?line receive {Pid1,R1} -> {ok, Tab} = R1 end, + ?line receive {Pid2,R2} -> {ok, Tab} = R2 end, + ?line receive {Pid3,R3} -> {ok, Tab} = R3 end, + timer:sleep(200), + case dets:info(Tab) of + undefined -> + ok; + _Info -> + timer:sleep(5000), + ?line undefined = dets:info(Tab) + end, + + file:delete(File), + ok. + +qlen() -> + {_, {_, N}} = lists:keysearch(message_queue_len, 1, process_info(self())), + N. + +create_opened_log(File) -> + Tab = t, + file:delete(File), + ?line {ok, Tab} = dets:open_file(Tab, [{file,File}]), + ?line ok = ins(Tab, 60000), + ?line ok = dets:close(Tab), + ?line crash(File, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED), + ok. + +insert_new(doc) -> + ["OTP-5075. insert_new/2"]; +insert_new(suite) -> + []; +insert_new(Config) -> + Tab = insert_new, + File = filename(Tab, Config), + file:delete(File), + ?line {ok, T} = dets:open_file(Tab, [{file,File}]), + ?line {'EXIT', {badarg, _}} = (catch dets:insert_new(Tab, 14)), + ?line {'EXIT', {badarg, _}} = (catch dets:insert_new(Tab, {})), + ?line true = dets:insert_new(Tab, {1,a}), + ?line false = dets:insert_new(Tab, {1,a}), + ?line true = dets:insert_new(Tab, [{2,b}, {3,c}]), + ?line false = dets:insert_new(Tab, [{2,b}, {3,c}]), + ?line false = dets:insert_new(Tab, [{1,a}, {4,d}]), + ?line ok = dets:close(Tab), + + file:delete(File), + ?line {ok, T} = dets:open_file(Tab, [{file,File},{type,bag}]), + ?line true = dets:insert_new(Tab, {1,a}), + ?line false = dets:insert_new(Tab, {1,b}), + ?line true = dets:insert_new(Tab, [{2,b}, {3,c}]), + ?line false = dets:insert_new(Tab, [{2,a}, {3,d}]), + ?line false = dets:insert_new(Tab, [{1,a}, {4,d}]), + ?line ok = dets:close(Tab), + + + file:delete(File), + ok. + +repair_continuation(doc) -> + ["OTP-5126. repair_continuation/2"]; +repair_continuation(suite) -> + []; +repair_continuation(Config) -> + Tab = repair_continuation_table, + ?line Fname = filename(repair_cont, Config), + ?line file:delete(Fname), + ?line {ok, _} = dets:open_file(Tab, [{file,Fname}]), + ?line ok = dets:insert(Tab, [{1,a},{2,b},{3,c}]), + + ?line MS = [{'_',[],[true]}], + + ?line {[true], C1} = dets:select(Tab, MS, 1), + ?line C2 = binary_to_term(term_to_binary(C1)), + ?line {'EXIT', {badarg, _}} = (catch dets:select(C2)), + ?line C3 = dets:repair_continuation(C2, MS), + ?line {[true], C4} = dets:select(C3), + ?line C5 = dets:repair_continuation(C4, MS), + ?line {[true], _} = dets:select(C5), + ?line {'EXIT', {badarg, _}} = (catch dets:repair_continuation(Tab, bu)), + + ?line ok = dets:close(Tab), + ?line file:delete(Fname), + ok. + +otp_5487(doc) -> + ["OTP-5487. Growth of read-only table (again)."]; +otp_5487(suite) -> + []; +otp_5487(Config) -> + otp_5487(Config, 9), + otp_5487(Config, 8), + ok. + +otp_5487(Config, Version) -> + Tab = otp_5487, + ?line Fname = filename(otp_5487, Config), + ?line file:delete(Fname), + ?line Ets = ets:new(otp_5487, [public, set]), + ?line lists:foreach(fun(I) -> ets:insert(Ets, {I,I+1}) end, + lists:seq(0,1000)), + ?line {ok, _} = dets:open_file(Tab, [{file,Fname},{version,Version}]), + ?line ok = dets:from_ets(Tab, Ets), + ?line ok = dets:sync(Tab), + ?line ok = dets:close(Tab), + ?line {ok, _} = dets:open_file(Tab, [{file,Fname},{access,read}]), + ?line [{1,2}] = dets:lookup(Tab, 1), + ?line ok = dets:close(Tab), + ?line ets:delete(Ets), + ?line file:delete(Fname). + +otp_6206(doc) -> + ["OTP-6206. Badly formed free lists."]; +otp_6206(suite) -> + []; +otp_6206(Config) -> + Tab = otp_6206, + File = filename(Tab, Config), + + file:delete(File), + Options = [{file,File}], + ?line {ok, Tab} = dets:open_file(Tab, Options), + NObjs = 13006, + ?line ok = ins(Tab, NObjs), + ?line ok = del(Tab, NObjs, 2), + ?line ok = dets:close(Tab), + + %% Used to return {badmatch,{error,{bad_freelists,File}}. + ?line {ok, Tab} = dets:open_file(Tab, [{repair,false}|Options]), + ?line ok = dets:close(Tab), + file:delete(File), + ok. + +otp_6359(doc) -> + ["OTP-6359. select and match never return the empty list."]; +otp_6359(suite) -> + []; +otp_6359(Config) -> + Tab = otp_6359, + File = filename(Tab, Config), + + file:delete(File), + ?line {ok, _} = dets:open_file(Tab, [{file, File}]), + %% Used to return {[], Cont}: + ?line '$end_of_table' = dets:match(Tab, '_', 100), + ?line ok = dets:close(Tab), + file:delete(File), + ok. + +otp_4738(doc) -> + ["OTP-4738. ==/2 and =:=/2."]; +otp_4738(suite) -> + []; +otp_4738(Config) -> + %% Version 8 has not been corrected. + %% (The constant -12857447 is for version 9 only.) + otp_4738_set(9, Config), + otp_4738_bag(9, Config), + otp_4738_dupbag(9, Config), + ok. + +otp_4738_dupbag(Version, Config) -> + Tab = otp_4738, + File = filename(Tab, Config), + file:delete(File), + I = -12857447, + F = float(I), + One = 1, + FOne = float(One), + Args = [{file,File},{type,duplicate_bag},{version,Version}], + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]), + ?line ok = dets:sync(Tab), + ?line [{F,One},{F,FOne}] = dets:lookup(Tab, F), + ?line [{I,One},{I,FOne}] = dets:lookup(Tab, I), + ?line ok = dets:insert(Tab, [{F,One},{F,FOne}]), + ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One},{F,FOne}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:insert(Tab, [{F,FOne},{F,One}]), + ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One}, + {F,FOne},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:delete_object(Tab, {I,FOne}), + ?line [{I,One},{F,One},{F,FOne},{F,One},{F,FOne},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:insert(Tab, {I,FOne}), + ?line [{I,One},{I,FOne},{F,One},{F,FOne},{F,One}, + {F,FOne},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:delete_object(Tab, {F,FOne}), + ?line [{I,One},{I,FOne},{F,One},{F,One},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:delete(Tab, F), + ?line [{I,One},{I,FOne}] = dets:match_object(Tab, '_'), + ?line ok = dets:close(Tab), + file:delete(File), + + Zero = 0, + FZero = float(Zero), + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]), + ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]), + ?line ok = dets:insert(Tab, [{I,Zero},{F,Zero},{I,FZero},{I,FZero}]), + ?line Objs0 = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + crash(File, ?CLOSED_PROPERLY_POS+3, ?NOT_PROPERLY_CLOSED), + io:format("Expect repair:~n"), + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line Objs1 = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + ?line Objs1 = Objs0, + file:delete(File), + ok. + +otp_4738_bag(Version, Config) -> + Tab = otp_4738, + File = filename(Tab, Config), + file:delete(File), + I = -12857447, + F = float(I), + One = 1, + FOne = float(One), + Args = [{file,File},{type,bag},{version,Version}], + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I,One},{F,One},{I,FOne},{F,FOne}]), + ?line ok = dets:sync(Tab), + ?line [{F,One},{F,FOne}] = dets:lookup(Tab, F), + ?line [{I,One},{I,FOne}] = dets:lookup(Tab, I), + ?line ok = dets:insert(Tab, [{F,One},{F,FOne}]), + ?line [{I,One},{I,FOne},{F,One},{F,FOne}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:insert(Tab, [{F,FOne},{F,One}]), + ?line [{I,One},{I,FOne},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:delete_object(Tab, {I,FOne}), + ?line [{I,One},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:insert(Tab, {I,FOne}), + ?line [{I,One},{I,FOne},{F,FOne},{F,One}] = + dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:delete(Tab, F), + ?line [{I,One},{I,FOne}] = dets:match_object(Tab, '_'), + ?line ok = dets:close(Tab), + file:delete(File). + +otp_4738_set(Version, Config) -> + Tab = otp_4738, + File = filename(Tab, Config), + file:delete(File), + Args = [{file,File},{type,set},{version,Version}], + + %% I and F share the same slot. + I = -12857447, + F = float(I), + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I},{F}]), + ?line ok = dets:sync(Tab), + ?line [{F}] = dets:lookup(Tab, F), + ?line [{I}] = dets:lookup(Tab, I), + ?line ok = dets:insert(Tab, [{F}]), + ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + file:delete(File), + + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I}]), + ?line ok = dets:sync(Tab), + ?line [] = dets:lookup(Tab, F), + ?line [{I}] = dets:lookup(Tab, I), + ?line ok = dets:insert(Tab, [{F}]), + ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + file:delete(File), + + ?line {ok, Tab} = dets:open_file(Tab, Args), + ok = dets:insert(Tab, [{I},{F}]), + %% {insert, ...} in the cache, try lookup: + ?line [{F}] = dets:lookup(Tab, F), + ?line [{I}] = dets:lookup(Tab, I), + %% Both were found, but that cannot be verified. + ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + file:delete(File), + + ?line {ok, Tab} = dets:open_file(Tab, Args), + ?line ok = dets:insert(Tab, [{I}]), + ?line ok = dets:sync(Tab), + ?line ok = dets:insert(Tab, [{F}]), + %% {insert, ...} in the cache, try lookup: + ?line [{F}] = dets:lookup(Tab, F), + ?line [{I}] = dets:lookup(Tab, I), + ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + file:delete(File), + + ?line {ok, Tab} = dets:open_file(Tab, Args), + %% Both operations in the cache: + ?line ok = dets:insert(Tab, [{I}]), + ?line ok = dets:insert(Tab, [{F}]), + ?line [{I},{F}] = dets_utils:mkeysort(1, dets:match_object(Tab, '_')), + ?line ok = dets:close(Tab), + file:delete(File), + ok. + +otp_7146(doc) -> + ["OTP-7146. Bugfix: missing test when re-hashing."]; +otp_7146(suite) -> + []; +otp_7146(Config) -> + Tab = otp_7146, + File = filename(Tab, Config), + file:delete(File), + + Max = 2048, + ?line {ok, Tab} = dets:open_file(Tab, [{max_no_slots,Max}, {file,File}]), + write_dets(Tab, Max), + ?line ok = dets:close(Tab), + + file:delete(File), + ok. + +write_dets(Tab, Max) -> + write_dets(Tab, 0, Max). + +write_dets(_Tab, N, Max) when N > Max -> + ok; +write_dets(Tab, N, Max) -> + ok = dets:insert(Tab,{ N, {entry,N}}), + write_dets(Tab, N+1, Max). + +otp_8070(doc) -> + ["OTP-8070. Duplicated objects with insert_new() and duplicate_bag."]; +otp_8070(suite) -> + []; +otp_8070(Config) when is_list(Config) -> + Tab = otp_8070, + File = filename(Tab, Config), + file:delete(File), + ?line {ok, _} = dets:open_file(Tab, [{file,File},{type, duplicate_bag}]), + ?line ok = dets:insert(Tab, [{3,0}]), + ?line false = dets:insert_new(Tab, [{3,1},{3,1}]), + ?line [{3,0}] = dets:lookup(Tab, 3), + ?line ok = dets:close(Tab), + file:delete(File), + ok. + +%% +%% Parts common to several test cases +%% + +crash(File, Where) -> + crash(File, Where, 10). + +crash(File, Where, What) when is_integer(What) -> + ?line {ok, Fd} = file:open(File, read_write), + ?line file:position(Fd, Where), + ?line ok = file:write(Fd, [What]), + ?line ok = file:close(Fd). + +args(Config) -> + {Sets, Bags, Dups} = + {[ + [], + [{type, set}, {estimated_no_objects, 300}, + {ram_file, true}], + [{type, set}, {estimated_no_objects, 300}], + [{type, set}, {estimated_no_objects, 300}], + [{auto_save,20}, {type, set}, + {estimated_no_objects, 300}] + ], + + [ + [{type, bag}, {estimated_no_objects, 300}, {ram_file, true}], + [{type, bag}], + [{type, bag}, {estimated_no_objects, 300}], + [{type, bag}, {estimated_no_objects, 300}], + [{type, bag}, + {auto_save,20}, {estimated_no_objects, 300}], + [{type, bag}, {estimated_no_objects, 300}, {ram_file, true}] + ], + + [ + [{type, duplicate_bag}, {estimated_no_objects, 300}, + {ram_file, true}], + [{type, duplicate_bag}], + [{type, duplicate_bag}, {estimated_no_objects, 300}], + [{type, duplicate_bag}, {estimated_no_objects, 300}], + [{type, duplicate_bag}, + {auto_save,20}, {estimated_no_objects, 300}], + [{type, duplicate_bag}, {estimated_no_objects, 300}, + {ram_file, true}] + ] + }, + zip_filename(Sets, Bags, Dups, Config). + +zip_filename(S, B, D, Conf) -> + zip_filename(S, B, D, [], [], [], 1, Conf). + +zip_filename([H|T], B, D, S1, B1, D1, I, Conf) -> + zip_filename(T, B, D, [[{file, new_filename(I, Conf)} | H] | S1], + B1, D1, I+1, Conf); +zip_filename([], [H|B], D, S1, B1, D1, I, Conf) -> + zip_filename([], B, D, S1, [[{file, new_filename(I, Conf)} | H] | B1], + D1, I+1, Conf); +zip_filename([], [], [H|T], S1, B1, D1, I, Conf) -> + zip_filename([], [], T, S1, B1, [[{file, new_filename(I, Conf)} | H] | D1], + I+1, Conf); +zip_filename([], [], [], S1, B1, D1, _, _Conf) -> + {reverse(S1), reverse(B1), reverse(D1)}. + +del_test(Tab) -> + ?format("Deltest on ~p~n", [Tab]), + ?line Objs = safe_get_all_objects(Tab), + ?line Keys = map(fun(X) -> element(1, X) end, Objs), + ?line foreach(fun(Key) -> dets:delete(Tab, Key) end, Keys), + ?line 0 = length(get_all_objects(Tab)), + ?line [] = get_all_objects_fast(Tab), + ?line 0 = dets:info(Tab, size). + +del_obj_test(Tab) -> + ?format("Delobjtest on ~p~n", [Tab]), + ?line Objs = safe_get_all_objects(Tab), + ?line LL = length(Objs), + ?line LL = dets:info(Tab, size), + ?line foreach(fun(Obj) -> dets:delete_object(Tab, Obj) end, Objs), + ?line 0 = length(get_all_objects(Tab)), + ?line [] = get_all_objects_fast(Tab), + ?line 0 = dets:info(Tab, size). + +match_del_test(Tab) -> + ?line ?format("Match delete test on ~p~n", [Tab]), + ?line ok = dets:match_delete(Tab, {'_','_','_'}), + ?line Sz = dets:info(Tab, size), + ?line true = Sz =:= length(dets:match_object(Tab, '_')), + ?line ok = dets:match_delete(Tab, '_'), + ?line 0 = dets:info(Tab, size), + ?line 0 = length(get_all_objects(Tab)), + ?line [] = get_all_objects_fast(Tab). + +trav_test(_Data, Len, Tab) -> + ?format("Travtest on ~p~n", [Tab]), + ?line _X0 = dets:traverse(Tab, fun(_X) -> continue end), + ?line XX = dets:traverse(Tab, fun(X) -> {continue, X} end), + ?line case Len =:= length(XX) of + false -> ?format("DIFF ~p~n", [XX -- _Data]); + true -> ok + end, + ?line 1 = length(dets:traverse(Tab, fun(X) -> {done, X} end)). + +match_test(Data, Tab) -> + ?line ?format("Match test on ~p~n", [Tab]), + ?line Data1 = sort(filter(fun(X) when tuple_size(X) =:= 3 -> true; + (_X) -> false + end, Data)), + ?line Data1 = sort(dets:match_object(Tab, {'$1', '$2', '$3'})), + + ?line Len = length(Data), + ?line Len = length(dets:match(Tab, '_')), + ?line Len2 = length(Data1), + ?line Len2 = length(dets:match(Tab, {'$1', '_', '_'})), + + ?line Data3 = + filter(fun(X) -> + K = element(1, X), + if + tuple_size(X) =:= 3, tuple_size(K) =:= 2 -> true; + true -> false + end + end, Data), + ?line Len3 = length(Data3), + ?line Len3 = length(dets:match(Tab, {{'$1', '$2'}, '_', '_'})), + ?line Len3 = length(dets:match_object(Tab, {{'$1', '$2'}, '_', '_'})), + + ?line R = make_ref(), + ?line dets:insert(Tab, {{R, R}, 33 ,44}), + ?line 1 = length(dets:match(Tab, {{R, R}, '_', '_'})), + ?line 1 = length(dets:match_object(Tab, {{R, R}, '_', '_'})). + +%% +%% Utilities +%% + +headsz(8) -> + ?HEADSZ_v8; +headsz(_) -> + ?HEADSZ_v9. + +unwritable(Fname) -> + ?line {ok, Info} = file:read_file_info(Fname), + Mode = Info#file_info.mode - 8#00200, + ?line file:write_file_info(Fname, Info#file_info{mode = Mode}). + +writable(Fname) -> + ?line {ok, Info} = file:read_file_info(Fname), + Mode = Info#file_info.mode bor 8#00200, + ?line file:write_file_info(Fname, Info#file_info{mode = Mode}). + +truncate(File, Where) -> + ?line {ok, Fd} = file:open(File, read_write), + ?line file:position(Fd, Where), + ?line ok = file:truncate(Fd), + ?line ok = file:close(Fd). + +new_filename(Name, _Config) when is_integer(Name) -> + filename:join(?privdir(_Config), + integer_to_list(Name) ++ ".DETS"). + +filename(Name, Config) when is_atom(Name) -> + filename(atom_to_list(Name), Config); +filename(Name, _Config) -> + filename:join(?privdir(_Config), Name). + +open_files(_Name, [], _Version) -> + []; +open_files(Name0, [Args | Tail], Version) -> + ?format("init ~p~n", [Args]), + ?line Name = list_to_atom(integer_to_list(Name0)), + ?line {ok, Name} = dets:open_file(Name, [{version,Version} | Args]), + [Name | open_files(Name0+1, Tail, Version)]. + +close_all(Tabs) -> foreach(fun(Tab) -> ok = dets:close(Tab) end, Tabs). + +delete_files(Args) -> + Fun = fun(F) -> + {value, {file, File}} = keysearch(file, 1, F), + file:delete(File), + File + end, + map(Fun, Args). + +%% Initialize all tables +initialize(Tabs, Data) -> + ?line foreach(fun(Tab) -> + Fun = fun(Obj) -> ok = dets:insert(Tab, Obj) end, + foreach(Fun, Data), + dets:sync(Tab) + end, Tabs). + +%% need more than 512 objects to really trig overflow +make_data(Kp) -> + make_data(Kp, set). + +make_data(Kp, Type) -> + dup(Type, make_data(Kp, Type, 520)). + +dup(duplicate_bag, [H1, H2 |T]) -> + [H1,H2, H1, H2 | dup(duplicate_bag, T)]; +dup(_, Other) -> + Other. + +make_data(_Kp, Type, 0) -> + odd_keys(Type); +make_data(1, set, I) -> + [{I, q,w} | make_data(1, set, I-1)]; +make_data(2, set, I) -> + [{hh, I, q,w} | make_data(2, set, I-1)]; +make_data(1, bag, I) -> + [{I, q,w} , {I, hah, 77} | make_data(1, bag, I-1)]; +make_data(2, bag, I) -> + [{hh, I, q,w} , {hh, I, lalal, 900} | make_data(2, bag, I-1)]; +make_data(1, duplicate_bag, I) -> + [{I, q,w} , {I, hah, 77} | make_data(1, duplicate_bag, I-1)]; +make_data(2, duplicate_bag, I) -> + [{hh, I, q,w} , {hh, I, lalal, 900} | make_data(2, duplicate_bag, I-1)]. + +odd_keys(_) -> + [{foo, 1 ,2}, + {{foo, foo}, 2,3}, + {"kakaka", {{{}}}, jj}, + {{"kallll", "kkk", []}, 66.7777}, + {make_ref(), 99, 66}, + {{1},2,3,4,5,6,7,duplicate(50, 8)}, + {self(), 7,8,88}, + {[self()], 8, 11}]. + + +ins(_T, 0) -> + ok; +ins(T, N) -> + case dets:insert(T, {N, item(N)}) of + ok -> ins(T, N-1); + Error -> Error + end. + +item(N) when N rem 2 =:= 0 -> + {item, number, N}; +item(N) -> + {item, number, N, a, much, bigger, one, i, think}. + +del(_T, N, _I) when N =< 0 -> + ok; +del(T, N, I) -> + ok = dets:delete(T, N), + del(T, N-I, I). + +ensure_node(0, _Node) -> + could_not_start_node; +ensure_node(N, Node) -> + case net_adm:ping(Node) of + pang -> + receive after 1000 -> + ok + end, + ensure_node(N-1,Node); + pong -> + ok + end. + +size_test(Len, Tabs) -> + ?line foreach(fun(Tab) -> + Len = dets:info(Tab, size) + end, Tabs). + +no_keys_test([T | Ts]) -> + no_keys_test(T), + no_keys_test(Ts); +no_keys_test([]) -> + ok; +no_keys_test(T) -> + case dets:info(T, version) of + 8 -> + ok; + 9 -> + Kp = dets:info(T, keypos), + ?line All = dets:match_object(T, '_'), + ?line L = lists:map(fun(X) -> element(Kp, X) end, All), + ?line NoKeys = length(lists:usort(L)), + ?line case {dets:info(T, no_keys), NoKeys} of + {N, N} -> + ok; + {N1, N2} -> + exit({no_keys_test, N1, N2}) + end + end. + +safe_get_all_objects(Tab) -> + dets:safe_fixtable(Tab, true), + Objects = get_all_objects(Tab), + dets:safe_fixtable(Tab, false), + Objects. + +%% Caution: unless the table has been fixed, strange results can be returned. +get_all_objects(Tab) -> get_all_objects(dets:first(Tab), Tab, []). + +%% Assuming no key matches {error, Reason}... +get_all_objects('$end_of_table', _Tab, L) -> L; +get_all_objects({error, Reason}, _Tab, _L) -> + exit({get_all_objects, get(line), {error, Reason}}); +get_all_objects(Key, Tab, L) -> + Objs = dets:lookup(Tab, Key), + ?line get_all_objects(dets:next(Tab, Key), Tab, Objs ++ L). + +count_objects_quite_fast(Tab) -> + ?line R1 = dets:match_object(Tab, '_', 1), + count_objs_1(R1, 0). + +count_objs_1('$end_of_table', N) -> + N; +count_objs_1({Ts,C}, N) when is_list(Ts) -> + count_objs_1(dets:match_object(C), length(Ts) + N). + +get_all_objects_fast(Tab) -> + dets:match_object(Tab, '_'). + +%% Relevant for version 8. +histogram(Tab) -> + OnePercent = case dets:info(Tab, no_slots) of + undefined -> undefined; + {_, NoSlots, _} -> NoSlots/100 + end, + histogram(Tab, OnePercent). + +histogram(Tab, OnePercent) -> + ?line E = ets:new(histo, []), + ?line dets:safe_fixtable(Tab, true), + ?line Hist = histo(Tab, E, 0, OnePercent, OnePercent), + ?line dets:safe_fixtable(Tab, false), + ?line case Hist of + ok -> + ?line H = ets:tab2list(E), + ?line true = ets:delete(E), + sort(H); + Error -> + ets:delete(E), + Error + end. + +histo(T, E, I, One, Count) when is_number(Count), I > Count -> + io:format("."), + histo(T, E, I, One, Count+One); +histo(T, E, I, One, Count) -> + ?line case dets:slot(T, I) of + '$end_of_table' when is_number(Count) -> + io:format("~n"), + ok; + '$end_of_table' -> + ok; + Objs when is_list(Objs) -> + L = length(Objs), + case catch ets:update_counter(E, L, 1) of + {'EXIT', _} -> + ets:insert(E, {L, 1}); + _ -> + ok + end, + histo(T, E, I+1, One, Count); + Error -> + Error + end. + +sum_histogram(H) -> + sum_histogram(H, 0). + +sum_histogram([{S,N1} | H], N) -> + sum_histogram(H, N + S*N1); +sum_histogram([], N) -> + N. + +ave_histogram(H) -> + ave_histogram(H, 0)/sum_histogram(H). + +ave_histogram([{S,N1} | H], N) -> + ave_histogram(H, N + S*S*N1); +ave_histogram([], N) -> + N. + +bad_object({error,{bad_object,FileName}}, FileName) -> + ok; % Version 8, no debug. +bad_object({error,{{bad_object,_,_},FileName}}, FileName) -> + ok; % Version 8, debug... +bad_object({error,{{bad_object,_}, FileName}}, FileName) -> + ok; % No debug. +bad_object({error,{{{bad_object,_,_},_,_,_}, FileName}}, FileName) -> + ok. % Debug. + +check_pps(P0) -> + case pps() of + P0 -> + ok; + _ -> + %% On some (rare) occasions the dets process is still + %% running although the call to close() has returned, as + %% it seems... + timer:sleep(500), + case pps() of + P0 -> + ok; + P1 -> + io:format("failure, got ~p~n, expected ~p\n", [P1, P0]), + {Ports0,Procs0} = P0, + {Ports1,Procs1} = P1, + show("Old ports", Ports0 -- Ports1), + show("New ports", Ports1 -- Ports0), + show("Old procs", Procs0 -- Procs1), + show("New procs", Procs1 -- Procs0), + ?t:fail() + end + end. + +show(_S, []) -> + ok; +show(S, L) -> + io:format("~s: ~p~n", [S, L]). + +pps() -> + dets:start(), + {port_list(), process_list()}. + +port_list() -> + [{P,safe_second_element(erlang:port_info(P, name))} || + P <- erlang:ports()]. + +process_list() -> + [{P,process_info(P, registered_name), + safe_second_element(process_info(P, initial_call))} || + P <- processes()]. + +safe_second_element({_,Info}) -> Info; +safe_second_element(Other) -> Other. -- cgit v1.2.3