File 8041-eunit-Add-possibility-to-scale-timeouts.patch of Package erlang
From 0d75a4bd6fbe42081702ea5532979dce4b8b9e55 Mon Sep 17 00:00:00 2001
From: Tomas Abrahamsson <tomas.abrahamsson@gmail.com>
Date: Wed, 6 Sep 2023 20:51:01 +0200
Subject: [PATCH] eunit: Add possibility to scale timeouts
Add an option to eunit:test(..., Options):
{scale_timeouts, N}
to be able to increase the timeouts by some factor, or
decrease it if the factor is below 1.0.
This applies to both the the default 5 second timeout,
and to timeouts specified as {timeout, Seconds, Test}
in eunit test generators.
---
lib/eunit/src/eunit.erl | 5 +++
lib/eunit/src/eunit_proc.erl | 16 ++++++++--
lib/eunit/test/eunit_SUITE.erl | 57 ++++++++++++++++++++++++++++++++--
3 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/lib/eunit/src/eunit.erl b/lib/eunit/src/eunit.erl
index f30c238366..7c84b0209e 100644
--- a/lib/eunit/src/eunit.erl
+++ b/lib/eunit/src/eunit.erl
@@ -140,6 +140,11 @@ test(Tests) ->
%% not automatically execute tests found in related module suffixed with "_tests".
%% This behaviour might be unwanted if execution of modules found in a folder
%% is ordered while it contains both source and test modules.</dd>
+%% <dt>`scale_timeouts'</dt>
+%% <dd>If this numeric value is set, timeouts will get scaled accordingly.
+%% It may be useful when running a set of tests on a slower host.
+%% Examples: `{scale_timeouts,10}' make the timeouts 10 times longer, while
+%% `{scale_timeouts,0.1}' would shorten them by a factor of 10.</dd>
%% </dl>
%%
%% Options in the environment variable EUNIT are also included last in
diff --git a/lib/eunit/src/eunit_proc.erl b/lib/eunit/src/eunit_proc.erl
index 48254f53a3..1cdaaeaf9b 100644
--- a/lib/eunit/src/eunit_proc.erl
+++ b/lib/eunit/src/eunit_proc.erl
@@ -336,9 +336,21 @@ clear_timeout(Ref) ->
erlang:cancel_timer(Ref).
with_timeout(undefined, Default, F, St) ->
- with_timeout(Default, F, St);
+ with_timeout(scale_timeout(Default, St), F, St);
with_timeout(Time, _Default, F, St) ->
- with_timeout(Time, F, St).
+ with_timeout(scale_timeout(Time, St), F, St).
+
+scale_timeout(infinity, _St) ->
+ infinity;
+scale_timeout(Time, St) ->
+ case proplists:get_value(scale_timeouts, St#procstate.options) of
+ undefined ->
+ Time;
+ N when is_integer(N) ->
+ N * Time;
+ N when is_float(N) ->
+ round(N * Time)
+ end.
with_timeout(infinity, F, _St) ->
%% don't start timers unnecessarily
diff --git a/lib/eunit/test/eunit_SUITE.erl b/lib/eunit/test/eunit_SUITE.erl
index 33bf090eeb..377dc97557 100644
--- a/lib/eunit/test/eunit_SUITE.erl
+++ b/lib/eunit/test/eunit_SUITE.erl
@@ -24,11 +24,15 @@
app_test/1, appup_test/1, eunit_test/1, eunit_exact_test/1,
fixture_test/1, primitive_test/1, surefire_utf8_test/1,
surefire_latin_test/1, surefire_c0_test/1, surefire_ensure_dir_test/1,
- stacktrace_at_timeout_test/1]).
+ stacktrace_at_timeout_test/1, scale_timeouts_test/1]).
+
+%% Two eunit tests:
+-export([times_out_test_/0, times_out_default_test/0]).
-export([sample_gen/0]).
-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
-define(TIMEOUT, 1000).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -36,7 +40,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test, appup_test, eunit_test, eunit_exact_test, primitive_test,
fixture_test, surefire_utf8_test, surefire_latin_test, surefire_c0_test,
- surefire_ensure_dir_test, stacktrace_at_timeout_test].
+ surefire_ensure_dir_test, stacktrace_at_timeout_test,
+ scale_timeouts_test].
groups() ->
[].
@@ -203,3 +208,51 @@ check_surefire(Module) ->
%% Check that file is valid XML
xmerl_scan:file(File),
Chars.
+
+scale_timeouts_test(_Config) ->
+ %% Scaling with integers
+ %% The times_out_test_ will timeout after 1 second.
+ %% Scale it up by a factor of 2 and check that at least 2s have passed.
+ Millis1 = run_eunit_test_that_times_out(times_out_test_,
+ [{scale_timeouts, 2}]),
+ ?assert(Millis1 >= 2000, #{duration => Millis1}),
+ ?assert(Millis1 < 5000, #{duration => Millis1}),
+
+ %% Scaling with float: should get rounded
+ %% Scaling down should work too
+ Millis2 = run_eunit_test_that_times_out(times_out_test_,
+ [{scale_timeouts, 0.25}]),
+ ?assert(Millis2 >= 250, #{duration => Millis2}),
+ ?assert(Millis2 < 1000, #{duration => Millis2}),
+
+ %% It should be possible to scale the default timeout as well
+ Millis3 = run_eunit_test_that_times_out(times_out_default_test,
+ [{scale_timeouts, 0.01}]),
+ ?assert(Millis3 > 0, #{duration => Millis3}),
+ ?assert(Millis3 < 1000, #{duration => Millis3}),
+ ok.
+
+run_eunit_test_that_times_out(TestFn, Options) ->
+ T0 = erlang:monotonic_time(millisecond),
+ %% Expect error due to the timeout:
+ case lists:suffix("_test_", atom_to_list(TestFn)) of
+ true ->
+ error = eunit:test({generator, ?MODULE, TestFn}, Options);
+ false ->
+ error = eunit:test({?MODULE, TestFn}, Options)
+ end,
+ T1 = erlang:monotonic_time(millisecond),
+ T1 - T0.
+
+%% an eunit test generator:
+times_out_test_() ->
+ {timeout, 1, % the fun should timeout after this many seconds
+ fun() -> timer:sleep(10_000) % long enough to cause a timeout
+ end}.
+
+%% an eunit test:
+times_out_default_test() ->
+ %% The default timeout for an xyz_test/0 is 5s,
+ %% so this is long enough to cause a time out.
+ timer:sleep(20_000).
+
--
2.35.3