%% %% %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(now()), 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.