%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2001-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%
%%
-module(cover_web).
-author('marting@erix.ericsson.se').
-behaviour(gen_server).
%%Export of configuration function
-export([configData/0]).
%% External exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([start_link/0,start/0,stop/0]).
-export([menu_frame/2,nodes_frame/2,import_frame/2,
compile_frame/2,result_frame/2]).
-export([list_dir/2,compile/2,add_node/2,remove_node/2,result/2,
calls/2,coverage/2,import/2]).
-record(state,{dir}).
-include_lib("kernel/include/file.hrl").
%% Timeouts
-define(DEFAULT_TIME,10000).
-define(MAX_COMPILE_TIME,60000).
-define(MAX_ANALYSE_TIME,30000).
%% Colors
-define(INFO_BG_COLOR,"#C0C0EA").
%%%----------------------------------------------------------------------
%%% API - called from erlang shell
%%%----------------------------------------------------------------------
%% Start webtool and webcover from erlang shell
start() ->
webtool:start(),
webtool:start_tools([],"app=webcover"),
ok.
%% Stop webtool and webcover from erlang shell
stop() ->
webtool:stop_tools([],"app=webcover"),
webtool:stop().
%%%----------------------------------------------------------------------
%%% API - called from webtool
%%%----------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, webcover_server},cover_web, [], []).
nodes_frame(Env,Input)->
call({nodes_frame,Env,Input}).
add_node(Env,Input)->
call({add_node,Env,Input}).
remove_node(Env,Input)->
call({remove_node,Env,Input}).
compile_frame(Env,Input)->
call({compile_frame,Env,Input}).
list_dir(Env,Input) ->
call({list_dir,Env,Input}).
compile(Env,Input)->
call({compile,Env,Input},?MAX_COMPILE_TIME).
result_frame(Env,Input)->
call({result_frame,Env,Input}).
result(Env,Input) ->
call({result,Env,Input},?MAX_ANALYSE_TIME).
calls(Env,Input) ->
call({calls,Env,Input}).
coverage(Env,Input) ->
call({coverage,Env,Input}).
import_frame(Env,Input)->
call({import_frame,Env,Input}).
import(Env,Input)->
call({import,Env,Input}).
menu_frame(Env,Input)->
call({menu_frame,Env,Input}).
call(Msg) ->
call(Msg,?DEFAULT_TIME).
call(Msg,Time) ->
gen_server:call(webcover_server,Msg,Time).
configData()->
{webcover,[{web_data,{"WebCover","/webcover"}},
{alias,{"/webcover",code:priv_dir(tools)}},
{alias,{erl_alias,"/webcover/erl",[cover_web]}},
{start,{child,{{local,webcover_server},
{cover_web,start_link,[]},
permanent,100,worker,[cover_web]}}}
]}.
%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
init([]) ->
cover:start(),
CS = whereis(cover_server),
link(CS),
GL = spawn_link(fun group_leader_proc/0),
group_leader(GL,CS),
%% Must trap exists in order to have terminate/2 executed when
%% crashing because of a linked process crash.
process_flag(trap_exit,true),
{ok,Cwd} = file:get_cwd(),
{ok, #state{dir=Cwd}}.
group_leader_proc() ->
register(cover_group_leader_proc,self()),
group_leader_loop([]).
group_leader_loop(Warnings) ->
receive
{io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} ->
Msg = (catch io_lib:Func(Format,Args)),
From ! {io_reply,ReplyAs,ok},
case lists:member(Msg,Warnings) of
true -> group_leader_loop(Warnings);
false -> group_leader_loop([Msg|Warnings])
end;
{io_request,From,ReplyAs,{put_chars,_Encoding,io_lib,Func,[Format,Args]}} ->
Msg = (catch io_lib:Func(Format,Args)),
From ! {io_reply,ReplyAs,ok},
case lists:member(Msg,Warnings) of
true -> group_leader_loop(Warnings);
false -> group_leader_loop([Msg|Warnings])
end;
IoReq when element(1,IoReq)=:= io_request ->
group_leader() ! IoReq,
group_leader_loop(Warnings);
{From,get_warnings} ->
Warnings1 =
receive
{io_request,From,ReplyAs,
{put_chars,io_lib,Func,[Format,Args]}} ->
Msg = (catch io_lib:Func(Format,Args)),
From ! {io_reply,ReplyAs,ok},
case lists:member(Msg,Warnings) of
true -> Warnings;
false -> [Msg|Warnings]
end
after 0 ->
Warnings
end,
From ! {warnings,Warnings1},
group_leader_loop([])
end.
%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_call({nodes_frame,_Env,_Input},_From,State)->
{reply,nodes_frame1(),State};
handle_call({add_node,_Env,Input},_From,State)->
{reply,do_add_node(Input),State};
handle_call({remove_node,_Env,Input},_From,State)->
{reply,do_remove_node(Input),State};
handle_call({compile_frame,_Env,_Input},_From,State)->
{reply,compile_frame1(State#state.dir),State};
handle_call({list_dir,_Env,Input},_From,State)->
Dir = get_input_data(Input,"path"),
case filelib:is_dir(Dir) of
true ->
{reply,compile_frame1(Dir),State#state{dir=Dir}};
false ->
Err = Dir ++ " is not a directory",
{reply,compile_frame1(State#state.dir,Err),State}
end;
handle_call({compile,_Env,Input},_From,State)->
{reply,do_compile(Input,State#state.dir),State};
handle_call({result_frame,_Env,_Input},_From,State)->
{reply,result_frame1(),State};
handle_call({result,_Env,Input},_From,State)->
{reply,handle_result(Input),State};
handle_call({calls,_Env,Input},_From,State)->
{reply,call_page(Input),State};
handle_call({coverage,_Env,Input},_From,State)->
{reply,coverage_page(Input),State};
handle_call({import_frame,_Env,_Input},_From,State)->
{ok,Cwd} = file:get_cwd(),
{reply,import_frame1(Cwd),State};
handle_call({import,_Env,Input},_From,State)->
{reply,do_import(Input),State};
handle_call({menu_frame,_Env,_Input},_From,State)->
{reply,menu_frame1(),State};
handle_call(_Request, _From, State) ->
Reply = bad_request,
{reply, Reply, State}.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info({'EXIT',_Pid,Reason}, State) ->
{stop, Reason, State}.
%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(_Reason, _State) ->
cover:stop(),
ok.
%%--------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% The functions that creates the whole pages by collecting all the %%
%% neccessary data for each page. These functions are the public %%
%% interface. %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%----------------------------------------------------------------------
%% Returns the page to the left frame
%%----------------------------------------------------------------------
menu_frame1()->
[header(),html_header(""),menu_body(),html_end()].
%%----------------------------------------------------------------------
%% Creates the page where the user can add and remove nodes
%%----------------------------------------------------------------------
nodes_frame1()->
nodes_frame1([]).
nodes_frame1(Err)->
[header(),html_header("Add/remove nodes"),nodes_body(Err),html_end()].
%%----------------------------------------------------------------------
%% Creates the page where the user can cover compile modules
%%----------------------------------------------------------------------
compile_frame1(Dir)->
compile_frame1(Dir,[]).
compile_frame1(Dir,Err) ->
[header(),html_header("Cover compile"),compile_body(Dir,Err),html_end()].
%%----------------------------------------------------------------------
%% Creates the page where the user can handle results
%%----------------------------------------------------------------------
result_frame1()->
result_frame1([]).
result_frame1(Err) ->
[header(),html_header("Show cover results"),result_body(Err),html_end()].
%%----------------------------------------------------------------------
%%The beginning of the page that clear the cover information on a cover
%%compiled module
%%----------------------------------------------------------------------
call_page(Input)->
[header(),html_header("Code coverage"),call_result(Input),html_end()].
coverage_page(Input)->
[header(),html_header("Code coverage"),coverage_result(Input),html_end()].
%%----------------------------------------------------------------------
%% Creates the page where the user an import files
%%----------------------------------------------------------------------
import_frame1(Dir) ->
import_frame1(Dir,"").
import_frame1(Dir,Err) ->
[header(),html_header("Import coverdata"),import_body(Dir,Err),html_end()].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% The functions that build the body of the menu frame %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
menu_body() ->
Nodes = cover:which_nodes(),
Modules = cover:modules(),
Imported = cover:imported(),
["<A HREF=\"./nodes_frame\" TARGET=\"main\">Nodes</A><BR>\n",
"<A HREF=\"./compile_frame\" TARGET=\"main\">Compile</A><BR>\n",
"<A HREF=\"./import_frame\" TARGET=\"main\">Import</A><BR>\n",
"<A HREF=\"./result_frame\" TARGET=\"main\">Result</A>\n",
"<P><B>Nodes:</B>\n",
"<UL>\n",
lists:map(fun(N) -> "<LI>"++atom_to_list(N)++"</LI>\n" end,[node()|Nodes]),
"</UL>\n",
"<P><B>Compiled modules:</B>\n",
"<UL>\n",
lists:map(fun(M) -> "<LI>"++atom_to_list(M)++"</LI>\n" end,Modules),
"</UL>\n",
"<P><B>Imported files:</B>\n",
"<UL>\n",
"<FONT SIZE=-1>\n",
lists:map(fun(F) ->
Short = filename:basename(F),
"<LI TITLE=\""++F++"\">"++Short++"</LI>\n" end,Imported),
"</FONT>\n",
"</UL>\n"].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% The functions that build the body of the nodes frame %%
%% %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
nodes_body(Err) ->
CN = cover:which_nodes(),
Fun = fun(N) ->
NStr = atom_to_list(N),
["<OPTION VALUE=",NStr,
" onClick=\"node.value=selected_node.value\">",NStr,
"</OPTION>\n"]
end,
AllNodes = lists:append(lists:map(Fun,nodes()--CN)),
CoverNodes = lists:append(lists:map(Fun,CN)),
[reload_menu_script(Err),
"<H1 ALIGN=center>Nodes</H1>\n",
"<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
"<TR><TD BGCOLOR=",?INFO_BG_COLOR," COLSPAN=2>\n",
"<P>You can run cover over several nodes simultaneously. Coverage data\n",
"from all involved nodes will be merged during analysis.\n",
"<P>Select or enter node names to add or remove here.\n",
"</TD></TR>\n",
"<TR><TD COLSPAN=2><BR><BR></TD></TR>\n",
"<FORM ACTION=\"./add_node\" NAME=add_node>\n",
"<TR><TD VALIGN=top>Add node:</TD>\n",
"<TD><INPUT TYPE=text NAME=\"node\" SIZE=40 >",
"<INPUT TYPE=submit\n",
" onClick=\"if(!node.value){node.value=selected_node.value};\" VALUE=Add>"
"<BR><SELECT NAME=selected_node TITLE=\"Select node\">\n",
AllNodes ++
"</SELECT>\n",
"</TD></TR>\n"
"</FORM>\n",
"<TR><TD COLSPAN=2><BR><BR></TD></TR>\n",
"<FORM ACTION=\"./remove_node\" NAME=remove_node>\n",
"<TR><TD>Remove node:</TD>\n",
"<TD><SELECT NAME=node TITLE=\"Select node\">\n",
CoverNodes ++
"</SELECT>\n",
"<INPUT TYPE=submit VALUE=Remove>"
"</TD></TR>\n",
"</FORM>",
"</TABLE>"].
do_add_node(Input) ->
NodeStr = get_input_data(Input, "node"),
Node = list_to_atom(NodeStr),
case net_adm:ping(Node) of
pong ->
cover:start(Node),
nodes_frame1();
pang ->
nodes_frame1("Node \\\'" ++ NodeStr ++ "\\\' is not alive")
end.
do_remove_node(Input) ->
Node = list_to_atom(get_input_data(Input, "node")),
cover:stop(Node),
nodes_frame1().
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% The functions that is used when the user wants to compile something %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile_body(Dir,Err) ->
Erls = filelib:wildcard(filename:join(Dir,"*.erl")),
Beams = filelib:wildcard(filename:join(Dir,"*.beam")),
[reload_menu_script(Err),
"<H1 ALIGN=center>Compile</H1>\n",
"<TABLE WIDTH=600 ALIGN=center BORDER=0>\n",
"<TR><TD COLSPAN=3 BGCOLOR=",?INFO_BG_COLOR,">\n",
"Each module which shall be part of the cover analysis must be prepared\n",
"or 'cover compiled'. On this page you can select .erl files and/or\n",
".beam files to include in the analysis. If you select a .erl file it\n",
"will first be compiled with the Erlang compiler and then prepared for\n",
"coverage analysis. If you select a .beam file it will be prepared for\n",
"coverage analysis directly.\n",
"</TD></TR>\n",
"<FORM ACTION=\"./list_dir\" NAME=list_dir>\n",
"<TR><TD WIDTH=30% BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
"To list a different directory, enter the directory name here.\n",
"</TD>\n",
"<TH COLSPAN=2><BR>List directory:<BR></TH>\n",
"</TR>\n",
"<TR><TD ALIGN=center COLSPAN=2>\n",
"<INPUT TYPE=text NAME=\"path\" SIZE=40 VALUE=",Dir,">",
"<INPUT TYPE=submit VALUE=Ok>",
"<BR><BR></TD></TR>\n",
"</FORM>\n",
"<FORM ACTION=\"./compile\" NAME=compile_selection>\n",
"<TR><TD BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
"<P>Select one or more .erl or .beam files to prepare for coverage\n"
"analysis, and click the \"Compile\" button.\n",
"<P>To reload the original file after coverage analysis is complete,\n"
"select one or more files and click the \"Uncompile\" button, or\n",
"simply click the \"Uncompile all\" button to reload all originals.\n"
"</TD>\n",
"<TH>.erl files</TH><TH>.beam files</TH></TR>\n",
"<TR><TD ALIGN=center VALIGN=top>\n",
"<SELECT NAME=erl TITLE=\"Select .erl files to compile\" MULTIPLE=true",
" SIZE=15>\n",
list_modules(Erls) ++
"</SELECT></TD>\n",
"<TD ALIGN=center VALIGN=top>\n",
"<SELECT NAME=beam TITLE=\"Select .beam files to compile\"MULTIPLE=true",
" SIZE=15>\n",
list_modules(Beams) ++
"</SELECT></TD></TR>\n"
"<TR><TD BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
"Compile options are only needed for .erl files. The options must be\n"
"given e.g. like this: \n"
"<FONT SIZE=-1>[{i,\"/my/path/include\"},{i,\"/other/path/\"}]</FONT>\n"
"</TD>\n",
"<TH COLSPAN=2><BR>Compile options:<BR></TH>\n",
"</TR>\n",
"<TR><TD COLSPAN=2 ALIGN=center>\n",
"<INPUT TYPE=text NAME=\"options\" SIZE=40>\n",
"<INPUT TYPE=hidden NAME=\"action\"></TD></TR>\n",
"<TR><TD></TD><TD ALIGN=center COLSPAN=2>\n",
"<INPUT TYPE=submit onClick=\"action.value=\'compile\';\"VALUE=Compile>",
"<INPUT TYPE=submit onClick=\"action.value=\'uncompile\';\" ",
"VALUE=Uncompile>",
"<INPUT TYPE=submit onClick=\"action.value=\'uncompile_all\';\" ",
"VALUE=\"Uncompile all\">",
"<BR><INPUT TYPE=reset VALUE=\"Reset form\"></TD></TR>\n",
"</FORM>\n",
"</TABLE>\n"].
list_modules([File|Files]) ->
Mod = filename:basename(File),
["<OPTION VALUE=",File," onDblClick=\"action.value=\'compile\';submit();\">",
Mod,"</OPTION>\n" | list_modules(Files)];
list_modules([]) ->
[].
do_compile(Input,Dir) ->
{Erls,Beams,Opts,Action} = get_compile_input(parse(Input),[],[]),
Errs =
case Action of
"compile" ->
do_compile(Erls,Beams,Opts,[]);
"uncompile" ->
do_uncompile(Erls++Beams);
"uncompile_all" ->
do_uncompile(cover:modules())
end,
compile_frame1(Dir,Errs).
get_compile_input([{"erl",File}|Input],Erl,Beam) ->
get_compile_input(Input,[File|Erl],Beam);
get_compile_input([{"beam",File}|Input],Erl,Beam) ->
get_compile_input(Input,Erl,[File|Beam]);
get_compile_input([{"options",Opts0},{"action",Action}],Erl,Beam) ->
Opts = parse_options(Opts0),
{Erl,Beam,Opts,Action}.
do_compile([Erl|Erls],Beams,Opts,Errs) ->
case cover:compile_module(Erl,Opts) of
{ok,_} ->
do_compile(Erls,Beams,Opts,Errs);
{error,File} ->
do_compile(Erls,Beams,Opts,["\\n"++File|Errs])
end;
do_compile([],[Beam|Beams],Opts,Errs) ->
case cover:compile_beam(Beam) of
{ok,_} ->
do_compile([],Beams,Opts,Errs);
{error,{no_abstract_code,File}} ->
do_compile([],Beams,Opts,["\\n"++File++" (no_abstract_code)"|Errs])
end;
do_compile([],[],_,[]) ->
[];
do_compile([],[],_,Errs) ->
"Compilation failed for the following files:" ++ Errs.
parse_options(Options)->
case erl_scan:string(Options ++".") of
{ok,Tokens,_Line} ->
case erl_parse:parse_exprs(Tokens) of
{ok,X}->
case lists:map(fun erl_parse:normalise/1, X) of
[List] when is_list(List) -> List;
List -> List
end;
_ ->
[]
end;
_ ->
[]
end.
do_uncompile(Files) ->
lists:foreach(
fun(File) ->
Module =
if is_atom(File) ->
File;
true ->
ModStr = filename:basename(filename:rootname(File)),
list_to_atom(ModStr)
end,
case code:which(Module) of
cover_compiled ->
code:purge(Module),
case code:load_file(Module) of
{module, Module} ->
ok;
{error, _Reason2} ->
code:delete(Module)
end;
_ ->
ok
end
end,
Files),
[].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% The functions that builds the body of the page for coverage analysis%
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
result_body(Err) ->
[reload_menu_script(Err),
"<H1 ALIGN=center>Result</H1>\n",
"<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
"<TR><TD BGCOLOR=",?INFO_BG_COLOR,">\n",
"<P>After executing all your tests you can view the result of the\n",
"coverage analysis here. For each module you can\n",
"<DL>\n",
"<DT><B>Analyse to file</B></DT>\n",
"<DD>The source code of the module is shown with the number of calls\n",
"to each line stated in the left margin. Lines which are never called\n",
"are colored red.</DD>\n",
"<DT><B>Analyse coverage</B></DT>\n",
"<DD>Show the number of covered and uncovered lines in the module.</DD>\n",
"<DT><B>Analyse calls</B></DT>\n",
"<DD>Show the number of calls in the module.</DD>\n",
"<DT><B>Reset module</B></DT>\n",
"<DD>Delete all coverage data for the module.</DD>\n",
"<DT><B>Export module</B></DT>\n",
"<DD>Write all coverage data for the module to a file. The data can\n",
"later be imported from the \"Import\" page.</DD>\n",
"</DL>\n",
"<P>You can also reset or export data for all modules with the\n",
"<B>Reset all</B> and <B>Export all</B> actions respectively. For these\n",
"two actions there is no need to select a module.\n",
"<P>Select module and action from the drop down menus below, and click\n",
"the \"Execute\" button.\n",
"</TD></TR>\n",
"<TR><TD><BR><BR>\n",
result_selections(),
"</TD></TR></TABLE>"].
result_selections() ->
ModList = filter_modlist(cover:modules()++cover:imported_modules(),[]),
["<FORM ACTION=\"./result\" NAME=result_selection>\n",
"<TABLE WIDTH=\"300\" BORDER=0 ALIGN=center>\n",
"<TR><TD ALIGN=left>\n",
"Module:\n",
"<BR><SELECT NAME=module TITLE=\"Select module\">\n",
ModList ++
"</SELECT>\n",
"</TD>\n",
"<TD ALIGN=left>\n",
"Action:\n",
"<BR><SELECT NAME=action TITLE=\"Select action\">\n",
"<OPTION VALUE=\"analyse_to_file\">Analyse to file</OPTION>\n"
"<OPTION VALUE=\"coverage\">Analyse coverage</OPTION>\n"
"<OPTION VALUE=\"calls\">Analyse calls</OPTION>\n"
"<OPTION VALUE=\"reset\">Reset module</OPTION>\n"
"<OPTION VALUE=\"reset_all\">Reset all</OPTION>\n"
"<OPTION VALUE=\"export\">Export module</OPTION>\n"
"<OPTION VALUE=\"export_all\">Export all</OPTION>\n"
"</SELECT>\n",
"</TD>\n",
"<TD ALIGN=center VALIGN=bottom><INPUT TYPE=submit VALUE=Execute>\n"
"</TD></TR>\n"
"</TABLE>\n",
"</FORM>\n"].
filter_modlist([M|Ms],Already) ->
case lists:member(M,Already) of
true ->
filter_modlist(Ms,Already);
false ->
MStr = atom_to_list(M),
["<OPTION VALUE=",MStr,">",MStr,"</OPTION>\n" |
filter_modlist(Ms,[M|Already])]
end;
filter_modlist([],_Already) ->
[].
handle_result(Input) ->
case parse(Input) of
[{"module",M},{"action",A}] ->
case A of
"analyse_to_file" ->
case cover:analyse_to_file(list_to_atom(M),[html]) of
{ok,File} ->
case file:read_file(File) of
{ok,HTML}->
file:delete(File),
[header(),
reload_menu_script(""),
binary_to_list(HTML)];
_ ->
result_frame1("Can not read file" ++ File)
end;
{error,no_source_code_found} ->
result_frame1("No source code found for \\\'" ++
M ++ "\\\'")
end;
"calls" ->
call_page(Input);
"coverage" ->
coverage_page(Input);
"reset" ->
cover:reset(list_to_atom(M)),
result_frame1("Coverage data for \\\'" ++ M ++
"\\\' is now reset");
"reset_all" ->
cover:reset(),
result_frame1("All coverage data is now reset");
"export" ->
ExportFile = generate_filename(M),
cover:export(ExportFile,list_to_atom(M)),
result_frame1("Coverage data for \\\'" ++ M ++
"\\\' is now exported to file \\\"" ++
ExportFile ++ "\\\"");
"export_all" ->
ExportFile = generate_filename("COVER"),
cover:export(ExportFile),
result_frame1(
"All coverage data is now exported to file \\\"" ++
ExportFile ++ "\\\"")
end;
[{"action",_A}] ->
result_frame1("No module is selected")
end.
generate_filename(Prefix) ->
{ok,Cwd} = file:get_cwd(),
filename:join(Cwd,Prefix ++ "_" ++ ts() ++ ".coverdata").
ts() ->
{{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(erlang:timestamp()),
io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w",
[Y,M,D,H,Min,S]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% The functions that builds the body of the page that shows the calls %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
call_result(Input)->
Mod = list_to_atom(get_input_data(Input, "module")),
case cover:analyse(Mod,calls) of
{error,_}->
error_body();
{ok,_} ->
call_result2(Mod,Input)
end.
call_result2(Mod,Input)->
Result =
case get_input_data(Input,"what") of
"mod" ->
call_result(mod,Mod);
"func" ->
call_result(func,Mod);
"clause" ->
call_result(clause,Mod);
_->
call_result(all,Mod)
end,
result_choice("calls",Mod) ++ Result.
result_choice(Level,Mod)->
ModStr=atom_to_list(Mod),
[reload_menu_script(""),
"<TABLE WIDTH=100%><TR>\n",
"<TD><A HREF=./",Level,"?module=",ModStr,"&what=all>All Data</A></TD>\n",
"<TD><A HREF=./",Level,"?module=",ModStr,"&what=mod>Module</A></TD>\n",
"<TD><A HREF=./",Level,"?module=",ModStr,"&what=func>Function</A></TD>\n",
"<TD><A HREF=./",Level,"?module=",ModStr,"&what=clause>Clause</A></TD>\n",
"</TR></TABLE><BR>\n"].
call_result(Mode,Module)->
Content =
case Mode of
mod->
format_cover_call(cover:analyse(Module,calls,module),mod);
func->
format_cover_call(cover:analyse(Module,calls,function),func);
clause->
format_cover_call(cover:analyse(Module,calls,clause),clause);
_->
format_cover_call(cover:analyse(Module,calls,module),mod) ++
format_cover_call(cover:analyse(Module,calls,function),func)++
format_cover_call(cover:analyse(Module,calls,clause),clause)
end,
getModDate(Module,date())++"<BR>"++
"<TABLE WIDTH=\"100%\" BORDER=1>"
++ Content ++"</TABLE>".
format_cover_call({error,_},_)->
["<TR><TD>\n",
"<BR><BR><BR><BR>\n",
"<FONT SIZE=5>The selected module is not Cover Compiled</FONT>\n",
"<BR>\n",
"</TD></TR>\n"];
format_cover_call({ok,{Mod,Calls}},mod)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Module calls</B></TD></TR>\n",
"<TR><TD COLSPAN=4><I>Module</I></TD>",
"<TD ALIGN=\"right\"><I>Number of calls</I></TD></TR>\n",
"<TR><TD COLSPAN=4>" ++ atom_to_list(Mod) ++"</TD>"
"<TD ALIGN=\"right\">" ++ integer_to_list(Calls)++"</TD></TR>\n"];
format_cover_call({ok,Calls},func)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Function calls</B></TD></TR>\n",
"<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
"<TD COLSPAN=2 ALIGN=\"right\"><I>Arity</I></TD>",
"<TD ALIGN=\"right\"><I>Number of calls </I></TD></TR>\n",
lists:append(
lists:map(
fun({{Mod,Func,Arity},Nr_of_calls})->
["<TR><TD WIDTH=\"20%\">"++ atom_to_list(Mod)++"</TD>\n",
"<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++" </TD>\n",
"<TD COLSPAN=2 WIDTH=\"40%\" ALIGN=\"right\">",
integer_to_list(Arity),
"</TD>\n",
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Nr_of_calls),
"</TD></TR>\n"]
end,
Calls))];
format_cover_call({ok,Calls},clause)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Clause calls</B></TD></TR>\n",
"<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
"<TD ALIGN=\"right\"><I>Arity</I></TD>",
"<TD ALIGN=\"right\"><I>Ordinal</I></TD>",
"<TD ALIGN=\"right\"><I>Number of calls</I></TD></TR>\n",
lists:append(
lists:map(
fun({{Mod,Func,Arity,Ord},Nr_of_calls})->
["<TR><TD WIDTH=\"20%\" >", atom_to_list(Mod), "</TD>\n",
"<TD WIDTH=\"20%\" >", atom_to_list(Func), "</TD>\n",
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Arity),
"</TD>\n",
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Ord),
"</TD>\n",
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Nr_of_calls),
"</TD></TR>\n"]
end,
Calls))].
error_body()->
["<TABLE WIDTH=\"100%\" BORDER=1>\n",
"<TR ALIGN=\"center\">\n",
"<TD>\n",
"<BR><BR><BR><BR><BR><BR>\n",
"<FONT SIZE=5>The selected module is not Cover Compiled</FONT>\n",
"<BR>\n",
"</TD>\n",
"</TR>\n",
"</TABLE>\n"].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% The functions that builds the body of the page that shows coverage %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
coverage_result(Input)->
Mod = list_to_atom(get_input_data(Input, "module")),
case cover:analyse(Mod,coverage) of
{error,_}->
error_body();
{ok,_} ->
coverage_result2(Mod,Input)
end.
coverage_result2(Mod,Input)->
Result =
case get_input_data(Input,"what") of
"mod" ->
coverage_result(mod,Mod);
"func" ->
coverage_result(func,Mod);
"clause" ->
coverage_result(clause,Mod);
_->
coverage_result(all,Mod)
end,
result_choice("coverage",Mod) ++ Result.
coverage_result(Mode,Module)->
Content =
case Mode of
mod->
format_cover_coverage(cover:analyse(Module,coverage,module),
mod);
func->
format_cover_coverage(cover:analyse(Module,coverage,function),
func);
clause->
format_cover_coverage(cover:analyse(Module,coverage,clause),
clause);
_->
format_cover_coverage(cover:analyse(Module,coverage,module),
mod) ++
format_cover_coverage(cover:analyse(Module,coverage,function),
func)++
format_cover_coverage(cover:analyse(Module,coverage,clause),
clause)
end,
getModDate(Module,date())++"<BR>"++
"<TABLE WIDTH=\"100%\" BORDER=1>"
++ Content ++"</TABLE>".
getModDate(Module,{Year,Mon,Day})->
"<TABLE>
<TR>
<TD>Module:</TD>
<TD>" ++ atom_to_list(Module) ++ "</TD>
</TR>
<TR>
<TD>Date:</TD>
<TD>" ++ integer_to_list(Day) ++ "/" ++
integer_to_list(Mon) ++" - "++
integer_to_list(Year) ++
"</TD>
</TR>
</TABLE>".
format_cover_coverage({error,_},_)->
"<TR><TD>
<BR><BR><BR><BR>
<FONT SIZE=5>The selected module is not Cover Compiled</FONT>
<BR>
</TD></TR>";
format_cover_coverage({ok,{Mod,{Cov,Not_cov}}},mod)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Module coverage</B></TD></TR>\n",
"<TR><TD COLSPAN=4><I>Module</I></TD>\n",
"<TD ALIGN=\"right\"><I>Covered</I></TD>\n"
"<TD ALIGN=\"RIGHT\" NOWRAP=\"true\"><I>Not Covered</I></TD>\n",
"</TR>\n",
"<TR><TD COLSPAN=4>", atom_to_list(Mod), "</TD>\n"
"<TD ALIGN=\"right\">", integer_to_list(Cov), "</TD>\n"
"<TD ALIGN=\"right\" >", integer_to_list(Not_cov), "</TD></TR>\n"];
format_cover_coverage({ok,Cov_res},func)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Function coverage</B></TD>\n",
"</TR>\n",
"<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
"<TD ALIGN=\"right\"><I>Arity</I></TD>",
"<TD COLSPAN=2 ALIGN=\"right\"><I>Covered</I></TD>",
"<TD ALIGN=\"right\" STYLE=\"white-space:nowrap\"><I>Not Covered</I></TD>",
"</TR>\n",
lists:append(
lists:map(
fun({{Mod,Func,Arity},{Cov,Not_cov}})->
["<TR><TD WIDTH=\"20%\" >"++ atom_to_list(Mod) ++" </TD>\n",
"<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++"</TD>\n",
"<TD WIDTH=\"40%\" ALIGN=\"right\">",
integer_to_list(Arity),
"</TD>\n",
"<TD WIDTH=\"40%\" ALIGN=\"right\" COLSPAN=2>",
integer_to_list(Cov),
"</TD>\n"
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Not_cov),
"</TD></TR>\n"]
end,
Cov_res))];
format_cover_coverage({ok,Cov_res},clause)->
["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Clause coverage</B></TD></TR>\n",
"<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>\n",
"<TD ALIGN=\"right\"><I>Arity</I></TD>\n",
"<TD ALIGN=\"right\"><I>Ordinal<I></TD>\n",
"<TD ALIGN=\"right\">Covered</TD>\n",
"<TD ALIGN=\"right\" STYLE=\"white-space:nowrap\">Not Covered</TD></TR>\n",
lists:append(
lists:map(
fun({{Mod,Func,Arity,Ord},{Cov,Not_cov}})->
["<TR><TD WIDTH=\"20%\" >"++ atom_to_list(Mod) ++"</TD>\n",
"<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++" </TD>\n",
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Arity),
"</TD>\n"
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Ord),
"</TD>\n"
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Cov),
"</TD>\n"
"<TD WIDTH=\"20%\" ALIGN=\"right\">",
integer_to_list(Not_cov),
"</TD></TR>\n"]
end,
Cov_res))].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% The functions that builds the body of the import page %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
import_body(Dir,Err) ->
[reload_menu_script(Err),
"<H1 ALIGN=center>Import</H1>\n",
"<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
"<TR><TD BGCOLOR=",?INFO_BG_COLOR,">\n",
"<P>You can import coverage data from a previous analysis. If you do so\n",
"the imported data will be merged with the current coverage data.\n",
"<P>You can export data from the current analysis from the \"Result\"\n",
"page.\n",
"<P>Select the file to import here.\n",
"</TD></TR>\n",
"<TR><TD ALIGN=center><BR><BR>\n",
"<FORM NAME=change_import_dir METHOD=post ACTION=\"./import\">\n",
"<B>Change directory:</B><BR>\n",
"<INPUT TYPE=text NAME=\"file\" SIZE=30 VALUE=",Dir,">",
"<INPUT TYPE=hidden NAME=dir VALUE=",Dir,">\n",
"<INPUT TYPE=submit VALUE=Ok><BR>\n",
"</FORM>\n",
browse_import(Dir),
"</TABLE>"].
browse_import(Dir) ->
{ok,List} = file:list_dir(Dir),
Sorted = lists:reverse(lists:sort(List)),
{Dirs,Files} = filter_files(Dir,Sorted,[],[]),
["<FORM NAME=browse_import METHOD=post ACTION=\"./import\">\n"
"<SELECT NAME=file TITLE=\"Select import file\" SIZE=10>\n",
"<OPTION VALUE=\"..\" onDblClick=submit()>../</OPTION>\n",
Dirs,
Files,
"</SELECT>\n",
"<INPUT TYPE=hidden NAME=dir VALUE=",Dir,">\n",
"<BR><INPUT TYPE=submit VALUE=Ok>\n"
"</FORM>\n"].
filter_files(Dir,[File|Files],Ds,Fs) ->
case filename:extension(File) of
".coverdata" ->
Fs1 = ["<OPTION VALUE=",File," onDblClick=submit()>",
File,"</OPTION>\n" | Fs],
filter_files(Dir,Files,Ds,Fs1);
_ ->
FullName = filename:join(Dir,File),
case filelib:is_dir(FullName) of
true ->
Ds1 = ["<OPTION VALUE=",File," onDblClick=submit()>",
File,"/</OPTION>\n" | Ds],
filter_files(Dir,Files,Ds1,Fs);
false ->
filter_files(Dir,Files,Ds,Fs)
end
end;
filter_files(_Dir,[],Ds,Fs) ->
{Ds,Fs}.
do_import(Input) ->
case parse(Input) of
[{"file",File0},{"dir",Dir}] ->
File = filename:join(Dir,File0),
case filelib:is_dir(File) of
true ->
import_frame1(File);
false ->
case filelib:is_file(File) of
true ->
case cover:import(File) of
ok ->
import_frame1(Dir);
{error,{cant_open_file,ExportFile,_Reason}} ->
import_frame1(Dir,
"Error importing file\\n\\\""
++ ExportFile ++ "\\\"")
end;
false ->
import_frame1(Dir,
"Error importing file\\n\\\"" ++
File ++ "\\\"")
end
end;
[{"dir",Dir}] ->
import_frame1(Dir,"No file is selected")
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% Different private helper functions %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%Create the Header for the page If we now the mimetype use that type %%
%%otherwise use text %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
header() ->
header("text/html").
header(MimeType) ->
"Pragma:no-cache\r\n" ++
"Content-type: " ++ MimeType ++ "\r\n\r\n".
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%Create the Htmlheader set the title of the page %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
html_header(Title) ->
"<HTML>\n" ++
"<HEAD>\n" ++
"<TITLE>" ++ Title ++ "</TITLE>\n" ++
"</HEAD>\n"
"<BODY BGCOLOR=\"#FFFFFF\">\n".
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Close the body- and Html tags %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
html_end()->
"</BODY></HTML>".
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A script which reloads the menu frame and possibly pops up an alert%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
reload_menu_script(Err) ->
["<SCRIPT>\n",
"function reloadMenu()\n",
" {\n",
" parent.menu.document.location.href=\"./menu_frame\";\n",
case Err of
"" -> "";
_ -> " alert(\""++Err++"\");\n"
end,
case get_warnings() of
[] ->
"";
Warnings ->
" alert(\""++fix_newline(lists:flatten(Warnings))++"\");\n"
end,
" }\n",
"</SCRIPT>\n",
"<BODY onLoad=reloadMenu() BGCOLOR=\"#FFFFFF\">"].
fix_newline([$\n|Rest]) ->
[$\\,$n|fix_newline(Rest)];
fix_newline([$"|Rest]) ->
[$\\,$"|fix_newline(Rest)];
fix_newline([Char|Rest]) ->
[Char|fix_newline(Rest)];
fix_newline([]) ->
[].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Control the input data and return the intresting values or error %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
get_input_data(Input,Key)->
case lists:keysearch(Key,1,parse(Input)) of
{value,{Key,Value}} ->
Value;
false ->
undefined
end.
parse(Input) ->
httpd:parse_query(Input).
get_warnings() ->
cover_group_leader_proc ! {self(), get_warnings},
receive {warnings,Warnings} ->
Warnings
end.