📄 cover.erl
字号:
{{op,Line,Op,MungedExprL,MungedExprR}, Vars3};munge_expr({op,Line,Op,Expr}, Vars) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), {{op,Line,Op,MungedExpr}, Vars2};munge_expr({'catch',Line,Expr}, Vars) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), {{'catch',Line,MungedExpr}, Vars2};munge_expr({call,Line1,{remote,Line2,ExprM,ExprF},Exprs}, Vars) when Vars#vars.is_guard=:=false-> {MungedExprM, Vars2} = munge_expr(ExprM, Vars), {MungedExprF, Vars3} = munge_expr(ExprF, Vars2), {MungedExprs, Vars4} = munge_exprs(Exprs, Vars3, []), {{call,Line1,{remote,Line2,MungedExprM,MungedExprF},MungedExprs}, Vars4};munge_expr({call,Line1,{remote,_Line2,_ExprM,ExprF},Exprs}, Vars) when Vars#vars.is_guard=:=true -> %% Difference in abstract format after preprocessing: BIF calls in guards %% are translated to {remote,...} (which is not allowed as source form) %% NOT NECESSARY FOR Vsn=raw_abstract_v1 munge_expr({call,Line1,ExprF,Exprs}, Vars);munge_expr({call,Line,Expr,Exprs}, Vars) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), {MungedExprs, Vars3} = munge_exprs(Exprs, Vars2, []), {{call,Line,MungedExpr,MungedExprs}, Vars3};munge_expr({lc,Line,Expr,LC}, Vars) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), {MungedLC, Vars3} = munge_lc(LC, Vars2, []), {{lc,Line,MungedExpr,MungedLC}, Vars3};munge_expr({block,Line,Body}, Vars) -> {MungedBody, Vars2} = munge_body(Body, Vars, []), {{block,Line,MungedBody}, Vars2};munge_expr({'if',Line,Clauses}, Vars) -> {MungedClauses,Vars2} = munge_clauses(Clauses, Vars, []), {{'if',Line,MungedClauses}, Vars2};munge_expr({'case',Line,Expr,Clauses}, Vars) -> {MungedExpr,Vars2} = munge_expr(Expr,Vars), {MungedClauses,Vars3} = munge_clauses(Clauses, Vars2, []), {{'case',Line,MungedExpr,MungedClauses}, Vars3};munge_expr({'receive',Line,Clauses}, Vars) -> {MungedClauses,Vars2} = munge_clauses(Clauses, Vars, []), {{'receive',Line,MungedClauses}, Vars2};munge_expr({'receive',Line,Clauses,Expr,Body}, Vars) -> {MungedClauses,Vars2} = munge_clauses(Clauses, Vars, []), {MungedExpr, Vars3} = munge_expr(Expr, Vars2), {MungedBody, Vars4} = munge_body(Body, Vars3, []), {{'receive',Line,MungedClauses,MungedExpr,MungedBody}, Vars4};munge_expr({'try',Line,Exprs,Clauses,CatchClauses,After}, Vars) -> {MungedExprs, Vars1} = munge_exprs(Exprs, Vars, []), {MungedClauses, Vars2} = munge_clauses(Clauses, Vars1, []), {MungedCatchClauses, Vars3} = munge_clauses(CatchClauses, Vars2, []), {MungedAfter, Vars4} = munge_body(After, Vars3, []), {{'try',Line,MungedExprs,MungedClauses,MungedCatchClauses,MungedAfter}, Vars4};%% Difference in abstract format after preprocessing: Funs get an extra%% element Extra.%% NOT NECESSARY FOR Vsn=raw_abstract_v1munge_expr({'fun',Line,{function,Name,Arity},_Extra}, Vars) -> {{'fun',Line,{function,Name,Arity}}, Vars};munge_expr({'fun',Line,{clauses,Clauses},_Extra}, Vars) -> {MungedClauses,Vars2}=munge_clauses(Clauses, Vars, []), {{'fun',Line,{clauses,MungedClauses}}, Vars2};munge_expr({'fun',Line,{clauses,Clauses}}, Vars) -> %% Only for Vsn=raw_abstract_v1 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars, []), {{'fun',Line,{clauses,MungedClauses}}, Vars2};munge_expr(Form, Vars) -> % var|char|integer|float|string|atom|nil|bin|eof {Form, Vars}.munge_exprs([Expr|Exprs], Vars, MungedExprs) when Vars#vars.is_guard=:=true, is_list(Expr) -> {MungedExpr, _Vars} = munge_exprs(Expr, Vars, []), munge_exprs(Exprs, Vars, [MungedExpr|MungedExprs]);munge_exprs([Expr|Exprs], Vars, MungedExprs) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_exprs(Exprs, Vars2, [MungedExpr|MungedExprs]);munge_exprs([], Vars, MungedExprs) -> {reverse(MungedExprs), Vars}.munge_lc([{generate,Line,Pattern,Expr}|LC], Vars, MungedLC) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_lc(LC, Vars2, [{generate,Line,Pattern,MungedExpr}|MungedLC]);munge_lc([Expr|LC], Vars, MungedLC) -> {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_lc(LC, Vars2, [MungedExpr|MungedLC]);munge_lc([], Vars, MungedLC) -> {reverse(MungedLC), Vars}.%%%--Analysis------------------------------------------------------------%% Collect data for all modulescollect(Nodes) -> %% local node MS = ets:fun2ms(fun({M,C}) when is_atom(M) -> {M,C} end), AllClauses = ets:select(?COVER_TABLE,MS), move_modules(AllClauses), %% remote nodes remote_collect('_',Nodes,false).%% Collect data for one modulecollect(Module,Clauses,Nodes) -> %% local node move_modules([{Module,Clauses}]), %% remote nodes remote_collect(Module,Nodes,false).%% When analysing, the data from the local ?COVER_TABLE is moved to the%% ?COLLECTION_TABLE. Resetting data in ?COVER_TABLEmove_modules([{Module,Clauses}|AllClauses]) -> ets:insert(?COLLECTION_TABLE,{Module,Clauses}), move_clauses(Clauses), move_modules(AllClauses);move_modules([]) -> ok. move_clauses([{M,F,A,C,_L}|Clauses]) -> Pattern = {#bump{module=M, function=F, arity=A, clause=C}, '_'}, Bumps = ets:match_object(?COVER_TABLE,Pattern), lists:foreach(fun({Key,Val}) -> ets:insert(?COVER_TABLE, {Key,0}), insert_in_collection_table(Key,Val) end, Bumps), move_clauses(Clauses);move_clauses([]) -> ok. %% Given a .beam file, find the .erl file. Look first in same directory as%% the .beam file, then in <beamdir>/../srcfind_source(File0) -> case filename:rootname(File0,".beam") of File0 -> File0; File -> InSameDir = File++".erl", case filelib:is_file(InSameDir) of true -> InSameDir; false -> Dir = filename:dirname(File), Mod = filename:basename(File), InDotDotSrc = filename:join([Dir,"..","src",Mod++".erl"]), case filelib:is_file(InDotDotSrc) of true -> InDotDotSrc; false -> {beam,File0} end end end.%% do_analyse(Module, Analysis, Level, Clauses)-> {ok,Answer} | {error,Error}%% Clauses = [{Module,Function,Arity,Clause,Lines}]do_analyse(Module, Analysis, line, _Clauses) -> Pattern = {#bump{module=Module},'_'}, Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), Fun = case Analysis of coverage -> fun({#bump{line=L}, 0}) -> {{Module,L}, {0,1}}; ({#bump{line=L}, _N}) -> {{Module,L}, {1,0}} end; calls -> fun({#bump{line=L}, N}) -> {{Module,L}, N} end end, Answer = lists:keysort(1, lists:map(Fun, Bumps)), {ok, Answer};do_analyse(_Module, Analysis, clause, Clauses) -> Fun = case Analysis of coverage -> fun({M,F,A,C,Ls}) -> Pattern = {#bump{module=M,function=F,arity=A, clause=C},0}, Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), NotCov = length(Bumps), {{M,F,A,C}, {Ls-NotCov, NotCov}} end; calls -> fun({M,F,A,C,_Ls}) -> Pattern = {#bump{module=M,function=F,arity=A, clause=C},'_'}, Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), {_Bump, Calls} = hd(lists:keysort(1, Bumps)), {{M,F,A,C}, Calls} end end, Answer = lists:map(Fun, Clauses), {ok, Answer};do_analyse(Module, Analysis, function, Clauses) -> {ok, ClauseResult} = do_analyse(Module, Analysis, clause, Clauses), Result = merge_clauses(ClauseResult, merge_fun(Analysis)), {ok, Result};do_analyse(Module, Analysis, module, Clauses) -> {ok, FunctionResult} = do_analyse(Module, Analysis, function, Clauses), Result = merge_functions(FunctionResult, merge_fun(Analysis)), {ok, {Module,Result}}.merge_fun(coverage) -> fun({Cov1,NotCov1}, {Cov2,NotCov2}) -> {Cov1+Cov2, NotCov1+NotCov2} end;merge_fun(calls) -> fun(Calls1, Calls2) -> Calls1+Calls2 end.merge_clauses(Clauses, MFun) -> merge_clauses(Clauses, MFun, []).merge_clauses([{{M,F,A,_C1},R1},{{M,F,A,C2},R2}|Clauses], MFun, Result) -> merge_clauses([{{M,F,A,C2},MFun(R1,R2)}|Clauses], MFun, Result);merge_clauses([{{M,F,A,_C},R}|Clauses], MFun, Result) -> merge_clauses(Clauses, MFun, [{{M,F,A},R}|Result]);merge_clauses([], _Fun, Result) -> reverse(Result).merge_functions([{_MFA,R}|Functions], MFun) -> merge_functions(Functions, MFun, R);merge_functions([],_MFun) -> % There are no clauses. {0,0}. % No function can be covered or notcov.merge_functions([{_MFA,R}|Functions], MFun, Result) -> merge_functions(Functions, MFun, MFun(Result, R));merge_functions([], _MFun, Result) -> Result.%% do_analyse_to_file(Module,OutFile,ErlFile) -> {ok,OutFile} | {error,Error}%% Module = atom()%% OutFile = ErlFile = string()do_analyse_to_file(Module, OutFile, ErlFile, HTML) -> case file:open(ErlFile, read) of {ok, InFd} -> case file:open(OutFile, write) of {ok, OutFd} -> if HTML -> io:format(OutFd, "<html>\n" "<head><title>~s</title></head>" "<body bgcolor=white text=black>\n" "<pre>\n", [OutFile]); true -> ok end, %% Write some initial information to the output file {{Y,Mo,D},{H,Mi,S}} = calendar:local_time(), io:format(OutFd, "File generated from ~s by COVER " "~p-~s-~s at ~s:~s:~s~n", [ErlFile, Y, string:right(integer_to_list(Mo), 2, $0), string:right(integer_to_list(D), 2, $0), string:right(integer_to_list(H), 2, $0), string:right(integer_to_list(Mi), 2, $0), string:right(integer_to_list(S), 2, $0)]), io:format(OutFd, "~n" "**************************************" "**************************************" "~n~n", []), print_lines(Module, InFd, OutFd, 1, HTML), if HTML -> io:format(OutFd,"</pre>\n</body>\n</html>\n",[]); true -> ok end, file:close(OutFd), file:close(InFd), {ok, OutFile}; {error, Reason} -> {error, {file, OutFile, Reason}} end; {error, Reason} -> {error, {file, ErlFile, Reason}} end.print_lines(Module, InFd, OutFd, L, HTML) -> case io:get_line(InFd, '') of eof -> ignore; "%"++_=Line -> %Comment line - not executed. io:put_chars(OutFd, [tab(),Line]), print_lines(Module, InFd, OutFd, L+1, HTML); RawLine -> Line = escape_lt_and_gt(RawLine,HTML), Pattern = {#bump{module=Module,line=L},'$1'}, case ets:match(?COLLECTION_TABLE, Pattern) of [] -> io:put_chars(OutFd, [tab(),Line]); Ns -> N = lists:foldl(fun([Ni], Nacc) -> Nacc+Ni end, 0, Ns), if N=:=0, HTML=:=true -> LineNoNL = Line -- "\n", Str = " 0", %%Str = string:right("0", 6, 32), RedLine = ["<font color=red>",Str,fill1(), LineNoNL,"</font>\n"], io:put_chars(OutFd, RedLine); N<1000000 -> Str = string:right(integer_to_list(N), 6, 32), io:put_chars(OutFd, [Str,fill1(),Line]); N<10000000 -> Str = integer_to_list(N), io:put_chars(OutFd, [Str,fill2(),Line]); true -> Str = integer_to_list(N), io:put_chars(OutFd, [Str,fill3(),Line]) end end, print_lines(Module, InFd, OutFd, L+1, HTML) end.tab() -> " | ".fill1() -> "..| ".fill2() -> ".| ".fill3() -> "| ".%%%--Export--------------------------------------------------------------do_export_table(Compiled, Imported, Fd) -> ModList = merge(Imported,Compiled), write_module_data(ModList,Fd).merge([{Module,File,_ImportFiles}|Imported],ModuleList) -> case lists:keymember(Module,1,ModuleList) of true -> merge(Imported,ModuleList); false -> merge(Imported,[{Module,File}|ModuleList]) end;merge([],ModuleList) -> ModuleList.write_module_data([{Module,File}|ModList],Fd) -> write({file,Module,File},Fd), [Clauses] = ets:lookup(?COLLECTION_TABLE,Module), write(Clauses,Fd), ModuleData = ets:match_object(?COLLECTION_TABLE,{#bump{module=Module},'_'}), do_write_module_data(ModuleData,Fd), write_module_data(ModList,Fd);write_module_data([],_Fd) -> ok.do_write_module_data([H|T],Fd) -> write(H,Fd), do_write_module_data(T,Fd);do_write_module_data([],_Fd) -> ok.write(Element,Fd) -> Bin = term_to_binary(Element,[compressed]), case size(Bin) of Size when Size > 255 -> SizeBin = term_to_binary({'$size',Size}), file:write(Fd,<<(size(SizeBin)):8,SizeBin/binary,Bin/binary>>); Size -> file:write(Fd,<<Size:8,Bin/binary>>) end, ok. %%%--Import--------------------------------------------------------------do_import_to_table(Fd,ImportFile,Imported) -> do_import_to_table(Fd,ImportFile,Imported,[]).do_import_to_table(Fd,ImportFile,Imported,DontImport) -> case get_term(Fd) of {file,Module,File} -> case add_imported(Module, File, ImportFile, Imported) of {ok,NewImported} -> do_import_to_table(Fd,ImportFile,NewImported,DontImport); dont_import -> do_import_to_table(Fd,ImportFile,Imported, [Module|DontImport]) end; {Key=#bump{module=Module},Val} -> case lists:member(Module,DontImport) of false -> insert_in_collection_table(Key,Val); true -> ok end, do_import_to_table(Fd,ImportFile,Imported,DontImport); {Module,Clauses} -> case lists:member(Module,DontImport) of false -> ets:insert(?COLLECTION_TABLE,{Module,Clauses}); true -> ok end, do_import_to_table(Fd,ImportFile,Imported,DontImport); eof -> Imported end. get_term(Fd) -> case file:read(Fd,1) of {ok,<<Size1:8>>} -> {ok,Bin1} = file:read(Fd,Size1), case binary_to_term(Bin1) of {'$size',Size2} -> {ok,Bin2} = file:read(Fd,Size2), binary_to_term(Bin2); Term -> Term end; eof -> eof end.%%%--Reset---------------------------------------------------------------%% Reset main node and all remote nodesdo_reset_main_node(Module,Nodes) -> do_reset(Module), do_reset_collection_table(Module), remote_reset(Module,Nodes).do_reset_collection_table(Module) -> ets:delete(?COLLECTION_TABLE,Module), ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}).%% do_reset(Module) -> ok%% The reset is done on a per-clause basis to avoid building%% long lists in the case of very large modulesdo_reset(Module) -> [{Module,Clauses}] = ets:lookup(?COVER_TABLE, Module), do_reset2(Clauses).do_reset2([{M,F,A,C,_L}|Clauses]) -> Pattern = {#bump{module=M, function=F, arity=A, clause=C}, '_'}, Bumps = ets:match_object(?COVER_TABLE, Pattern), lists:foreach(fun({Bump,_N}) -> ets:insert(?COVER_TABLE, {Bump,0}) end, Bumps), do_reset2(Clauses);do_reset2([]) -> ok. do_clear(Module) -> ets:match_delete(?COVER_TABLE, {Module,'_'}), ets:match_delete(?COVER_TABLE, {#bump{module=Module},'_'}), ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}).%%%--Div-----------------------------------------------------------------reverse(List) -> reverse(List,[]).reverse([H|T],Acc) -> reverse(T,[H|Acc]);reverse([],Acc) -> Acc.escape_lt_and_gt(Rawline,HTML) when HTML =/= true -> Rawline;escape_lt_and_gt(Rawline,_HTML) -> escape_lt_and_gt1(Rawline,[]).escape_lt_and_gt1([$<|T],Acc) -> escape_lt_and_gt1(T,[$;,$t,$l,$&|Acc]);escape_lt_and_gt1([$>|T],Acc) -> escape_lt_and_gt1(T,[$;,$t,$g,$&|Acc]);escape_lt_and_gt1([],Acc) -> lists:reverse(Acc);escape_lt_and_gt1([H|T],Acc) -> escape_lt_and_gt1(T,[H|Acc]).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -