%%
%% %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(),
["Nodes
\n",
"Compile
\n",
"Import
\n",
"Result\n",
"
Nodes:\n", "
Compiled modules:\n", "
Imported files:\n", "
\n",
" You can run cover over several nodes simultaneously. Coverage data\n", "from all involved nodes will be merged during analysis.\n", " Select or enter node names to add or remove here.\n", " | |
\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", " |
\n",
" After executing all your tests you can view the result of the\n", "coverage analysis here. For each module you can\n", "
You can also reset or export data for all modules with the\n", "Reset all and Export all actions respectively. For these\n", "two actions there is no need to select a module.\n", " Select module and action from the drop down menus below, and click\n", "the \"Execute\" button.\n", " |
\n", result_selections(), " |
All Data | \n", "Module | \n", "Function | \n", "Clause | \n", "
\n",
" \n", "The selected module is not Cover Compiled\n", " \n", " | \n",
"
Module: | " ++ atom_to_list(Module) ++ " |
Date: | " ++ integer_to_list(Day) ++ "/" ++ integer_to_list(Mon) ++" - "++ integer_to_list(Year) ++ " |
\n",
" 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", " You can export data from the current analysis from the \"Result\"\n", "page.\n", " Select the file to import here.\n", " |
\n", "\n", browse_import(Dir), " |