feat: add new module emqx_cover.erl
This commit is contained in:
parent
43aab61a3a
commit
ba65cf48c3
|
@ -0,0 +1,214 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% 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.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% @doc This module is NOT used in production.
|
||||||
|
%% It is used to collect coverage data when running blackbox test
|
||||||
|
-module(emqx_cover).
|
||||||
|
|
||||||
|
-include_lib("covertool/include/covertool.hrl").
|
||||||
|
|
||||||
|
-ifdef(EMQX_ENTERPRISE).
|
||||||
|
-define(OUTPUT_APPNAME, 'EMQX Enterprise').
|
||||||
|
-else.
|
||||||
|
-define(OUTPUT_APPNAME, 'EMQX').
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
-export([
|
||||||
|
start/0,
|
||||||
|
start/1,
|
||||||
|
abort/0,
|
||||||
|
export_and_stop/1,
|
||||||
|
lookup_source/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% This is a ETS table to keep a mapping of module name (atom) to
|
||||||
|
%% .erl file path (relative path from project root)
|
||||||
|
%% We needed this ETS table because the source file information
|
||||||
|
%% is missing from the .beam metadata sicne we are using 'deterministic'
|
||||||
|
%% compile flag.
|
||||||
|
-define(SRC, emqx_cover_module_src).
|
||||||
|
|
||||||
|
%% @doc Start cover.
|
||||||
|
%% All emqx_ modules will be cover-compiled, this may cause
|
||||||
|
%% some excessive RAM consumption and result in warning logs.
|
||||||
|
start() ->
|
||||||
|
start(#{}).
|
||||||
|
|
||||||
|
%% @doc Start cover.
|
||||||
|
%% All emqx_ modules will be cover-compiled, this may cause
|
||||||
|
%% some excessive RAM consumption and result in warning logs.
|
||||||
|
%% Supported options:
|
||||||
|
%% - project_root: the directory to search for .erl source code
|
||||||
|
%% - debug_secret_file: only applicable to EMQX Enterprise
|
||||||
|
start(Opts) ->
|
||||||
|
ok = abort(),
|
||||||
|
DefaultDir = os_env("EMQX_PROJECT_ROOT"),
|
||||||
|
ProjRoot = maps:get(project_root, Opts, DefaultDir),
|
||||||
|
case ProjRoot =:= "" of
|
||||||
|
true ->
|
||||||
|
io:format("Project source code root dir is not provided.~n"),
|
||||||
|
io:format(
|
||||||
|
"You may either start EMQX node with environment variable EMQX_PROJECT_ROOT set~n"
|
||||||
|
),
|
||||||
|
io:format("Or provide #{project_root => \"/path/to/emqx/\"} as emqx_cover:start arg~n"),
|
||||||
|
exit(project_root_is_not_set);
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
%% spawn a ets table owner
|
||||||
|
%% this implementation is kept dead-simple
|
||||||
|
%% because there is no concurrency requirement
|
||||||
|
Parent = self(),
|
||||||
|
{Pid, Ref} =
|
||||||
|
erlang:spawn_monitor(
|
||||||
|
fun() ->
|
||||||
|
true = register(?SRC, self()),
|
||||||
|
_ = ets:new(?SRC, [named_table, public]),
|
||||||
|
_ = Parent ! {started, self()},
|
||||||
|
receive
|
||||||
|
stop ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
),
|
||||||
|
receive
|
||||||
|
{started, Pid} ->
|
||||||
|
ok;
|
||||||
|
{'DOWN', Ref, process, Pid, Reason} ->
|
||||||
|
throw({failed_to_start, Reason})
|
||||||
|
after 1000 ->
|
||||||
|
throw({failed_to_start, timeout})
|
||||||
|
end,
|
||||||
|
Modules = modules(Opts),
|
||||||
|
case cover:start() of
|
||||||
|
{ok, _Pid} ->
|
||||||
|
ok;
|
||||||
|
{error, {already_started, _Pid}} ->
|
||||||
|
ok;
|
||||||
|
Other ->
|
||||||
|
throw(Other)
|
||||||
|
end,
|
||||||
|
ok = cover_compile(Modules),
|
||||||
|
io:format("cover-compiled ~p modules~n", [length(Modules)]),
|
||||||
|
ok = put_project_root(ProjRoot),
|
||||||
|
ok = do_build_source_mapping(ProjRoot, Modules),
|
||||||
|
CachedModulesCount = ets:info(?SRC, size),
|
||||||
|
io:format("source-cached ~p modules~n", [CachedModulesCount]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%% @doc Abort cover data collection without exporting.
|
||||||
|
abort() ->
|
||||||
|
_ = cover:stop(),
|
||||||
|
case whereis(?SRC) of
|
||||||
|
undefined ->
|
||||||
|
ok;
|
||||||
|
Pid ->
|
||||||
|
Ref = monitor(process, Pid),
|
||||||
|
exit(Pid, kill),
|
||||||
|
receive
|
||||||
|
{'DOWN', Ref, process, Pid, _} ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%% @doc Export coverage report (xml) format.
|
||||||
|
%% e.g. `emqx_cover:export_and_stop("/tmp/cover.xml").'
|
||||||
|
export_and_stop(Path) when is_list(Path) ->
|
||||||
|
ProjectRoot = get_project_root(),
|
||||||
|
Config = #config{
|
||||||
|
appname = ?OUTPUT_APPNAME,
|
||||||
|
sources = [ProjectRoot],
|
||||||
|
output = Path,
|
||||||
|
lookup_source = fun ?MODULE:lookup_source/1
|
||||||
|
},
|
||||||
|
covertool:generate_report(Config, cover:modules()).
|
||||||
|
|
||||||
|
get_project_root() ->
|
||||||
|
[{_, Dir}] = ets:lookup(?SRC, {root, ?OUTPUT_APPNAME}),
|
||||||
|
Dir.
|
||||||
|
|
||||||
|
put_project_root(Dir) ->
|
||||||
|
_ = ets:insert(?SRC, {{root, ?OUTPUT_APPNAME}, Dir}),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
do_build_source_mapping(Dir, Modules0) ->
|
||||||
|
Modules = sets:from_list(Modules0, [{version, 2}]),
|
||||||
|
All = filelib:wildcard("**/*.erl", Dir),
|
||||||
|
lists:foreach(
|
||||||
|
fun(Path) ->
|
||||||
|
ModuleNameStr = filename:basename(Path, ".erl"),
|
||||||
|
Module = list_to_atom(ModuleNameStr),
|
||||||
|
case sets:is_element(Module, Modules) of
|
||||||
|
true ->
|
||||||
|
ets:insert(?SRC, {Module, Path});
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
All
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
lookup_source(Module) ->
|
||||||
|
case ets:lookup(?SRC, Module) of
|
||||||
|
[{_, Path}] ->
|
||||||
|
Path;
|
||||||
|
[] ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
modules(_Opts) ->
|
||||||
|
%% TODO better filter based on Opts,
|
||||||
|
%% e.g. we may want to see coverage info for ehttpc
|
||||||
|
Filter = fun is_emqx_module/1,
|
||||||
|
find_modules(Filter).
|
||||||
|
|
||||||
|
cover_compile(Modules) ->
|
||||||
|
Results = cover:compile_beam(Modules),
|
||||||
|
Errors = lists:filter(
|
||||||
|
fun
|
||||||
|
({ok, _}) -> false;
|
||||||
|
(_) -> true
|
||||||
|
end,
|
||||||
|
Results
|
||||||
|
),
|
||||||
|
case Errors of
|
||||||
|
[] ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
io:format("failed_to_cover_compile:~n~p~n", [Errors]),
|
||||||
|
throw(failed_to_cover_compile)
|
||||||
|
end.
|
||||||
|
|
||||||
|
find_modules(Filter) ->
|
||||||
|
All = code:all_loaded(),
|
||||||
|
F = fun({M, _BeamPath}) -> Filter(M) andalso {true, M} end,
|
||||||
|
lists:filtermap(F, All).
|
||||||
|
|
||||||
|
is_emqx_module(?MODULE) ->
|
||||||
|
%% do not cover-compile self
|
||||||
|
false;
|
||||||
|
is_emqx_module(Module) ->
|
||||||
|
case erlang:atom_to_binary(Module, utf8) of
|
||||||
|
<<"emqx", _/binary>> ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
os_env(Name) ->
|
||||||
|
os:getenv(Name, "").
|
|
@ -3,7 +3,7 @@
|
||||||
{id, "emqx_machine"},
|
{id, "emqx_machine"},
|
||||||
{description, "The EMQX Machine"},
|
{description, "The EMQX Machine"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "0.1.3"},
|
{vsn, "0.2.0"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel, stdlib]},
|
{applications, [kernel, stdlib]},
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add two new Erlang apps 'tools' and 'covertool' to the release.
|
||||||
|
So we can run profiling and test coverage analysis on release packages.
|
|
@ -0,0 +1,2 @@
|
||||||
|
在发布包中增加了2个新的 Erlang app,分别是 ‘tools’ 和 ‘covertool’。
|
||||||
|
这两个 app 可以用于性能和测试覆盖率的分析。
|
3
mix.exs
3
mix.exs
|
@ -46,6 +46,7 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
[
|
[
|
||||||
{:lc, github: "emqx/lc", tag: "0.3.2", override: true},
|
{:lc, github: "emqx/lc", tag: "0.3.2", override: true},
|
||||||
{:redbug, "2.0.8"},
|
{:redbug, "2.0.8"},
|
||||||
|
{:covertool, github: "zmstone/covertool", tag: "2.0.4.1", override: true},
|
||||||
{:typerefl, github: "ieQu1/typerefl", tag: "0.9.1", override: true},
|
{:typerefl, github: "ieQu1/typerefl", tag: "0.9.1", override: true},
|
||||||
{:ehttpc, github: "emqx/ehttpc", tag: "0.4.6", override: true},
|
{:ehttpc, github: "emqx/ehttpc", tag: "0.4.6", override: true},
|
||||||
{:gproc, github: "uwiger/gproc", tag: "0.8.0", override: true},
|
{:gproc, github: "uwiger/gproc", tag: "0.8.0", override: true},
|
||||||
|
@ -222,6 +223,8 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
emqx_plugin_libs: :load,
|
emqx_plugin_libs: :load,
|
||||||
esasl: :load,
|
esasl: :load,
|
||||||
observer_cli: :permanent,
|
observer_cli: :permanent,
|
||||||
|
tools: :load,
|
||||||
|
covertool: :load,
|
||||||
system_monitor: :load,
|
system_monitor: :load,
|
||||||
emqx_http_lib: :permanent,
|
emqx_http_lib: :permanent,
|
||||||
emqx_resource: :permanent,
|
emqx_resource: :permanent,
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
{deps,
|
{deps,
|
||||||
[ {lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}}
|
[ {lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}}
|
||||||
, {redbug, "2.0.8"}
|
, {redbug, "2.0.8"}
|
||||||
|
, {covertool, {git, "https://github.com/zmstone/covertool", {tag, "2.0.4.1"}}}
|
||||||
, {gpb, "4.19.5"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
|
, {gpb, "4.19.5"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
|
||||||
, {typerefl, {git, "https://github.com/ieQu1/typerefl", {tag, "0.9.1"}}}
|
, {typerefl, {git, "https://github.com/ieQu1/typerefl", {tag, "0.9.1"}}}
|
||||||
, {gun, {git, "https://github.com/emqx/gun", {tag, "1.3.9"}}}
|
, {gun, {git, "https://github.com/emqx/gun", {tag, "1.3.9"}}}
|
||||||
|
|
|
@ -378,6 +378,8 @@ relx_apps(ReleaseType, Edition) ->
|
||||||
{emqx_plugin_libs, load},
|
{emqx_plugin_libs, load},
|
||||||
{esasl, load},
|
{esasl, load},
|
||||||
observer_cli,
|
observer_cli,
|
||||||
|
{tools, load},
|
||||||
|
{covertool, load},
|
||||||
% started by emqx_machine
|
% started by emqx_machine
|
||||||
{system_monitor, load},
|
{system_monitor, load},
|
||||||
emqx_http_lib,
|
emqx_http_lib,
|
||||||
|
|
Loading…
Reference in New Issue