File 2621-eunit-Include-a-stacktrace-when-a-test-times-out.patch of Package erlang
From 5ddb41e42652c270e62aab8baf9bfa1105875362 Mon Sep 17 00:00:00 2001
From: Tomas Abrahamsson <tomas.abrahamsson@gmail.com>
Date: Fri, 3 Sep 2021 17:59:25 +0200
Subject: [PATCH] eunit: Include a stacktrace when a test times out
In an eunit test, when a test case times out, include a stacktrace.
This can be useful when debugging.
In the event of a test timeout, the printout now looks like this:
1> eunit:test(tl, [verbose]).
eunit:test(tl, [verbose]).
======================== EUnit ========================
tl: takes_too_long_test (module 'tl')...*timed out*
in function timer:sleep/1 (timer.erl, line 152)
in call from tl:takes_too_long_test/0 (/.../tl.erl, line 6)
in call from eunit_test:'-mf_wrapper/2-fun-0-'/2 (eunit_test.erl, line 273)
in call from eunit_test:run_testfun/1 (eunit_test.erl, line 71)
in call from eunit_proc:run_test/1 (/.../eunit_proc.erl, line 528)
in call from eunit_proc:with_timeout/3 (/.../eunit_proc.erl, line 353)
in call from eunit_proc:handle_test/2 (/.../eunit_proc.erl, line 511)
in call from eunit_proc:tests_inorder/3 (/.../eunit_proc.erl, line 453)
undefined
=======================================================
Failed: 0. Skipped: 0. Passed: 0.
One or more tests were cancelled.
error
Previously, it looked like below.
1> eunit:test(tl, [verbose]).
eunit:test(tl, [verbose]).
======================== EUnit ========================
tl: takes_too_long_test (module 'tl')...*timed out*
undefined
=======================================================
Failed: 0. Skipped: 0. Passed: 0.
One or more tests were cancelled.
error
For an eunit_listener, the handle_cancel/3 will now be called
with {timeout, #{stacktrace => Stacktrace}} as 2nd argument,
in case of a timeout. Previously it was only the atom timeout.
In the eunit_surefire handler, include the stacktrace in the xml,
as well.
---
lib/eunit/src/eunit_lib.erl | 2 +-
lib/eunit/src/eunit_proc.erl | 11 ++++++++++-
lib/eunit/src/eunit_surefire.erl | 4 ++++
lib/eunit/src/eunit_tty.erl | 3 +++
lib/eunit/test/Makefile | 3 ++-
lib/eunit/test/eunit_SUITE.erl | 15 +++++++++++++--
lib/eunit/test/ttimesout.erl | 6 ++++++
7 files changed, 39 insertions(+), 5 deletions(-)
create mode 100644 lib/eunit/test/ttimesout.erl
diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl
index 5b7d90ab0d..c7555597c2 100644
--- a/lib/eunit/src/eunit_lib.erl
+++ b/lib/eunit/src/eunit_lib.erl
@@ -36,7 +36,7 @@
command/2, command/3, trie_new/0, trie_store/2, trie_match/2,
split_node/1, consult_file/1, list_dir/1, format_exit_term/1,
format_exception/1, format_exception/2, format_error/1, format_error/2,
- is_not_test/1]).
+ format_stacktrace/1, is_not_test/1]).
-define(DEFAULT_DEPTH, 20).
diff --git a/lib/eunit/src/eunit_proc.erl b/lib/eunit/src/eunit_proc.erl
index fb6619a6d4..7622bab2a9 100644
--- a/lib/eunit/src/eunit_proc.erl
+++ b/lib/eunit/src/eunit_proc.erl
@@ -95,6 +95,9 @@ get_output() ->
%% {cancel, Descriptor}
%% where Descriptor can be:
%% timeout a timeout occurred
+%% {timeout, #{stacktrace := Stacktrace}
+%% a timeout occurred and there is a stacktrace
+%% of the eunit test
%% {blame, Id} forced to terminate because of item `Id'
%% {abort, Cause} the test or group failed to execute
%% {exit, Reason} the test process terminated unexpectedly
@@ -273,7 +276,13 @@ insulator_wait(Child, Parent, Buf, St) ->
io_request(From, ReplyAs, Req, []),
insulator_wait(Child, Parent, Buf, St);
{timeout, Child, Id} ->
- exit_messages(Id, timeout, St),
+ Timeout = case process_info(Child, current_stacktrace) of
+ undefined ->
+ timeout;
+ {current_stacktrace, Stack} ->
+ {timeout, #{stacktrace => Stack}}
+ end,
+ exit_messages(Id, Timeout, St),
kill_task(Child, St);
{'EXIT', Child, normal} ->
terminate_insulator(St);
diff --git a/lib/eunit/src/eunit_surefire.erl b/lib/eunit/src/eunit_surefire.erl
index 71f3765f21..a37ec72965 100644
--- a/lib/eunit/src/eunit_surefire.erl
+++ b/lib/eunit/src/eunit_surefire.erl
@@ -395,6 +395,10 @@ format_testcase_result({skipped, {abort, Error}}) when is_tuple(Error) ->
[?INDENT, ?INDENT, <<"<skipped type=\"">>, escape_attr(atom_to_list(element(1, Error))), <<"\">">>, ?NEWLINE,
escape_text(eunit_lib:format_error(Error)),
?INDENT, ?INDENT, <<"</skipped>">>, ?NEWLINE];
+format_testcase_result({skipped, {timeout, #{stacktrace := Stack}}}) ->
+ [?INDENT, ?INDENT, <<"<skipped type=\"timeout\">">>, ?NEWLINE,
+ escape_text(eunit_lib:format_stacktrace(Stack)),
+ ?INDENT, ?INDENT, <<"</skipped>">>, ?NEWLINE];
format_testcase_result({skipped, {Type, Term}}) when is_atom(Type) ->
[?INDENT, ?INDENT, <<"<skipped type=\"">>, escape_attr(atom_to_list(Type)), <<"\">">>, ?NEWLINE,
escape_text(io_lib:write(Term)),
diff --git a/lib/eunit/src/eunit_tty.erl b/lib/eunit/src/eunit_tty.erl
index fd5245c971..aee8e466e2 100644
--- a/lib/eunit/src/eunit_tty.erl
+++ b/lib/eunit/src/eunit_tty.erl
@@ -253,6 +253,9 @@ format_cancel(undefined, _) ->
"*skipped*\n";
format_cancel(timeout, _) ->
"*timed out*\n";
+format_cancel({timeout, #{stacktrace := Stack}}, _) ->
+ ["*timed out*\n",
+ eunit_lib:format_stacktrace(Stack)];
format_cancel({startup, Reason}, Depth) ->
io_lib:fwrite("*could not start test process*\n::~tP\n\n",
[Reason, Depth]);
diff --git a/lib/eunit/test/Makefile b/lib/eunit/test/Makefile
index 99d1f8093a..903adfde3f 100644
--- a/lib/eunit/test/Makefile
+++ b/lib/eunit/test/Makefile
@@ -24,7 +24,8 @@ MODULES = \
eunit_SUITE \
tc0 \
tlatin \
- tutf8
+ tutf8 \
+ ttimesout
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/eunit/test/eunit_SUITE.erl b/lib/eunit/test/eunit_SUITE.erl
index df754e46c5..5940fd3857 100644
--- a/lib/eunit/test/eunit_SUITE.erl
+++ b/lib/eunit/test/eunit_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
app_test/1,appup_test/1,eunit_test/1,surefire_utf8_test/1,surefire_latin_test/1,
- surefire_c0_test/1, surefire_ensure_dir_test/1]).
+ surefire_c0_test/1, surefire_ensure_dir_test/1,
+ stacktrace_at_timeout_test/1]).
-include_lib("common_test/include/ct.hrl").
@@ -30,7 +31,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[app_test, appup_test, eunit_test, surefire_utf8_test, surefire_latin_test,
- surefire_c0_test, surefire_ensure_dir_test].
+ surefire_c0_test, surefire_ensure_dir_test, stacktrace_at_timeout_test].
groups() ->
[].
@@ -81,6 +82,16 @@ surefire_ensure_dir_test(Config) when is_list(Config) ->
ok = eunit:test(tc0, [{report,{eunit_surefire,[{dir,XMLDir}]}}]),
ok = file:del_dir_r(XMLDir).
+stacktrace_at_timeout_test(Config) when is_list(Config) ->
+ Chars = check_surefire(ttimesout),
+ case string:find(Chars, "in call from") of
+ nomatch ->
+ ct:pal("Surefire XML:~n~ts", [Chars]),
+ ct:fail(missing_stacktrace_in_surefire);
+ _ ->
+ ok
+ end.
+
check_surefire(Module) ->
File = "TEST-"++atom_to_list(Module)++".xml",
file:delete(File),
diff --git a/lib/eunit/test/ttimesout.erl b/lib/eunit/test/ttimesout.erl
new file mode 100644
index 0000000000..91f6be847d
--- /dev/null
+++ b/lib/eunit/test/ttimesout.erl
@@ -0,0 +1,6 @@
+-module(ttimesout).
+
+-include_lib("eunit/include/eunit.hrl").
+
+times_out_test_() ->
+ {timeout, 1, fun() -> timer:sleep(20_000) end}.
--
2.31.1