aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src/ct_cover.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src/ct_cover.erl')
-rw-r--r--lib/common_test/src/ct_cover.erl368
1 files changed, 368 insertions, 0 deletions
diff --git a/lib/common_test/src/ct_cover.erl b/lib/common_test/src/ct_cover.erl
new file mode 100644
index 0000000000..d39f50ba00
--- /dev/null
+++ b/lib/common_test/src/ct_cover.erl
@@ -0,0 +1,368 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. 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.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% @doc Common Test Framework code coverage support module.
+%%%
+%%% <p>This module exports help functions for performing code
+%%% coverage analysis.</p>
+
+-module(ct_cover).
+
+-export([get_spec/1, add_nodes/1, remove_nodes/1]).
+
+-include("ct_util.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+%%%-----------------------------------------------------------------
+%%% @spec add_nodes(Nodes) -> {ok,StartedNodes} | {error,Reason}
+%%% Nodes = [atom()]
+%%% StartedNodes = [atom()]
+%%% Reason = cover_not_running | not_main_node
+%%%
+%%% @doc Add nodes to current cover test (only works if cover support
+%%% is active!). To have effect, this function should be called
+%%% from init_per_suite/1 before any actual tests are performed.
+%%%
+add_nodes([]) ->
+ {ok,[]};
+add_nodes(Nodes) ->
+ case whereis(cover_server) of
+ undefined ->
+ {error,cover_not_running};
+ _ ->
+ {File,Nodes0,Import,Export,AppInfo} = ct_util:get_testdata(cover),
+ Nodes1 = [Node || Node <- Nodes,
+ lists:member(Node,Nodes0) == false],
+ ct_logs:log("COVER INFO",
+ "Adding nodes to cover test: ~w", [Nodes1]),
+ case cover:start(Nodes1) of
+ Result = {ok,_} ->
+ ct_util:set_testdata({cover,{File,Nodes1++Nodes0,
+ Import,Export,AppInfo}}),
+
+ Result;
+ Error ->
+ Error
+ end
+ end.
+
+
+%%%-----------------------------------------------------------------
+%%% @spec remove_nodes(Nodes) -> ok | {error,Reason}
+%%% Nodes = [atom()]
+%%% Reason = cover_not_running | not_main_node
+%%%
+%%% @doc Remove nodes from current cover test. Call this function
+%%% to stop cover test on nodes previously added with add_nodes/1.
+%%% Results on the remote node are transferred to the Common Test
+%%% node.
+%%%
+remove_nodes([]) ->
+ ok;
+remove_nodes(Nodes) ->
+ case whereis(cover_server) of
+ undefined ->
+ {error,cover_not_running};
+ _ ->
+ {File,Nodes0,Import,Export,AppInfo} = ct_util:get_testdata(cover),
+ ToRemove = [Node || Node <- Nodes, lists:member(Node,Nodes0)],
+ ct_logs:log("COVER INFO",
+ "Removing nodes from cover test: ~w", [ToRemove]),
+ case cover:stop(ToRemove) of
+ ok ->
+ Nodes1 = lists:foldl(fun(N,Deleted) ->
+ lists:delete(N,Deleted)
+ end, Nodes0, ToRemove),
+ ct_util:set_testdata({cover,{File,Nodes1,
+ Import,Export,AppInfo}}),
+ ok;
+ Error ->
+ Error
+ end
+ end.
+
+
+%%%-----------------------------------------------------------------
+%%% @hidden
+
+%% Read cover specification file and return the parsed info.
+%% -> CoverSpec: {CoverFile,Nodes,Import,Export,AppCoverInfo}
+get_spec(File) ->
+ catch get_spec_test(File).
+
+get_spec_test(File) ->
+ FullName = filename:absname(File),
+ case filelib:is_file(FullName) of
+ true ->
+ case file:consult(FullName) of
+ {ok,Terms} ->
+ Import =
+ case lists:keysearch(import, 1, Terms) of
+ {value,{_,Imps=[S|_]}} when is_list(S) ->
+ ImpsFN = lists:map(fun(F) ->
+ filename:absname(F)
+ end, Imps),
+ test_files(ImpsFN, ImpsFN);
+ {value,{_,Imp=[IC|_]}} when is_integer(IC) ->
+ ImpFN = filename:absname(Imp),
+ test_files([ImpFN], [ImpFN]);
+ _ ->
+ []
+ end,
+ Export =
+ case lists:keysearch(export, 1, Terms) of
+ {value,{_,Exp=[EC|_]}} when is_integer(EC) ->
+ filename:absname(Exp);
+ {value,{_,[Exp]}} ->
+ filename:absname(Exp);
+ _ ->
+ []
+ end,
+ Nodes =
+ case lists:keysearch(nodes, 1, Terms) of
+ {value,{_,Ns}} ->
+ Ns;
+ _ ->
+ []
+ end,
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% NOTE! We can read specifications with multiple %%
+ %% apps, but since we don't have support for %%
+ %% running cover on more than one app at a time, %%
+ %% we just allow 1 app per spec for now. %%
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ case collect_apps(Terms, []) of
+ Res when Res == [] ; length(Res) == 1 -> % 1 app = ok
+ Apps = case Res of
+ [] -> [#cover{app=none, level=details}];
+ _ -> Res
+ end,
+ case get_cover_opts(Apps, Terms, []) of
+ E = {error,_} ->
+ E;
+ [CoverSpec] ->
+ CoverSpec1 = remove_excludes_and_dups(CoverSpec),
+ {FullName,Nodes,Import,Export,CoverSpec1};
+ _ ->
+ {error,multiple_apps_in_cover_spec}
+ end;
+ Apps when is_list(Apps) ->
+ {error,multiple_apps_in_cover_spec}
+ end;
+ Error -> % file:consult/1 fails
+ {error,{invalid_cover_spec,Error}}
+ end;
+ false ->
+ {error,{cant_read_cover_spec_file,FullName}}
+ end.
+
+collect_apps([{level,Level}|Ts], Apps) ->
+ collect_apps(Ts, [#cover{app=none, level=Level}|Apps]);
+collect_apps([{incl_app,App,Level}|Ts], Apps) ->
+ collect_apps(Ts, [#cover{app=App, level=Level}|Apps]);
+collect_apps([_|Ts], Apps) ->
+ collect_apps(Ts, Apps);
+collect_apps([], Apps) ->
+ Apps.
+
+%% get_cover_opts(Terms) -> AppCoverInfo
+%% AppCoverInfo: [#cover{app=App,...}]
+
+get_cover_opts([App | Apps], Terms, CoverInfo) ->
+ case get_app_info(App, Terms) of
+ E = {error,_} -> E;
+ AppInfo ->
+ AppInfo1 = files2mods(AppInfo),
+ get_cover_opts(Apps, Terms, [AppInfo1|CoverInfo])
+ end;
+get_cover_opts([], _, CoverInfo) ->
+ lists:reverse(CoverInfo).
+
+%% get_app_info(App, Terms) -> App1
+
+get_app_info(App=#cover{app=none}, [{incl_dirs,Dirs}|Terms]) ->
+ get_app_info(App, [{incl_dirs,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{incl_dirs,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".beam", false, []) of
+ E = {error,_} -> E;
+ Mods1 ->
+ Mods = App#cover.incl_mods,
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{incl_dirs_r,Dirs}|Terms]) ->
+ get_app_info(App, [{incl_dirs_r,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{incl_dirs_r,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".beam", true, []) of
+ E = {error,_} -> E;
+ Mods1 ->
+ Mods = App#cover.incl_mods,
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{incl_mods,Mods1}|Terms]) ->
+ get_app_info(App, [{incl_mods,none,Mods1}|Terms]);
+get_app_info(App=#cover{app=Name}, [{incl_mods,Name,Mods1}|Terms]) ->
+ Mods = App#cover.incl_mods,
+ get_app_info(App#cover{incl_mods=Mods++Mods1},Terms);
+
+get_app_info(App=#cover{app=none}, [{excl_dirs,Dirs}|Terms]) ->
+ get_app_info(App, [{excl_dirs,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{excl_dirs,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".beam", false, []) of
+ E = {error,_} -> E;
+ Mods1 ->
+ Mods = App#cover.excl_mods,
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{excl_dirs_r,Dirs}|Terms]) ->
+ get_app_info(App, [{excl_dirs_r,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{excl_dirs_r,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".beam", true, []) of
+ E = {error,_} -> E;
+ Mods1 ->
+ Mods = App#cover.excl_mods,
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{excl_mods,Mods1}|Terms]) ->
+ get_app_info(App, [{excl_mods,none,Mods1}|Terms]);
+get_app_info(App=#cover{app=Name}, [{excl_mods,Name,Mods1}|Terms]) ->
+ Mods = App#cover.excl_mods,
+ get_app_info(App#cover{excl_mods=Mods++Mods1},Terms);
+
+get_app_info(App=#cover{app=Name}, [{cross_apps,Name,AppMods1}|Terms]) ->
+ AppMods = App#cover.cross,
+ get_app_info(App#cover{cross=AppMods++AppMods1},Terms);
+
+get_app_info(App=#cover{app=none}, [{src_dirs,Dirs}|Terms]) ->
+ get_app_info(App, [{src_dirs,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{src_dirs,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".erl", false, []) of
+ E = {error,_} -> E;
+ Src1 ->
+ Src = App#cover.src,
+ get_app_info(App#cover{src=Src++Src1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{src_dirs_r,Dirs}|Terms]) ->
+ get_app_info(App, [{src_dirs_r,none,Dirs}|Terms]);
+get_app_info(App=#cover{app=Name}, [{src_dirs_r,Name,Dirs}|Terms]) ->
+ case get_files(Dirs, ".erl", true, []) of
+ E = {error,_} -> E;
+ Src1 ->
+ Src = App#cover.src,
+ get_app_info(App#cover{src=Src++Src1},Terms)
+ end;
+
+get_app_info(App=#cover{app=none}, [{src_files,Src1}|Terms]) ->
+ get_app_info(App, [{src_files,none,Src1}|Terms]);
+get_app_info(App=#cover{app=Name}, [{src_files,Name,Src1}|Terms]) ->
+ Src = App#cover.src,
+ get_app_info(App#cover{src=Src++Src1},Terms);
+
+get_app_info(App, [_|Terms]) ->
+ get_app_info(App, Terms);
+
+get_app_info(App, []) ->
+ App.
+
+%% get_files(...)
+
+get_files([Dir|Dirs], Ext, Recurse, Files) ->
+ case file:list_dir(Dir) of
+ {ok,Entries} ->
+ {SubDirs,Matches} = analyse_files(Entries, Dir, Ext, [], []),
+ if Recurse == false ->
+ get_files(Dirs, Ext, Recurse, Files++Matches);
+ true ->
+ Files1 = get_files(SubDirs, Ext, Recurse, Files++Matches),
+ get_files(Dirs, Ext, Recurse, Files1)
+ end;
+ {error,Reason} ->
+ {error,{Reason,Dir}}
+ end;
+get_files([], _Ext, _R, Files) ->
+ Files.
+
+%% analyse_files(...)
+
+analyse_files([F|Fs], Dir, Ext, Dirs, Matches) ->
+ Fullname = filename:absname(F, Dir),
+ {ok,Info} = file:read_file_info(Fullname),
+ case Info#file_info.type of
+ directory ->
+ analyse_files(Fs, Dir, Ext,
+ [Fullname|Dirs], Matches);
+ _ ->
+ case filename:extension(Fullname) of
+ ".beam" when Ext == ".beam" ->
+ %% File = {file,Dir,filename:rootname(F)},
+ Mod = list_to_atom(filename:rootname(F)),
+ analyse_files(Fs, Dir, Ext, Dirs,
+ [Mod|Matches]);
+ ".erl" when Ext == ".erl" ->
+ analyse_files(Fs, Dir, Ext, Dirs,
+ [Fullname|Matches]);
+ _ ->
+ analyse_files(Fs, Dir, Ext, Dirs, Matches)
+ end
+ end;
+analyse_files([], _Dir, _Ext, Dirs, Matches) ->
+ {Dirs,Matches}.
+
+
+test_files([F|Fs], Ret) ->
+ case filelib:is_file(F) of
+ true ->
+ test_files(Fs, Ret);
+ false ->
+ throw({error,{invalid_cover_file,F}})
+ end;
+test_files([], Ret) ->
+ Ret.
+
+remove_excludes_and_dups(CoverData=#cover{excl_mods=Excl,incl_mods=Incl}) ->
+ Incl1 = [Mod || Mod <- Incl, lists:member(Mod, Excl) == false],
+ %% delete duplicates and sort
+ Incl2 = lists:sort(lists:foldl(fun(M,L) ->
+ case lists:member(M,L) of
+ true -> L;
+ false -> [M|L]
+ end
+ end, [], Incl1)),
+ CoverData#cover{incl_mods=Incl2}.
+
+
+files2mods(Info=#cover{excl_mods=ExclFs,
+ incl_mods=InclFs,
+ cross=CrossFs}) ->
+ Info#cover{excl_mods=files2mods1(ExclFs),
+ incl_mods=files2mods1(InclFs),
+ cross=files2mods1(CrossFs)}.
+
+files2mods1([M|Fs]) when is_atom(M) ->
+ [M|files2mods1(Fs)];
+files2mods1([F|Fs]) when is_list(F) ->
+ M = filename:rootname(filename:basename(F)),
+ [list_to_atom(M)|files2mods1(Fs)];
+files2mods1([]) ->
+ [].