File 2502-cover-Gracefully-handle-failures-to-collect-coverage.patch of Package erlang
From 426c582fbd5b3eab073a32b621f9a9ecd2463de1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org>
Date: Thu, 1 Aug 2024 15:40:46 +0200
Subject: [PATCH] cover: Gracefully handle failures to collect coverage data
On platforms that support native coverage (introduced in
Erlang/OTP 27), the `cover` module did not properly handle failed
calls to `code:get_coverage/2`. The call to that function will fail
if the module in question has been reloaded or unloaded.
For example, if `cover:export/1` was called and a module had been
reloaded on a remote node (for example by `meck`), the
`cover:export/1` call would never return.
Because the current API for `cover` has no good way to report this
kind of error, we will fix the issue by logging the failed call using
the `logger` module and otherwise ignore the failure. That implies
that there will be no coverage information for modules that have been
reloaded. That is compatible with the behavior of `cover` prior to
Erlang/OTP 27.
Closes #8691
---
lib/tools/src/cover.erl | 28 ++++++++++++++++++++++++++--
lib/tools/test/cover_SUITE.erl | 26 +++++++++++++++++++++++++-
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index 8827a10647..fc48997195 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -2364,8 +2364,17 @@ standard_move(Mod) ->
end.
native_move(Mod) ->
- Coverage = maps:from_list(code:get_coverage(cover_id_line, Mod)),
- _ = code:reset_coverage(Mod),
+ Coverage0 =
+ try
+ code:get_coverage(cover_id_line, Mod)
+ catch
+ error:badarg ->
+ log_native_move_error(Mod),
+ []
+ end,
+ _ = catch code:reset_coverage(Mod),
+ Coverage = maps:from_list(Coverage0),
+
fun({#bump{}=Key,Index}) ->
case Coverage of
#{Index := false} ->
@@ -2379,6 +2388,21 @@ native_move(Mod) ->
end
end.
+log_native_move_error(Mod) ->
+ S = "Module ~tp: Failed to collect coverage information. "
+ "Has it been reloaded or unloaded?",
+ F = fun(#{node := Node}) ->
+ case Node of
+ nonode@nohost ->
+ {S,[Mod]};
+ _ ->
+ {"On node ~tp: " ++ S,[Node,Mod]}
+ end
+ end,
+ logger:warning(#{coverage_collection_failed => Mod,
+ node => node()},
+ #{report_cb => F}).
+
%% Reset counters (set counters to 0).
reset_counters(Mod) ->
case has_native_coverage() of
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl
index 489c35d24d..d9e77cd651 100644
--- a/lib/tools/test/cover_SUITE.erl
+++ b/lib/tools/test/cover_SUITE.erl
@@ -35,7 +35,7 @@ all() ->
otp_13277, otp_13289, guard_in_lc, gh_4796,
eep49, gh_8159],
StartStop = [start, compile, analyse, misc, stop,
- distribution, reconnect, die_and_reconnect,
+ distribution, distribution_export, reconnect, die_and_reconnect,
dont_reconnect_after_stop, stop_node_after_disconnect,
export_import, otp_5031, otp_6115,
otp_8270, otp_10979_hanging_node, otp_14817,
@@ -539,6 +539,30 @@ distribution(Config) when is_list(Config) ->
peer:stop(P1),
peer:stop(P2).
+%% GH-8661. An attempt to export cover data on a remote node could
+%% hang if the module had been reloaded.
+distribution_export(Config) when is_list(Config) ->
+ ct:timetrap({seconds, 30}),
+
+ DataDir = proplists:get_value(data_dir, Config),
+
+ ok = file:set_cwd(DataDir),
+
+ {ok,P1,N1} = ?CT_PEER(),
+
+ {ok,f} = cover:compile(f),
+ {ok,[_]} = cover:start([N1]),
+ ok = cover:export("f.coverdata"),
+
+ {ok,f} = compile:file(f, [debug_info]),
+ {module, f} = erpc:call(N1, code, load_file, [f]),
+
+ ok = cover:export("f.coverdata"),
+
+ %% Cleanup
+ peer:stop(P1),
+ ok.
+
%% Test that a lost node is reconnected
reconnect(Config) ->
DataDir = proplists:get_value(data_dir, Config),
--
2.43.0