%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2014. 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(unique_SUITE). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2]). -export([unique_monotonic_integer_white_box/1, unique_integer_white_box/1]). -include_lib("test_server/include/test_server.hrl"). %-define(P(V), V). -define(P(V), print_ret_val(?FILE, ?LINE, V)). -define(PRINT(V), print_ret_val(?FILE, ?LINE, V)). init_per_testcase(Case, Config) -> ?line Dog=test_server:timetrap(test_server:minutes(2)), [{watchdog, Dog}, {testcase, Case}|Config]. end_per_testcase(_, Config) -> Dog=?config(watchdog, Config), test_server:timetrap_cancel(Dog), ok. suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [unique_monotonic_integer_white_box, unique_integer_white_box]. groups() -> []. init_per_suite(Config) -> erts_debug:set_internal_state(available_internal_state, true), Config. end_per_suite(_Config) -> erts_debug:set_internal_state(available_internal_state, false), ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. %% %% %% Unique counter white box test case %% %% unique_monotonic_integer_white_box(Config) when is_list(Config) -> {ok, Node} = start_node(Config), TestServer = self(), Success = make_ref(), %% Run this in a separate node, so we don't mess up %% the system when moving the strict monotonic counter %% around in a non-strict monotonic way... Test = spawn(Node, fun () -> unique_monotonic_integer_white_box_test(TestServer, Success) end), Mon = erlang:monitor(process, Test), receive {'DOWN', Mon, process, Test, Error} -> ?t:fail(Error); Success -> ok end, erlang:demonitor(Mon, [flush]), stop_node(Node), ok. set_unique_monotonic_integer_state(MinCounter, NextValue) -> true = erts_debug:set_internal_state(unique_monotonic_integer_state, NextValue-MinCounter-1). unique_monotonic_integer_white_box_test(TestServer, Success) -> erts_debug:set_internal_state(available_internal_state, true), WordSize = erlang:system_info({wordsize, internal}), SmallBits = WordSize*8 - 4, MinSmall = -1*(1 bsl (SmallBits-1)), MaxSmall = (1 bsl (SmallBits-1))-1, %% Make sure we got small sizes correct... 0 = erts_debug:size(MinSmall), false = 0 =:= erts_debug:size(MinSmall-1), 0 = erts_debug:size(MaxSmall), false = 0 =:= erts_debug:size(MaxSmall+1), ?PRINT({min_small, MinSmall}), ?PRINT({max_small, MaxSmall}), MinSint64 = -1*(1 bsl 63), MaxSint64 = (1 bsl 63)-1, ?PRINT({min_Sint64, MinSint64}), ?PRINT({max_Sint64, MaxSint64}), MinCounter = erts_debug:get_internal_state(min_unique_monotonic_integer), MaxCounter = MinCounter + (1 bsl 64) - 1, ?PRINT({min_counter, MinCounter}), ?PRINT({max_counter, MaxCounter}), case WordSize of 4 -> MinCounter = MinSint64; 8 -> MinCounter = MinSmall end, StartState = erts_debug:get_internal_state(unique_monotonic_integer_state), %% Verify that we get expected results over all internal limits... case MinCounter < MinSmall of false -> 8 = WordSize, ok; true -> 4 = WordSize, ?PRINT(over_min_small), set_unique_monotonic_integer_state(MinCounter, MinSmall-2), true = (?P(erlang:unique_integer([monotonic])) == MinSmall - 2), true = (?P(erlang:unique_integer([monotonic])) == MinSmall - 1), true = (?P(erlang:unique_integer([monotonic])) == MinSmall), true = (?P(erlang:unique_integer([monotonic])) == MinSmall + 1), true = (?P(erlang:unique_integer([monotonic])) == MinSmall + 2), garbage_collect(), ok end, ?PRINT(over_zero), %% Not really an interesting limit, but... set_unique_monotonic_integer_state(MinCounter, -2), true = (?P(erlang:unique_integer([monotonic])) == -2), true = (?P(erlang:unique_integer([monotonic])) == -1), true = (?P(erlang:unique_integer([monotonic])) == 0), true = (?P(erlang:unique_integer([monotonic])) == 1), true = (?P(erlang:unique_integer([monotonic])) == 2), garbage_collect(), ?PRINT(over_max_small), set_unique_monotonic_integer_state(MinCounter, MaxSmall-2), true = (?P(erlang:unique_integer([monotonic])) == MaxSmall - 2), true = (?P(erlang:unique_integer([monotonic])) == MaxSmall - 1), true = (?P(erlang:unique_integer([monotonic])) == MaxSmall), true = (?P(erlang:unique_integer([monotonic])) == MaxSmall + 1), true = (?P(erlang:unique_integer([monotonic])) == MaxSmall + 2), garbage_collect(), case MaxCounter > MaxSint64 of false -> 4 = WordSize, ok; true -> 8 = WordSize, ?PRINT(over_max_sint64), set_unique_monotonic_integer_state(MinCounter, MaxSint64-2), true = (?P(erlang:unique_integer([monotonic])) == MaxSint64 - 2), true = (?P(erlang:unique_integer([monotonic])) == MaxSint64 - 1), true = (?P(erlang:unique_integer([monotonic])) == MaxSint64), true = (?P(erlang:unique_integer([monotonic])) == MaxSint64 + 1), true = (?P(erlang:unique_integer([monotonic])) == MaxSint64 + 2), garbage_collect() end, ?PRINT(over_max_min_counter), set_unique_monotonic_integer_state(MinCounter, if MaxCounter == MaxSint64 -> MaxCounter-2; true -> MinCounter-3 end), true = (?P(erlang:unique_integer([monotonic])) == MaxCounter - 2), true = (?P(erlang:unique_integer([monotonic])) == MaxCounter - 1), true = (?P(erlang:unique_integer([monotonic])) == MaxCounter), true = (?P(erlang:unique_integer([monotonic])) == MinCounter), true = (?P(erlang:unique_integer([monotonic])) == MinCounter + 1), true = (?P(erlang:unique_integer([monotonic])) == MinCounter + 2), garbage_collect(), %% Restore initial state and hope we didn't mess it up for the %% system... true = erts_debug:set_internal_state(unique_monotonic_integer_state, StartState), TestServer ! Success. %% %% %% Unique integer white box test case %% %% -record(uniqint_info, {min_int, max_int, max_small, schedulers, sched_bits}). unique_integer_white_box(Config) when is_list(Config) -> UinqintInfo = init_uniqint_info(), #uniqint_info{min_int = MinInt, max_int = MaxInt, max_small = MaxSmall} = UinqintInfo, io:format("****************************************************~n", []), io:format("*** Around MIN_UNIQ_INT ~p ***~n", [MinInt]), io:format("****************************************************~n", []), check_unique_integer_around(MinInt, UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around 0 ***~n", []), io:format("****************************************************~n", []), check_unique_integer_around(0, UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around MAX_SMALL ~p ***~n", [MaxSmall]), io:format("****************************************************~n", []), check_unique_integer_around(MaxSmall, UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around 2^64+MIN_UNIQ_INT ~p ***~n", [(1 bsl 64)+MinInt]), io:format("****************************************************~n", []), check_unique_integer_around((1 bsl 64)+MinInt, UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around 2^64 ~p~n", [(1 bsl 64)]), io:format("****************************************************~n", []), check_unique_integer_around((1 bsl 64), UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around 2^64-MIN_UNIQ_INT ~p ***~n", [(1 bsl 64)-MinInt]), io:format("****************************************************~n", []), check_unique_integer_around((1 bsl 64)-MinInt, UinqintInfo), io:format("****************************************************~n", []), io:format("*** Around MAX_UNIQ_INT ~p ***~n", [MaxInt]), io:format("****************************************************~n", []), check_unique_integer_around(MaxInt, UinqintInfo), ok. %%% Internal unique_integer_white_box/1 test case calc_sched_bits(NoScheds, Shift) when NoScheds < 1 bsl Shift -> Shift; calc_sched_bits(NoScheds, Shift) -> calc_sched_bits(NoScheds, Shift+1). init_uniqint_info() -> SmallBits = erlang:system_info({wordsize, internal})*8-4, io:format("SmallBits=~p~n", [SmallBits]), Schedulers = erlang:system_info(schedulers), io:format("Schedulers=~p~n", [Schedulers]), MinSmall = -1*(1 bsl (SmallBits-1)), io:format("MinSmall=~p~n", [MinSmall]), MaxSmall = (1 bsl (SmallBits-1))-1, io:format("MaxSmall=~p~n", [MaxSmall]), SchedBits = calc_sched_bits(Schedulers, 0), io:format("SchedBits=~p~n", [SchedBits]), MaxInt = ((((1 bsl 64) - 1) bsl SchedBits) bor Schedulers) + MinSmall, io:format("MaxInt=~p~n", [MaxInt]), #uniqint_info{min_int = MinSmall, max_int = MaxInt, max_small = MaxSmall, schedulers = Schedulers, sched_bits = SchedBits}. valid_uniqint(Int, #uniqint_info{min_int = MinInt} = UinqintInfo) when Int < MinInt -> valid_uniqint(MinInt, UinqintInfo); valid_uniqint(Int, #uniqint_info{min_int = MinInt, sched_bits = SchedBits, schedulers = Scheds}) -> Int1 = Int - MinInt, {Inc, ThreadNo} = case Int1 band ((1 bsl SchedBits) - 1) of TN when TN > Scheds -> {1, Scheds}; TN -> {0, TN} end, Counter = ((Int1 bsr SchedBits) + Inc) rem (1 bsl 64), ((Counter bsl SchedBits) bor ThreadNo) + MinInt. smaller_valid_uniqint(Int, UinqintInfo) -> Cand = Int-1, case valid_uniqint(Cand, UinqintInfo) of RI when RI < Int -> RI; _ -> smaller_valid_uniqint(Cand, UinqintInfo) end. int32_to_bigendian_list(Int) -> 0 = Int bsr 32, [(Int bsr 24) band 16#ff, (Int bsr 16) band 16#ff, (Int bsr 8) band 16#ff, Int band 16#ff]. mk_uniqint(Int, #uniqint_info {min_int = MinInt, sched_bits = SchedBits} = _UinqintInfo) -> Int1 = Int - MinInt, ThrId = Int1 band ((1 bsl SchedBits) - 1), Value = (Int1 bsr SchedBits) band ((1 bsl 64) - 1), 0 = Int1 bsr (SchedBits + 64), NodeName = atom_to_list(node()), Make = {make_unique_integer, ThrId, Value}, %% erlang:display(Make), Res = erts_debug:get_internal_state(Make), %% erlang:display({uniq_int, Res}), Res. check_uniqint(Int, UinqintInfo) -> UniqInt = mk_uniqint(Int, UinqintInfo), io:format("UniqInt=~p ", [UniqInt]), case UniqInt =:= Int of true -> io:format("OK~n~n", []); false -> io:format("result UniqInt=~p FAILED~n", [UniqInt]), exit(badres) end. check_unique_integer_around(Int, #uniqint_info{min_int = MinInt, max_int = MaxInt} = UinqintInfo) -> {Start, End} = case {Int =< MinInt+100, Int >= MaxInt-100} of {true, false} -> {MinInt, MinInt+100}; {false, false} -> {smaller_valid_uniqint(Int-100, UinqintInfo), valid_uniqint(Int+100, UinqintInfo)}; {false, true} -> {MaxInt-100, MaxInt} end, lists:foldl(fun (I, OldRefInt) -> RefInt = valid_uniqint(I, UinqintInfo), case OldRefInt =:= RefInt of true -> ok; false -> check_uniqint(RefInt, UinqintInfo) end, RefInt end, none, lists:seq(Start, End)). %% helpers print_ret_val(File, Line, Value) -> io:format("~s:~p: ~p~n", [File, Line, Value]), Value. start_node(Config) -> start_node(Config, []). start_node(Config, Opts) when is_list(Config), is_list(Opts) -> ?line Pa = filename:dirname(code:which(?MODULE)), ?line A = erlang:monotonic_time(1) + erlang:time_offset(1), ?line B = erlang:unique_integer([positive]), ?line Name = list_to_atom(atom_to_list(?MODULE) ++ "-" ++ atom_to_list(?config(testcase, Config)) ++ "-" ++ integer_to_list(A) ++ "-" ++ integer_to_list(B)), ?line ?t:start_node(Name, slave, [{args, Opts++" -pa "++Pa}]). stop_node(Node) -> ?t:stop_node(Node).