📄 cover.erl
字号:
%% ``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 via the world wide web 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.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %% $Id$%%-module(cover).%%%% This module implements the Erlang coverage tool. The module named%% cover_web implements a user interface for the coverage tool to run%% under webtool.%% %% ARCHITECTURE%% The coverage tool consists of one process on each node involved in%% coverage analysis. The process is registered as 'cover_server'%% (?SERVER). All cover_servers in the distributed system are linked%% together. The cover_server on the 'main' node is in charge, and it%% traps exits so it can detect nodedown or process crashes on the%% remote nodes. This process is implemented by the functions%% init_main/1 and main_process_loop/1. The cover_server on the remote%% nodes are implemented by the functions init_remote/2 and%% remote_process_loop/1.%%%% TABLES%% Each nodes has an ets table named 'cover_internal_data_table'%% (?COVER_TABLE). This table contains the coverage data and is%% continously updated when cover compiled code is executed.%% %% The main node owns a table named%% 'cover_collected_remote_data_table' (?COLLECTION_TABLE). This table%% contains data which is collected from remote nodes (either when a%% remote node is stopped with cover:stop/1 or when analysing. When%% analysing, data is even moved from the ?COVER_TABLE on the main%% node to the ?COLLECTION_TABLE.%%%% The main node also has a table named 'cover_binary_code_table'%% (?BINARY_TABLE). This table contains the binary code for each cover%% compiled module. This is necessary so that the code can be loaded%% on remote nodes that are started after the compilation.%%%% External exports-export([start/0, start/1, compile/1, compile/2, compile_module/1, compile_module/2, compile_directory/0, compile_directory/1, compile_directory/2, compile_beam/1, compile_beam_directory/0, compile_beam_directory/1, analyse/1, analyse/2, analyse/3, analyze/1, analyze/2, analyze/3, analyse_to_file/1, analyse_to_file/2, analyse_to_file/3, analyze_to_file/1, analyze_to_file/2, analyze_to_file/3, export/1, export/2, import/1, modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1, reset/1, reset/0, stop/0, stop/1]).-export([remote_start/1]).%-export([bump/5]).-export([transform/4]). % for test purposes-record(main_state, {compiled=[], % [{Module,File}] imported=[], % [{Module,File,ImportFile}] stopper, % undefined | pid() nodes=[]}). % [Node]-record(remote_state, {compiled=[], % [{Module,File}] main_node}). % atom()-record(bump, {module = '_', % atom() function = '_', % atom() arity = '_', % integer() clause = '_', % integer() line = '_' % integer() }).-define(BUMP_REC_NAME,bump).-record(vars, {module, % atom() Module name vsn, % atom() init_info=[], % [{M,F,A,C,L}] function, % atom() arity, % int() clause, % int() lines, % [int()] depth, % int() is_guard=false % boolean }).-define(COVER_TABLE, 'cover_internal_data_table').-define(BINARY_TABLE, 'cover_binary_code_table').-define(COLLECTION_TABLE, 'cover_collected_remote_data_table').-define(TAG, cover_compiled).-define(SERVER, cover_server).-include_lib("stdlib/include/ms_transform.hrl").%%%----------------------------------------------------------------------%%% External exports%%%----------------------------------------------------------------------%% start() -> {ok,Pid} | {error,Reason}%% Pid = pid()%% Reason = {already_started,Pid} | term()start() -> case whereis(?SERVER) of undefined -> Starter = self(), Pid = spawn(fun() -> init_main(Starter) end), Ref = erlang:monitor(process,Pid), Return = receive {?SERVER,started} -> {ok,Pid}; {'DOWN', Ref, _Type, _Object, Info} -> {error,Info} end, erlang:demonitor(Ref), Return; Pid -> {error,{already_started,Pid}} end.%% start(Nodes) -> {ok,StartedNodes}%% Nodes = Node | [Node,...]%% Node = atom()start(Node) when is_atom(Node) -> start([Node]);start(Nodes) -> call({start_nodes,remove_myself(Nodes,[])}).%% compile(ModFile) ->%% compile(ModFile, Options) ->%% compile_module(ModFile) -> Result%% compile_module(ModFile, Options) -> Result%% ModFile = Module | File%% Module = atom()%% File = string()%% Options = [Option]%% Option = {i,Dir} | {d,Macro} | {d,Macro,Value}%% Result = {ok,Module} | {error,File}compile(ModFile) -> compile_module(ModFile, []).compile(ModFile, Options) -> compile_module(ModFile, Options).compile_module(ModFile) when is_atom(ModFile); is_list(ModFile) -> compile_module(ModFile, []).compile_module(Module, Options) when is_atom(Module), is_list(Options) -> compile_module(atom_to_list(Module), Options);compile_module(File, Options) when is_list(File), is_list(Options) -> WithExt = case filename:extension(File) of ".erl" -> File; _ -> File++".erl" end, AbsFile = filename:absname(WithExt), [R] = compile_modules([AbsFile], Options), R.%% compile_directory() ->%% compile_directory(Dir) ->%% compile_directory(Dir, Options) -> [Result] | {error,Reason}%% Dir = string()%% Options - see compile/1%% Result - see compile/1%% Reason = eacces | enoentcompile_directory() -> case file:get_cwd() of {ok, Dir} -> compile_directory(Dir, []); Error -> Error end.compile_directory(Dir) when is_list(Dir) -> compile_directory(Dir, []).compile_directory(Dir, Options) when is_list(Dir), is_list(Options) -> case file:list_dir(Dir) of {ok, Files} -> %% Filter out all erl files (except cover.erl) ErlFileNames = lists:filter(fun("cover.erl") -> false; (File) -> case filename:extension(File) of ".erl" -> true; _ -> false end end, Files), %% Create a list of .erl file names (incl path) and call %% compile_modules/2 with the list of file names. ErlFiles = lists:map(fun(ErlFileName) -> filename:join(Dir, ErlFileName) end, ErlFileNames), compile_modules(ErlFiles, Options); Error -> Error end.compile_modules(Files,Options) -> Options2 = lists:filter(fun(Option) -> case Option of {i, Dir} when is_list(Dir) -> true; {d, _Macro} -> true; {d, _Macro, _Value} -> true; _ -> false end end, Options), compile_modules(Files,Options2,[]).compile_modules([File|Files], Options, Result) -> R = call({compile, File, Options}), compile_modules(Files,Options,[R|Result]);compile_modules([],_Opts,Result) -> reverse(Result).%% compile_beam(ModFile) -> Result | {error,Reason}%% ModFile - see compile/1%% Result - see compile/1%% Reason = non_existing | already_cover_compiledcompile_beam(Module) when is_atom(Module) -> case code:which(Module) of non_existing -> {error,non_existing}; ?TAG -> compile_beam(Module,?TAG); File -> compile_beam(Module,File) end;compile_beam(File) when is_list(File) -> {WithExt,WithoutExt} = case filename:rootname(File,".beam") of File -> {File++".beam",File}; Rootname -> {File,Rootname} end, AbsFile = filename:absname(WithExt), Module = list_to_atom(filename:basename(WithoutExt)), compile_beam(Module,AbsFile).compile_beam(Module,File) -> call({compile_beam,Module,File}). %% compile_beam_directory(Dir) -> [Result] | {error,Reason}%% Dir - see compile_directory/1%% Result - see compile/1%% Reason = eacces | enoentcompile_beam_directory() -> case file:get_cwd() of {ok, Dir} -> compile_beam_directory(Dir); Error -> Error end.compile_beam_directory(Dir) when is_list(Dir) -> case file:list_dir(Dir) of {ok, Files} -> %% Filter out all beam files (except cover.beam) BeamFileNames = lists:filter(fun("cover.beam") -> false; (File) -> case filename:extension(File) of ".beam" -> true; _ -> false end end, Files), %% Create a list of .beam file names (incl path) and call %% compile_beam/1 for each such file name BeamFiles = lists:map(fun(BeamFileName) -> filename:join(Dir, BeamFileName) end, BeamFileNames), compile_beams(BeamFiles); Error -> Error end.compile_beams(Files) -> compile_beams(Files,[]).compile_beams([File|Files],Result) -> R = compile_beam(File), compile_beams(Files,[R|Result]);compile_beams([],Result) -> reverse(Result).%% analyse(Module) ->%% analyse(Module, Analysis) ->%% analyse(Module, Level) ->%% analyse(Module, Analysis, Level) -> {ok,Answer} | {error,Error}%% Module = atom()%% Analysis = coverage | calls%% Level = line | clause | function | module%% Answer = {Module,Value} | [{Item,Value}]%% Item = Line | Clause | Function%% Line = {M,N}%% Clause = {M,F,A,C}%% Function = {M,F,A}%% M = F = atom()%% N = A = C = integer()%% Value = {Cov,NotCov} | Calls%% Cov = NotCov = Calls = integer()%% Error = {not_cover_compiled,Module}analyse(Module) -> analyse(Module, coverage).analyse(Module, Analysis) when Analysis=:=coverage; Analysis=:=calls -> analyse(Module, Analysis, function);analyse(Module, Level) when Level=:=line; Level=:=clause; Level=:=function; Level=:=module -> analyse(Module, coverage, Level).analyse(Module, Analysis, Level) when is_atom(Module), Analysis=:=coverage; Analysis=:=calls, Level=:=line; Level=:=clause; Level=:=function; Level=:=module -> call({{analyse, Analysis, Level}, Module}).analyze(Module) -> analyse(Module).analyze(Module, Analysis) -> analyse(Module, Analysis).analyze(Module, Analysis, Level) -> analyse(Module, Analysis, Level).%% analyse_to_file(Module) ->%% analyse_to_file(Module, Options) ->%% analyse_to_file(Module, OutFile) ->%% analyse_to_file(Module, OutFile, Options) -> {ok,OutFile} | {error,Error}%% Module = atom()%% OutFile = string()%% Options = [Option]%% Option = html%% Error = {not_cover_compiled,Module} | no_source_code_found |%% {file,File,Reason}%% File = string()%% Reason = term()analyse_to_file(Module) when is_atom(Module) -> analyse_to_file(Module, outfilename(Module,[]), []).analyse_to_file(Module, []) when is_atom(Module) -> analyse_to_file(Module, outfilename(Module,[]), []);analyse_to_file(Module, Options) when is_atom(Module), is_list(Options), is_atom(hd(Options)) -> analyse_to_file(Module, outfilename(Module,Options), Options);analyse_to_file(Module, OutFile) when is_atom(Module), is_list(OutFile) -> analyse_to_file(Module, OutFile, []).analyse_to_file(Module, OutFile, Options) when is_atom(Module), is_list(OutFile) -> call({{analyse_to_file, OutFile, Options}, Module}).analyze_to_file(Module) -> analyse_to_file(Module).analyze_to_file(Module, OptOrOut) -> analyse_to_file(Module, OptOrOut).analyze_to_file(Module, OutFile, Options) -> analyse_to_file(Module, OutFile, Options).outfilename(Module,Opts) -> case lists:member(html,Opts) of true -> atom_to_list(Module)++".COVER.html"; false -> atom_to_list(Module)++".COVER.out" end.%% export(File)%% export(File,Module) -> ok | {error,Reason}%% File = string(); file to write the exported data to%% Module = atom()export(File) -> export(File, '_').export(File, Module) -> call({export,File,Module}).%% import(File) -> ok | {error, Reason}%% File = string(); file created with cover:export/1,2import(File) -> call({import,File}).%% modules() -> [Module]%% Module = atom()modules() -> call(modules).%% imported_modules() -> [Module]%% Module = atom()imported_modules() -> call(imported_modules).%% imported() -> [ImportFile]%% ImportFile = string()imported() -> call(imported).%% which_nodes() -> [Node]%% Node = atom()which_nodes() -> call(which_nodes).%% is_compiled(Module) -> {file,File} | false%% Module = atom()%% File = string()is_compiled(Module) when is_atom(Module) -> call({is_compiled, Module}).%% reset(Module) -> ok | {error,Error}%% reset() -> ok%% Module = atom()%% Error = {not_cover_compiled,Module}reset(Module) when is_atom(Module) -> call({reset, Module}).reset() -> call(reset).%% stop() -> okstop() -> call(stop).stop(Node) when is_atom(Node) -> stop([Node]);stop(Nodes) -> call({stop,remove_myself(Nodes,[])}).%% bump(Module, Function, Arity, Clause, Line)%% Module = Function = atom()%% Arity = Clause = Line = integer()%% This function is inserted into Cover compiled modules, once for each%% executable line.%bump(Module, Function, Arity, Clause, Line) ->% Key = #bump{module=Module, function=Function, arity=Arity, clause=Clause,% line=Line},% ets:update_counter(?COVER_TABLE, Key, 1).call(Request) -> Ref = erlang:monitor(process,?SERVER), receive {'DOWN', Ref, _Type, _Object, noproc} -> erlang:demonitor(Ref), start(), call(Request) after 0 -> ?SERVER ! {self(),Request}, Return = receive {'DOWN', Ref, _Type, _Object, Info} -> exit(Info); {?SERVER,Reply} -> Reply end, erlang:demonitor(Ref), Return end.reply(From, Reply) -> From ! {?SERVER,Reply}.is_from(From) -> is_pid(From).remote_call(Node,Request) -> Ref = erlang:monitor(process,{?SERVER,Node}), receive {'DOWN', Ref, _Type, _Object, noproc} -> erlang:demonitor(Ref), {error,node_dead}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -