diff options
Diffstat (limited to 'lib/common_test/src/ct_cover.erl')
-rw-r--r-- | lib/common_test/src/ct_cover.erl | 368 |
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([]) -> + []. |