diff options
Diffstat (limited to 'erts/emulator/test/time_SUITE.erl')
-rw-r--r-- | erts/emulator/test/time_SUITE.erl | 769 |
1 files changed, 620 insertions, 149 deletions
diff --git a/erts/emulator/test/time_SUITE.erl b/erts/emulator/test/time_SUITE.erl index 4d12e3449c..87b8c62cfa 100644 --- a/erts/emulator/test/time_SUITE.erl +++ b/erts/emulator/test/time_SUITE.erl @@ -1,23 +1,25 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2011. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. 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. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% -module(time_SUITE). +-compile({nowarn_deprecated_function, {erlang,now,0}}). %% "Time is on my side." -- The Rolling Stones @@ -34,11 +36,19 @@ bad_univ_to_local/1, bad_local_to_univ/1, univ_to_seconds/1, seconds_to_univ/1, consistency/1, - now_unique/1, now_update/1, timestamp/1]). + now_unique/1, now_update/1, timestamp/1, + time_warp_modes/1, + monotonic_time_monotonicity/1, + monotonic_time_monotonicity_parallel/1, + time_unit_conversion/1, + signed_time_unit_conversion/1, + erlang_timestamp/1]). + +-export([init_per_testcase/2, end_per_testcase/2]). -export([local_to_univ_utc/1]). --include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct.hrl"). -export([linear_time/1]). @@ -56,6 +66,12 @@ -define(dst_timezone, 2). +init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) -> + [{testcase, Func}|Config]. + +end_per_testcase(_Func, _Config) -> + ok. + suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> @@ -63,7 +79,13 @@ all() -> bad_univ_to_local, bad_local_to_univ, univ_to_seconds, seconds_to_univ, consistency, - {group, now}, timestamp]. + {group, now}, timestamp, + time_warp_modes, + monotonic_time_monotonicity, + monotonic_time_monotonicity_parallel, + time_unit_conversion, + signed_time_unit_conversion, + erlang_timestamp]. groups() -> [{now, [], [now_unique, now_update]}]. @@ -81,32 +103,29 @@ end_per_group(_GroupName, Config) -> Config. -local_to_univ_utc(suite) -> - []; -local_to_univ_utc(doc) -> - ["Test that DST = true on timezones without DST is ignored"]; +%% Test that DST = true on timezones without DST is ignored local_to_univ_utc(Config) when is_list(Config) -> case os:type() of {unix,_} -> %% TZ variable has a meaning - ?line {ok, Node} = + {ok, Node} = test_server:start_node(local_univ_utc,peer, [{args, "-env TZ UTC"}]), - ?line {{2008,8,1},{0,0,0}} = + {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, false]), - ?line {{2008,8,1},{0,0,0}} = + {{2008,8,1},{0,0,0}} = rpc:call(Node, erlang,localtime_to_universaltime, [{{2008, 8, 1}, {0, 0, 0}}, true]), - ?line [{{2008,8,1},{0,0,0}}] = + [{{2008,8,1},{0,0,0}}] = rpc:call(Node, calendar,local_time_to_universal_time_dst, [{{2008, 8, 1}, {0, 0, 0}}]), - ?line test_server:stop_node(Node), + test_server:stop_node(Node), ok; _ -> {skip,"Only valid on Unix"} @@ -116,24 +135,24 @@ local_to_univ_utc(Config) when is_list(Config) -> %% Tests conversion from univeral to local time. univ_to_local(Config) when is_list(Config) -> - ?line test_univ_to_local(test_data()). + test_univ_to_local(test_data()). test_univ_to_local([{Utc, Local}|Rest]) -> - ?line io:format("Testing ~p => ~p~n", [Local, Utc]), - ?line Local = erlang:universaltime_to_localtime(Utc), - ?line test_univ_to_local(Rest); + io:format("Testing ~p => ~p~n", [Local, Utc]), + Local = erlang:universaltime_to_localtime(Utc), + test_univ_to_local(Rest); test_univ_to_local([]) -> ok. %% Tests conversion from local to universal time. local_to_univ(Config) when is_list(Config) -> - ?line test_local_to_univ(test_data()). + test_local_to_univ(test_data()). test_local_to_univ([{Utc, Local}|Rest]) -> - ?line io:format("Testing ~p => ~p~n", [Utc, Local]), - ?line Utc = erlang:localtime_to_universaltime(Local), - ?line test_local_to_univ(Rest); + io:format("Testing ~p => ~p~n", [Utc, Local]), + Utc = erlang:localtime_to_universaltime(Local), + test_local_to_univ(Rest); test_local_to_univ([]) -> ok. @@ -141,11 +160,11 @@ test_local_to_univ([]) -> %% generate a badarg. bad_univ_to_local(Config) when is_list(Config) -> - ?line bad_test_univ_to_local(bad_dates()). + bad_test_univ_to_local(bad_dates()). bad_test_univ_to_local([Utc|Rest]) -> - ?line io:format("Testing ~p~n", [Utc]), - ?line case catch erlang:universaltime_to_localtime(Utc) of + io:format("Testing ~p~n", [Utc]), + case catch erlang:universaltime_to_localtime(Utc) of {'EXIT', {badarg, _}} -> bad_test_univ_to_local(Rest) end; bad_test_univ_to_local([]) -> @@ -155,11 +174,11 @@ bad_test_univ_to_local([]) -> %% generate a badarg. bad_local_to_univ(Config) when is_list(Config) -> - ?line bad_test_local_to_univ(bad_dates()). + bad_test_local_to_univ(bad_dates()). bad_test_local_to_univ([Local|Rest]) -> - ?line io:format("Testing ~p~n", [Local]), - ?line case catch erlang:localtime_to_universaltime(Local) of + io:format("Testing ~p~n", [Local]), + case catch erlang:localtime_to_universaltime(Local) of {'EXIT', {badarg, _}} -> bad_test_local_to_univ(Rest) end; bad_test_local_to_univ([]) -> @@ -190,28 +209,22 @@ test_seconds_to_univ([]) -> %% Test that the the different time functions return -%% consistent results. (See the test case for assumptions -%% and limitations.) -consistency(Config) when is_list(Config) -> - %% Test the following equations: - %% date() & time() == erlang:localtime() - %% erlang:universaltime() + timezone == erlang:localtime() +%% consistent results. +consistency(_Config) -> + %% Test that: + %% * date() & time() gives the same time as erlang:localtime() %% - %% Assumptions: - %% Middle-European time zone, EU rules for daylight-saving time. - %% - %% Limitations: - %% Localtime and universaltime must be in the same month. - %% Daylight-saving calculations are incorrect from the last - %% Sunday of March and October to the end of the month. + %% * the difference between erlang:universaltime() and + %% erlang:localtime() is reasonable (with assuming any + %% particular timezone) - ?line ok = compare_date_time_and_localtime(16), - ?line ok = compare_local_and_universal(16). + ok = compare_date_time_and_localtime(16), + compare_local_and_universal(16). compare_date_time_and_localtime(Times) when Times > 0 -> - ?line {Year, Mon, Day} = date(), - ?line {Hour, Min, Sec} = time(), - ?line case erlang:localtime() of + {Year, Mon, Day} = date(), + {Hour, Min, Sec} = time(), + case erlang:localtime() of {{Year, Mon, Day}, {Hour, Min, Sec}} -> ok; _ -> compare_date_time_and_localtime(Times-1) end; @@ -219,74 +232,84 @@ compare_date_time_and_localtime(0) -> error. compare_local_and_universal(Times) when Times > 0 -> - case compare(erlang:universaltime(), erlang:localtime()) of - true -> ok; - false -> compare_local_and_universal(Times-1) - end; -compare_local_and_universal(0) -> - error. + Utc = erlang:universaltime(), + Local = erlang:localtime(), + io:format("local = ~p, utc = ~p", [Local,Utc]), -compare(Utc0, Local) -> - io:format("local = ~p, utc = ~p", [Local, Utc0]), - Utc = linear_time(Utc0)+effective_timezone(Utc0)*3600, - case linear_time(Local) of - Utc -> true; - Other -> - io:format("Failed: local = ~p, utc = ~p~n", - [Other, Utc]), - false + AcceptableDiff = 14*3600, + case linear_time(Utc) - linear_time(Local) of + Diff when abs(Diff) < AcceptableDiff -> + ok; + Diff -> + io:format("More than ~p seconds difference betwen " + "local and universal time", [Diff]), + ct:fail(huge_diff) end. %% This function converts a date and time to a linear time. %% Two linear times can be subtracted to give their difference %% in seconds. %% -%% XXX Limitations: The length of months and leap years are not -%% taken into account; thus a comparision of dates is only -%% valid if they are in the SAME month. +%% XXX Limitations: Simplified leap year calc will fail for 2100 :-) linear_time({{Year, Mon, Day}, {Hour, Min, Sec}}) -> - 86400*(366*Year + 31*(Mon-1) + (Day-1)) + + 86400*(year_to_days(Year) + month_to_days(Year,Mon) + (Day-1)) + 3600*Hour + 60*Min + Sec. -%% This functions returns either the normal timezone or the -%% the DST timezone, depending on the given UTC time. -%% -%% XXX This function uses an approximation of the EU rule for -%% daylight saving time. This function will fail in the -%% following intervals: After the last Sunday in March upto -%% the end of March, and after the last Sunday in October -%% upto the end of October. +year_to_days(Year) -> + Year * 365 + (Year-1) div 4. -effective_timezone(Time) -> - case os:type() of - {unix,_} -> - case os:cmd("date '+%Z'") of - "SAST"++_ -> - 2; - _ -> - effective_timezone1(Time) - end; - _ -> - effective_timezone1(Time) - end. +month_to_days(Year, Mon) -> + DoM = [31,days_in_february(Year),31,30,31,30,31,31,30,31,30,31], + {PastMonths,_} = lists:split(Mon-1, DoM), + lists:sum(PastMonths). -effective_timezone1({{_Year,Mon,_Day}, _}) when Mon < 4 -> - ?timezone; -effective_timezone1({{_Year,Mon,_Day}, _}) when Mon > 10 -> - ?timezone; -effective_timezone1(_) -> - ?dst_timezone. +days_in_february(Year) -> + case (Year rem 4) of + 0 -> 29; + _ -> 28 + end. %% Test (the bif) os:timestamp/0, which is something quite like, but not %% similar to erlang:now... -timestamp(suite) -> - []; -timestamp(doc) -> - ["Test that os:timestamp works."]; +%% Test that os:timestamp works. timestamp(Config) when is_list(Config) -> - repeating_timestamp_check(100000). + try + repeating_timestamp_check(100000) + catch + throw : {fail, Failure} -> + %% + %% Our time warping test machines currently warps + %% time every 6:th second. If we get a warp during + %% 10 seconds, assume this is a time warping test + %% and ignore the failure. + %% + case had_time_warp(10) of + true -> + {skip, "Seems to be time warp test run..."}; + false -> + ct:fail(Failure) + end + end. + +os_system_time_offset() -> + erlang:convert_time_unit(os:system_time() - erlang:monotonic_time(), + native, micro_seconds). + +had_time_warp(Secs) -> + had_time_warp(os_system_time_offset(), Secs). + +had_time_warp(OrigOffs, 0) -> + false; +had_time_warp(OrigOffs, N) -> + receive after 1000 -> ok end, + case OrigOffs - os_system_time_offset() of + Diff when Diff > 500000; Diff < -500000 -> + true; + _Diff -> + had_time_warp(OrigOffs, N-1) + end. repeating_timestamp_check(0) -> ok; @@ -300,9 +323,7 @@ repeating_timestamp_check(N) -> C < 1000000 -> ok; true -> - test_server:fail( - lists:flatten( - io_lib:format("Strange return from os:timestamp/0 ~w~n",[TS]))) + ct:fail("Strange return from os:timestamp/0 ~w~n",[TS]) end, %% I assume the now and timestamp should not differ more than 1 hour, %% which is safe assuming the system has not had a large time-warp @@ -312,15 +333,15 @@ repeating_timestamp_check(N) -> NSecs = NA*1000000+NB+round(NC/1000000), case Secs - NSecs of TooLarge when TooLarge > 3600 -> - test_server:fail( - lists:flatten( + throw({fail, + lists:flatten( io_lib:format("os:timestamp/0 is ~w s more than erlang:now/0", - [TooLarge]))); + [TooLarge]))}); TooSmall when TooSmall < -3600 -> - test_server:fail( + throw({fail, lists:flatten( io_lib:format("os:timestamp/0 is ~w s less than erlang:now/0", - [-TooSmall]))); + [-TooSmall]))}); _ -> ok end, @@ -335,22 +356,22 @@ repeating_timestamp_check(N) -> %% times (in microseconds). now_unique(Config) when is_list(Config) -> - ?line now_unique(1000, now(), []), - ?line fast_now_unique(100000, now()). + now_unique(1000, now(), []), + fast_now_unique(100000, now()). now_unique(N, Previous, Result) when N > 0 -> - ?line case now() of + case now() of Previous -> - test_server:fail("now/0 returned the same value twice"); + ct:fail("now/0 returned the same value twice"); New -> now_unique(N-1, New, [New|Result]) end; now_unique(0, _, [Then|Rest]) -> - ?line now_calc_increment(Rest, microsecs(Then), []). + now_calc_increment(Rest, microsecs(Then), []). now_calc_increment([Then|Rest], Previous, _Result) -> - ?line This = microsecs(Then), - ?line now_calc_increment(Rest, This, [Previous-This]); + This = microsecs(Then), + now_calc_increment(Rest, This, [Previous-This]); now_calc_increment([], _, Differences) -> {comment, "Median increment: " ++ integer_to_list(median(Differences))}. @@ -358,15 +379,15 @@ fast_now_unique(0, _) -> ok; fast_now_unique(N, Then) -> case now() of Then -> - ?line ?t:fail("now/0 returned the same value twice"); + ct:fail("now/0 returned the same value twice"); Now -> fast_now_unique(N-1, Now) end. median(Unsorted_List) -> - ?line Length = length(Unsorted_List), - ?line List = lists:sort(Unsorted_List), - ?line case Length rem 2 of + Length = length(Unsorted_List), + List = lists:sort(Unsorted_List), + case Length rem 2 of 0 -> % Even length. [A, B] = lists:nthtail((Length div 2)-1, List), (A+B)/2; @@ -382,31 +403,460 @@ microsecs({Mega_Secs, Secs, Microsecs}) -> %% calls to erlang:localtime(). now_update(Config) when is_list(Config) -> - case ?t:is_debug() of - false -> ?line now_update1(10); + case test_server:is_debug() of + false -> now_update1(10); true -> {skip,"Unreliable in DEBUG build"} end. now_update1(N) when N > 0 -> - ?line T1_linear = linear_time(erlang:localtime()), - ?line T1_now = microsecs(now()), + T1_linear = linear_time(erlang:localtime()), + T1_now = microsecs(now()), - ?line receive after 1008 -> ok end, + receive after 1008 -> ok end, - ?line T2_linear = linear_time(erlang:localtime()), - ?line T2_now = microsecs(now()), + T2_linear = linear_time(erlang:localtime()), + T2_now = microsecs(now()), - ?line Linear_Diff = (T2_linear-T1_linear)*1000000, - ?line Now_Diff = T2_now-T1_now, - test_server:format("Localtime diff = ~p; now() diff = ~p", - [Linear_Diff, Now_Diff]), - ?line case abs(Linear_Diff - Now_Diff) of + Linear_Diff = (T2_linear-T1_linear)*1000000, + Now_Diff = T2_now-T1_now, + io:format("Localtime diff = ~p; now() diff = ~p", [Linear_Diff, Now_Diff]), + case abs(Linear_Diff - Now_Diff) of Abs_Delta when Abs_Delta =< 40000 -> ok; _ -> now_update1(N-1) end; now_update1(0) -> - ?line test_server:fail(). + ct:fail("now_update zero"). + +time_warp_modes(Config) when is_list(Config) -> + %% All time warp modes always supported in + %% combination with no time correction... + check_time_warp_mode(Config, false, no_time_warp), + check_time_warp_mode(Config, false, single_time_warp), + check_time_warp_mode(Config, false, multi_time_warp), + + erts_debug:set_internal_state(available_internal_state, true), + try + case erts_debug:get_internal_state({check_time_config, + true, no_time_warp}) of + false -> ok; + true -> check_time_warp_mode(Config, true, no_time_warp) + end, + case erts_debug:get_internal_state({check_time_config, + true, single_time_warp}) of + false -> ok; + true -> check_time_warp_mode(Config, true, single_time_warp) + end, + case erts_debug:get_internal_state({check_time_config, + true, multi_time_warp}) of + false -> ok; + true -> check_time_warp_mode(Config, true, multi_time_warp) + end + after + erts_debug:set_internal_state(available_internal_state, false) + end. + +check_time_warp_mode(Config, TimeCorrection, TimeWarpMode) -> + io:format("~n~n~n***** Testing TimeCorrection=~p TimeWarpMode=~p *****~n", + [TimeCorrection, TimeWarpMode]), + Mon = erlang:monitor(time_offset, clock_service), + _ = erlang:time_offset(), + Start = erlang:monotonic_time(1000), + MonotonicityTimeout = 2000, + {ok, Node} = start_node(Config, + "+c " ++ atom_to_list(TimeCorrection) + ++ " +C " ++ atom_to_list(TimeWarpMode)), + StartTime = rpc:call(Node, erlang, system_info, [start_time]), + Me = self(), + MonotincityTestStarted = make_ref(), + MonotincityTestDone = make_ref(), + spawn_link(Node, + fun () -> + Me ! MonotincityTestStarted, + cmp_times(erlang:start_timer(MonotonicityTimeout, + self(), + timeout), + erlang:monotonic_time()), + Me ! MonotincityTestDone + end), + receive MonotincityTestStarted -> ok end, + check_time_offset(Node, TimeWarpMode), + TimeWarpMode = rpc:call(Node, erlang, system_info, [time_warp_mode]), + TimeCorrection = rpc:call(Node, erlang, system_info, [time_correction]), + receive MonotincityTestDone -> ok end, + MonotonicTime = rpc:call(Node, erlang, monotonic_time, []), + MonotonicTimeUnit = rpc:call(Node, + erlang, + convert_time_unit, + [1, seconds, native]), + UpMilliSeconds = erlang:convert_time_unit(MonotonicTime - StartTime, + MonotonicTimeUnit, + milli_seconds), + io:format("UpMilliSeconds=~p~n", [UpMilliSeconds]), + End = erlang:monotonic_time(milli_seconds), + stop_node(Node), + try + true = (UpMilliSeconds > (98*MonotonicityTimeout) div 100), + true = (UpMilliSeconds < (102*(End-Start)) div 100) + catch + error:_ -> + io:format("Uptime inconsistency", []), + case {TimeCorrection, erlang:system_info(time_correction)} of + {true, true} -> + ct:fail(uptime_inconsistency); + {true, false} -> + _ = erlang:time_offset(), + receive + {'CHANGE', Mon, time_offset, clock_service, _} -> + ignore + after 1000 -> + ct:fail(uptime_inconsistency) + end; + _ -> + ignore + end + end, + erlang:demonitor(Mon, [flush]), + ok. + +check_time_offset(Node, no_time_warp) -> + final = rpc:call(Node, erlang, system_info, [time_offset]), + final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), + final = rpc:call(Node, erlang, system_info, [time_offset]); +check_time_offset(Node, single_time_warp) -> + preliminary = rpc:call(Node, erlang, system_info, [time_offset]), + preliminary = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), + final = rpc:call(Node, erlang, system_info, [time_offset]), + final = rpc:call(Node, erlang, system_flag, [time_offset, finalize]); +check_time_offset(Node, multi_time_warp) -> + volatile = rpc:call(Node, erlang, system_info, [time_offset]), + volatile = rpc:call(Node, erlang, system_flag, [time_offset, finalize]), + volatile = rpc:call(Node, erlang, system_info, [time_offset]). + +monotonic_time_monotonicity(Config) when is_list(Config) -> + Done = erlang:start_timer(10000,self(),timeout), + cmp_times(Done, erlang:monotonic_time()). + +cmp_times(Done, X0) -> + X1 = erlang:monotonic_time(), + X2 = erlang:monotonic_time(), + X3 = erlang:monotonic_time(), + X4 = erlang:monotonic_time(), + X5 = erlang:monotonic_time(), + true = (X0 =< X1), + true = (X1 =< X2), + true = (X2 =< X3), + true = (X3 =< X4), + true = (X4 =< X5), + receive + {timeout, Done, timeout} -> + ok + after 0 -> + cmp_times(Done, X5) + end. + +-define(NR_OF_MONOTONIC_CALLS, 100000). + +monotonic_time_monotonicity_parallel(Config) when is_list(Config) -> + Me = self(), + Result = make_ref(), + Go = make_ref(), + UpAndRunning = make_ref(), + NoOnlnScheds = erlang:system_info(schedulers_online), + OffsetUI = erlang:unique_integer([monotonic]), + OffsetMT = erlang:monotonic_time(), + MinHSz = ?NR_OF_MONOTONIC_CALLS*(2 + + 3 + + erts_debug:flat_size(OffsetUI) + + erts_debug:flat_size(OffsetMT)), + Ps = lists:map( + fun (Sched) -> + spawn_opt( + fun () -> + Me ! {self(), UpAndRunning}, + receive Go -> ok end, + Res = fetch_monotonic(?NR_OF_MONOTONIC_CALLS, []), + Me ! {self(), Result, Sched, Res} + end, + [{scheduler, Sched}, + {priority, max}, + {min_heap_size, MinHSz}]) + end, + lists:seq(1, NoOnlnScheds)), + lists:foreach(fun (P) -> receive {P, UpAndRunning} -> ok end end, Ps), + lists:foreach(fun (P) -> P ! Go end, Ps), + TMs = recv_monotonics(Result, OffsetMT, OffsetUI, NoOnlnScheds, []), + true = check_monotonic_result(TMs, OffsetMT, OffsetUI, true). + +check_monotonic_result([{_Sched, _PrevUI, _MT, _PostUI}], + _OffsetMT, _OffsetUI, Res) -> + Res; +check_monotonic_result([{_ASched, _APrevUI, AMT, APostUI} = A, + {_BSched, BPrevUI, BMT, _BPostUI} = B | _] = L, + OffsetMT, OffsetUI, Res) -> + NewRes = case (AMT =< BMT) orelse (BPrevUI < APostUI) of + true -> + Res; + false -> + io:format("INCONSISTENCY: ~p ~p~n", [A, B]), + false + end, + check_monotonic_result(tl(L), OffsetMT, OffsetUI, NewRes). + +recv_monotonics(_Result, _OffsetMT, _OffsetUI, 0, Acc) -> + lists:keysort(2, Acc); +recv_monotonics(Result, OffsetMT, OffsetUI, N, Acc) -> + receive + {_, Result, Sched, Res} -> + CRes = convert_monotonic(Sched, OffsetMT, OffsetUI, Res, []), + recv_monotonics(Result, OffsetMT, OffsetUI, N-1, CRes ++ Acc) + end. + +convert_monotonic(_Sched, _OffsetMT, _OffsetUI, [{_MT, _UI}], Acc) -> + Acc; +convert_monotonic(Sched, OffsetMT, OffsetUI, + [{MT, UI}, {_PrevMT, PrevUI} | _] = L, Acc) -> + convert_monotonic(Sched, OffsetMT, OffsetUI, tl(L), + [{Sched, PrevUI-OffsetUI, MT-OffsetMT, UI-OffsetUI} + | Acc]). + +fetch_monotonic(0, Acc) -> + Acc; +fetch_monotonic(N, Acc) -> + MT = erlang:monotonic_time(), + UI = erlang:unique_integer([monotonic]), + fetch_monotonic(N-1, [{MT, UI} | Acc]). + +-define(CHK_RES_CONVS_TIMEOUT, 400). + +time_unit_conversion(Config) when is_list(Config) -> + Mon = erlang:monitor(time_offset, clock_service), + start_check_res_convs(Mon, 1000000000000), + start_check_res_convs(Mon, 2333333333333), + start_check_res_convs(Mon, 5732678356789), + erlang:demonitor(Mon, [flush]). + +start_check_res_convs(Mon, Res) -> + io:format("Checking ~p time_unit~n", [Res]), + check_res_convs(Mon, + erlang:start_timer(?CHK_RES_CONVS_TIMEOUT, + self(), + timeout), + Res). + + +check_res_convs(Mon, Done, Res) -> + receive + {timeout, Done, timeout} -> + case Res div 10 of + 0 -> + ok; + NewRes -> + start_check_res_convs(Mon, NewRes) + end + after 0 -> + do_check_res_convs(Mon, Done, Res) + end. + +do_check_res_convs(Mon, Done, Res) -> + TStart = erlang:monotonic_time(), + T = erlang:monotonic_time(Res), + TEnd = erlang:monotonic_time(), + TMin = erlang:convert_time_unit(TStart, native, Res), + TMax = erlang:convert_time_unit(TEnd, native, Res), + %io:format("~p =< ~p =< ~p~n", [TMin, T, TEnd]), + true = (TMin =< T), + true = (TMax >= T), + check_time_offset_res_conv(Mon, Res), + check_res_convs(Mon, Done, Res). + + +check_time_offset_res_conv(Mon, Res) -> + TORes = erlang:time_offset(Res), + TO = erlang:time_offset(), + case erlang:convert_time_unit(TO, native, Res) of + TORes -> + ok; + TORes2 -> + case check_time_offset_change(Mon, TO, 1000) of + {TO, false} -> + ct:fail({time_unit_conversion_inconsistency, + TO, TORes, TORes2}); + {_NewTO, true} -> + io:format("time_offset changed", []), + check_time_offset_res_conv(Mon, Res) + end + end. + +signed_time_unit_conversion(Config) when is_list(Config) -> + chk_strc(1000000000, 1000000), + chk_strc(1000000000, 1000), + chk_strc(1000000000, 1), + chk_strc(1000000, 1000), + chk_strc(1000000, 1), + chk_strc(1000, 1), + chk_strc(4711, 17), + chk_strc(1 bsl 10, 1), + chk_strc(1 bsl 16, 10), + chk_strc(1 bsl 17, 1 bsl 8), + chk_strc((1 bsl 17) + 1, (1 bsl 8) - 1), + chk_strc(1 bsl 17, 11), + ok. + +chk_strc(Res0, Res1) -> + case (Res0 /= Res1) andalso (Res0 =< 1000000) andalso (Res1 =< 1000000) of + true -> + {FromRes, ToRes} = case Res0 > Res1 of + true -> {Res0, Res1}; + false -> {Res1, Res0} + end, + MinFromValuesPerToValue = FromRes div ToRes, + MaxFromValuesPerToValue = ((FromRes-1) div ToRes)+1, + io:format("~p -> ~p [~p, ~p]~n", + [FromRes, ToRes, + MinFromValuesPerToValue, MaxFromValuesPerToValue]), + chk_values_per_value(FromRes, ToRes, + -10*FromRes, 10*FromRes, + MinFromValuesPerToValue, + MaxFromValuesPerToValue, + undefined, MinFromValuesPerToValue); + _ -> + ok + end, + chk_random_values(Res0, Res1), + chk_random_values(Res1, Res0), + ok. + +chk_random_values(FR, TR) -> + io:format("rand values ~p -> ~p~n", [FR, TR]), + rand:seed(exsplus, {268438039,268440479,268439161}), + Values = lists:map(fun (_) -> rand:uniform(1 bsl 65) - (1 bsl 64) end, + lists:seq(1, 100000)), + CheckFun = fun (V) -> + CV = erlang:convert_time_unit(V, FR, TR), + case {(FR*CV) div TR =< V, + (FR*(CV+1)) div TR >= V} of + {true, true} -> + ok; + Failure -> + ct:fail({Failure, CV, V, FR, TR}) + end + end, + lists:foreach(CheckFun, Values). + + +chk_values_per_value(_FromRes, _ToRes, + EndValue, EndValue, + MinFromValuesPerToValue, MaxFromValuesPerToValue, + _ToValue, FromValueCount) -> +% io:format("~p [~p]~n", [EndValue, FromValueCount]), + case ((MinFromValuesPerToValue =< FromValueCount) + andalso (FromValueCount =< MaxFromValuesPerToValue)) of + false -> + ct:fail({MinFromValuesPerToValue, + FromValueCount, + MaxFromValuesPerToValue}); + true -> + ok + end; +chk_values_per_value(FromRes, ToRes, Value, EndValue, + MinFromValuesPerToValue, MaxFromValuesPerToValue, + ToValue, FromValueCount) -> + case erlang:convert_time_unit(Value, FromRes, ToRes) of + ToValue -> + chk_values_per_value(FromRes, ToRes, + Value+1, EndValue, + MinFromValuesPerToValue, + MaxFromValuesPerToValue, + ToValue, FromValueCount+1); + NewToValue -> + case ((MinFromValuesPerToValue =< FromValueCount) + andalso (FromValueCount =< MaxFromValuesPerToValue)) of + false -> + ct:fail({MinFromValuesPerToValue, + FromValueCount, + MaxFromValuesPerToValue}); + true -> + % io:format("~p -> ~p [~p]~n", + % [Value, NewToValue, FromValueCount]), + chk_values_per_value(FromRes, ToRes, + Value+1, EndValue, + MinFromValuesPerToValue, + MaxFromValuesPerToValue, + NewToValue, 1) + end + end. + +erlang_timestamp(Config) when is_list(Config) -> + Mon = erlang:monitor(time_offset, clock_service), + {TO, _} = check_time_offset_change(Mon, + erlang:time_offset(), + 0), + Done = erlang:start_timer(10000,self(),timeout), + ok = check_erlang_timestamp(Done, Mon, TO). + +check_erlang_timestamp(Done, Mon, TO) -> + receive + {timeout, Done, timeout} -> + erlang:demonitor(Mon, [flush]), + ok + after 0 -> + do_check_erlang_timestamp(Done, Mon, TO) + end. + +do_check_erlang_timestamp(Done, Mon, TO) -> + MinMon = erlang:monotonic_time(), + {MegaSec, Sec, MicroSec} = erlang:timestamp(), + MaxMon = erlang:monotonic_time(), + TsMin = erlang:convert_time_unit(MinMon+TO, + native, + micro_seconds), + TsMax = erlang:convert_time_unit(MaxMon+TO, + native, + micro_seconds), + TsTime = (MegaSec*1000000+Sec)*1000000+MicroSec, + case (TsMin =< TsTime) andalso (TsTime =< TsMax) of + true -> + NewTO = case erlang:time_offset() of + TO -> + TO; + _ -> + check_time_offset_change(Mon, TO, 0) + end, + check_erlang_timestamp(Done, Mon, NewTO); + false -> + io:format("TsMin=~p TsTime=~p TsMax=~p~n", [TsMin, TsTime, TsMax]), + io:format("Detected inconsistency; " + "checking for time_offset change...", []), + case check_time_offset_change(Mon, TO, 1000) of + {TO, false} -> + ct:fail(timestamp_inconsistency); + {NewTO, true} -> + io:format("time_offset changed", []), + check_erlang_timestamp(Done, Mon, NewTO) + end + end. + +check_time_offset_change(Mon, TO, Wait) -> + process_changed_time_offset(Mon, TO, false, Wait). + +process_changed_time_offset(Mon, TO, Changed, Wait) -> + receive + {'CHANGE', Mon, time_offset, clock_service, NewTO} -> + process_changed_time_offset(Mon, NewTO, true, Wait) + after Wait -> + case erlang:time_offset() of + TO -> + {TO, Changed}; + _OtherTO -> + receive + {'CHANGE', Mon, time_offset, clock_service, NewTO} -> + process_changed_time_offset(Mon, NewTO, true, Wait) + end + end + end. + + %% Returns the test data: a list of {Utc, Local} tuples. @@ -423,13 +873,13 @@ test_data() -> _ -> {?timezone,?dst_timezone} end, - ?line test_data(nondst_dates(), TZ) ++ + test_data(nondst_dates(), TZ) ++ test_data(dst_dates(), DSTTZ) ++ crossover_test_data(crossover_dates(), TZ). %% test_data1() -> -%% ?line test_data(nondst_dates(), ?timezone) ++ +%% test_data(nondst_dates(), ?timezone) ++ %% test_data(dst_dates(), ?dst_timezone) ++ %% crossover_test_data(crossover_dates(), ?timezone). @@ -437,16 +887,16 @@ crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone > 0 -> Hour = 23, Min = 35, Sec = 55, - ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, - ?line Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, - ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; + Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, + Local = {{Year, Month, Day+1}, {Hour+TimeZone-24, Min, Sec}}, + [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([{Year, Month, Day}|Rest], TimeZone) when TimeZone < 0 -> Hour = 0, Min = 23, Sec = 12, - ?line Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, - ?line Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, - ?line [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; + Utc = {{Year, Month, Day}, {Hour, Min, Sec}}, + Local = {{Year, Month, Day-1}, {Hour+TimeZone+24, Min, Sec}}, + [{Utc, Local}|crossover_test_data(Rest, TimeZone)]; crossover_test_data([], _) -> []. @@ -454,9 +904,9 @@ test_data([Date|Rest], TimeZone) -> Hour = 12, Min = 45, Sec = 7, - ?line Utc = {Date, {Hour, Min, Sec}}, - ?line Local = {Date, {Hour+TimeZone, Min, Sec}}, - ?line [{Utc, Local}|test_data(Rest, TimeZone)]; + Utc = {Date, {Hour, Min, Sec}}, + Local = {Date, {Hour+TimeZone, Min, Sec}}, + [{Utc, Local}|test_data(Rest, TimeZone)]; test_data([], _) -> []. @@ -542,4 +992,25 @@ bad_dates() -> {{1996, 4, 30}, {12, 0, -1}}, % Sec {{1996, 4, 30}, {12, 0, 60}}]. - + +start_node(Config) -> + start_node(Config, ""). + +start_node(Config, Args) -> + TestCase = proplists:get_value(testcase, Config), + PA = filename:dirname(code:which(?MODULE)), + ESTime = erlang:monotonic_time(1) + erlang:time_offset(1), + Unique = erlang:unique_integer([positive]), + Name = list_to_atom(atom_to_list(?MODULE) + ++ "-" + ++ atom_to_list(TestCase) + ++ "-" + ++ integer_to_list(ESTime) + ++ "-" + ++ integer_to_list(Unique)), + test_server:start_node(Name, + slave, + [{args, "-pa " ++ PA ++ " " ++ Args}]). + +stop_node(Node) -> + test_server:stop_node(Node). |