aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src/vts.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src/vts.erl')
-rw-r--r--lib/common_test/src/vts.erl882
1 files changed, 882 insertions, 0 deletions
diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl
new file mode 100644
index 0000000000..ad4845a7c3
--- /dev/null
+++ b/lib/common_test/src/vts.erl
@@ -0,0 +1,882 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2003-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(vts).
+
+-export([start/0,
+ init_data/4,
+ stop/0,
+ report/2]).
+
+-export([config_data/0,
+ start_link/0]).
+
+-export([start_page/2,
+ title_frame/2,
+ menu_frame/2,
+ welcome_frame/2,
+ config_frame/2,
+ add_config_file/2,
+ remove_config_file/2,
+ run_frame/2,
+ add_test_dir/2,
+ remove_test_dir/2,
+ select_case/2,
+ select_suite/2,
+ run_test/2,
+ result_frameset/2,
+ result_summary_frame/2,
+ no_result_log_frame/2,
+ redirect_to_result_log_frame/2]).
+
+-export([test_info/3]).
+
+-define(START_PAGE,"/vts_erl/vts/start_page").
+
+-define(tests,vts_tests).
+
+%% Colors
+-define(INFO_BG_COLOR,"#C0C0EA").
+
+-record(state,{tests=[],config=[],event_handler=[],test_runner,
+ running=0,reload_results=false,start_dir,current_log_dir,
+ total=0,ok=0,fail=0,skip=0,testruns=[]}).
+
+
+%%%-----------------------------------------------------------------
+%%% User API
+start() ->
+ webtool:start(),
+ webtool:start_tools([],"app=vts").
+
+init_data(ConfigFiles,EvHandlers,LogDir,Tests) ->
+ call({init_data,ConfigFiles,EvHandlers,LogDir,Tests}).
+
+stop() ->
+ webtool:stop_tools([],"app=vts"),
+ webtool:stop().
+
+report(What,Data) ->
+ call({report,What,Data}).
+
+%%%-----------------------------------------------------------------
+%%% Return config data used by webtool
+config_data() ->
+ {ok,LogDir} =
+ case lists:keysearch(logdir,1,init:get_arguments()) of
+ {value,{logdir,[LogD]}} -> {ok,filename:absname(LogD)};
+ false -> file:get_cwd()
+ end,
+ {vts,
+ [{web_data,{"VisualTestServer",?START_PAGE}},
+ {alias,{erl_alias,"/vts_erl",[?MODULE]}},
+ {alias,{"/log_dir",LogDir}},
+ {start,{child,{{local,?MODULE},
+ {?MODULE,start_link,[]},
+ permanent,100,worker,[?MODULE]}}}
+ ]}.
+
+start_link() ->
+ case whereis(?MODULE) of
+ undefined ->
+ Self = self(),
+ Pid = spawn_link(fun() -> init(Self) end),
+ MRef = erlang:monitor(process,Pid),
+ receive
+ {Pid,started} ->
+ erlang:demonitor(MRef),
+ {ok,Pid};
+ {'DOWN',MRef,process,_,Reason} ->
+ {error,{vts,died,Reason}}
+ end;
+ Pid ->
+ {ok,Pid}
+ end.
+
+start_page(_Env,_Input) ->
+ call(start_page).
+title_frame(_Env,_Input) ->
+ call(title_frame).
+welcome_frame(_Env,_Input) ->
+ call(welcome_frame).
+menu_frame(_Env,_Input) ->
+ call(menu_frame).
+config_frame(_Env,_Input) ->
+ call(config_frame).
+add_config_file(_Env,Input) ->
+ call({add_config_file,Input}).
+remove_config_file(_Env,Input) ->
+ call({remove_config_file,Input}).
+run_frame(_Env,_Input) ->
+ call(run_frame).
+add_test_dir(_Env,Input) ->
+ call({add_test_dir,Input}).
+remove_test_dir(_Env,Input) ->
+ call({remove_test_dir,Input}).
+select_suite(_Env,Input) ->
+ call({select_suite,Input}).
+select_case(_Env,Input) ->
+ call({select_case,Input}).
+run_test(_Env,_Input) ->
+ call(run_test).
+result_frameset(_Env,_Input) ->
+ call(result_frameset).
+redirect_to_result_log_frame(_Env,_Input) ->
+ call(redirect_to_result_log_frame).
+result_summary_frame(_Env,_Input) ->
+ call(result_summary_frame).
+no_result_log_frame(_Env,_Input) ->
+ call(no_result_log_frame).
+
+aborted() ->
+ call(aborted).
+
+test_info(_VtsPid,Type,Data) ->
+ call({test_info,Type,Data}).
+
+init(Parent) ->
+ register(?MODULE,self()),
+ process_flag(trap_exit,true),
+ Parent ! {self(),started},
+ {ok,Cwd} = file:get_cwd(),
+ InitState = #state{start_dir=Cwd},
+ loop(InitState).
+
+loop(State) ->
+ receive
+ {{init_data,ConfigFiles,EvHandlers,LogDir,Tests},From} ->
+ ct_install(State),
+ return(From,ok),
+ loop(#state{config=ConfigFiles,event_handler=EvHandlers,
+ current_log_dir=LogDir,tests=Tests});
+ {start_page,From} ->
+ return(From,start_page1()),
+ loop(State);
+ {title_frame,From} ->
+ return(From,title_frame1()),
+ loop(State);
+ {welcome_frame,From} ->
+ return(From,welcome_frame1()),
+ loop(State);
+ {menu_frame,From} ->
+ return(From,menu_frame1()),
+ loop(State);
+ {config_frame,From} ->
+ return(From,config_frame1(State)),
+ loop(State);
+ {{add_config_file,Input},From} ->
+ {Return,State1} = add_config_file1(Input,State),
+ ct_install(State1),
+ return(From,Return),
+ loop(State1);
+ {{remove_config_file,Input},From} ->
+ {Return,State1} = remove_config_file1(Input,State),
+ ct_install(State1),
+ return(From,Return),
+ loop(State1);
+ {run_frame,From} ->
+ return(From,run_frame1(State)),
+ loop(State);
+ {{add_test_dir,Input},From} ->
+ {Return,State1} = add_test_dir1(Input,State),
+ return(From,Return),
+ loop(State1);
+ {{remove_test_dir,Input},From} ->
+ {Return,State1} = remove_test_dir1(Input,State),
+ return(From,Return),
+ loop(State1);
+ {{select_suite,Input},From} ->
+ {Return,State1} = select_suite1(Input,State),
+ return(From,Return),
+ loop(State1);
+ {{select_case,Input},From} ->
+ {Return,State1} = select_case1(Input,State),
+ return(From,Return),
+ loop(State1);
+ {run_test,From} ->
+ State1 = run_test1(State),
+ return(From,redirect_to_result_frameset1()),
+ loop(State1);
+ {result_frameset,From} ->
+ return(From,result_frameset1(State)),
+ loop(State);
+ {redirect_to_result_log_frame,From} ->
+ return(From,redirect_to_result_log_frame1(State)),
+ loop(State);
+ {result_summary_frame,From} ->
+ return(From,result_summary_frame1(State)),
+ loop(State);
+ stop_reload_results ->
+ file:set_cwd(State#state.start_dir),
+ loop(State#state{reload_results=false});
+ {no_result_log_frame,From} ->
+ return(From,no_result_log_frame1()),
+ loop(State);
+ {aborted,From} ->
+ return(From,ok),
+ loop(State#state{test_runner=undefined,running=0});
+ {{report,What,Data},From} ->
+ State1 = report1(What,Data,State),
+ return(From,ok),
+ loop(State1);
+ {stop,From} ->
+ return(From,ok);
+ {'EXIT',Pid,Reason} ->
+ case State#state.test_runner of
+ Pid -> io:format("ERROR: test runner crashed: ~p\n",[Reason]);
+ _ -> ignore
+ end,
+ loop(State);
+ {{test_info,_Type,_Data},From} ->
+ return(From,ok),
+ loop(State)
+ end.
+
+call(Msg) ->
+ case whereis(?MODULE) of
+ undefined -> {error,no_proc};
+ Pid ->
+ MRef = erlang:monitor(process,Pid),
+ Ref = make_ref(),
+ Pid ! {Msg,{self(),Ref}},
+ receive
+ {Ref, Result} ->
+ erlang:demonitor(MRef),
+ Result;
+ {'DOWN',MRef,process,_,Reason} ->
+ {error,{process_down,Pid,Reason}}
+ end
+ end.
+
+return({To,Ref},Result) ->
+ To ! {Ref, Result}.
+
+
+run_test1(State=#state{tests=Tests,current_log_dir=LogDir}) ->
+ Self=self(),
+ RunTest = fun() ->
+ case ct_run:do_run(Tests,[],LogDir) of
+ {error,_Reason} ->
+ aborted();
+ _ ->
+ ok
+ end,
+ unlink(Self)
+ end,
+
+ Pid = spawn_link(RunTest),
+
+ Total =
+ receive
+ {{test_info,start_info,{_,_,Cases}},From} ->
+ return(From,ok),
+ Cases;
+ EXIT = {'EXIT',_,_} ->
+ self() ! EXIT
+ after 30000 ->
+ 0
+ end,
+ State#state{test_runner=Pid,running=length(Tests),
+ total=Total,ok=0,fail=0,skip=0,testruns=[]}.
+
+
+ct_install(#state{config=Config,event_handler=EvHandlers,
+ current_log_dir=LogDir}) ->
+ ct_run:install([{config,Config},{event_handler,EvHandlers}],LogDir).
+
+%%%-----------------------------------------------------------------
+%%% HTML
+start_page1() ->
+ header("Visual Test Server Start Page",start_page_frameset()).
+
+start_page_frameset() ->
+ frameset(
+ "ROWS=\"60,*\"",
+ [frame(["NAME=\"title\" SRC=\"./title_frame\""]),
+ frameset(
+ "COLS=\"150,*\"",
+ [frame(["NAME=\"menu\" SRC=\"./menu_frame\""]),
+ frame(["NAME=\"main\" SRC=\"./welcome_frame\""])])]).
+
+
+title_frame1() ->
+ header(body("BGCOLOR=lightgrey TEXT=darkgreen",title_body())).
+
+title_body() ->
+ p("ALIGN=center",font("SIZE=\"+3\"",b("Visual Test Server"))).
+
+welcome_frame1() ->
+ header(body(welcome_body())).
+
+welcome_body() ->
+ table(
+ "WIDTH=100% HEIGHT=60%",
+ [tr("VALIGN=middle",
+ td("ALIGN=center",
+ font("SIZE=6",
+ ["Welcome to the",br(),
+ "Visual Test Server"])))]).
+
+menu_frame1() ->
+ header(body(menu_body())).
+
+menu_body() ->
+ [h2("Content"),
+ ul([
+ li(href(["TARGET=\"main\""],"./config_frame","Config")),
+ li(href(["TARGET=\"main\""],"./run_frame","Run")),
+ li(href(["TARGET=\"main\""],"./result_frameset","Result"))
+ ]),
+ h2("Logs"),
+ ul([
+ li(href(["TARGET=\"new\""],"/log_dir/index.html","Last Runs")),
+ li(href(["TARGET=\"new\""],"/log_dir/all_runs.html","All Runs"))
+ ])
+ ].
+
+config_frame1(State) ->
+ header("Config",body(config_body(State))).
+
+config_body(State) ->
+ Entry = [input("TYPE=file NAME=browse SIZE=40"),
+ input("TYPE=hidden NAME=file")],
+ AddForm =
+ form(
+ "NAME=read_file_form METHOD=post ACTION=\"./add_config_file\"",
+ table(
+ "BORDER=0",
+ [tr(
+ [td(Entry),
+ td("ALIGN=center",
+ input("TYPE=submit onClick=\"file.value=browse.value;\""
+ " VALUE=\"Add\""))])])),
+ {Text,RemoveForm} =
+ case State#state.config of
+ [] ->
+ T = "To be able to run any tests, one or more configuration "
+ "files must be added. Enter the name of the configuration "
+ "file below and click the \"Add\" button.",
+ R = "",
+ {T,R};
+ Files ->
+ T = "The currently known configuration files are listed below. "
+ "To add a file, type the filename in the entry and "
+ "click the \"Add\" button. "
+ "To remove a file, select it and click the \"Remove\" "
+ "button.",
+ ConfigFiles = [option(File) || File <- Files],
+ Select = select("NAME=file TITLE=\"Select Config File\""
+ " MULTIPLE=true",
+ ConfigFiles),
+ R =
+ form(["NAME=remove_config METHOD=post ",
+ "ACTION=\"./remove_config_file\""],
+ table(
+ "BORDER=0",
+ [tr(td("ALIGN=center",Select)),
+ tr(td("ALIGN=center",
+ input("TYPE=submit VALUE=\"Remove\"")))])),
+ {T,R}
+ end,
+
+ [h1("ALIGN=center","Config"),
+ table(
+ "WIDTH=600 ALIGN=center CELLPADDING=5",
+ [tr(td(["BGCOLOR=",?INFO_BG_COLOR],Text)),
+ tr(td("ALIGN=center",AddForm)),
+ tr(td("ALIGN=center",RemoveForm))])].
+
+
+add_config_file1(Input,State) ->
+ State1 =
+ case get_input_data(Input,"file") of
+ "" -> State;
+ File -> State#state{config=[File|State#state.config]}
+ end,
+ Return = config_frame1(State1),
+ {Return,State1}.
+
+remove_config_file1(Input,State) ->
+ Files = get_all_input_data(Input,"file"),
+ State1 = State#state{config=State#state.config--Files},
+ Return = config_frame1(State1),
+ {Return,State1}.
+
+
+
+run_frame1(State) ->
+ header("Run Test",body(run_body(State))).
+
+run_body(#state{running=Running}) when Running>0 ->
+ [h1("ALIGN=center","Run Test"),
+ p(["Test are ongoing: ",href("./result_frameset","Results")])];
+run_body(State) ->
+ ConfigList = ul([li(File) || File <- State#state.config]),
+ ConfigFiles = [h3("Config Files"),
+ ConfigList],
+
+ AddDirForm =
+ form(
+ "NAME=add_dir_form METHOD=post ACTION=\"./add_test_dir\"",
+ table(
+ "BORDER=0",
+ [tr(td("COLSPAN=2","Enter test directory")),
+ tr(
+ [td(input("TYPE=text NAME=dir SIZE=40")),
+ td("ALIGN=center",
+ input("TYPE=submit onClick=\"dir.value=browse.value;\""
+ " VALUE=\"Add Test Dir\""))])])),
+
+ {LoadedTestsTable,Submit} =
+ case create_testdir_entries(State#state.tests,1) of
+ [] -> {"",""};
+ TestDirs ->
+ Heading = tr([th(""),
+ th("ALIGN=left","Directory"),
+ th("ALIGN=left","Suite"),
+ th("ALIGN=left","Case")]),
+ {table("CELLPADDING=5",[Heading,TestDirs]),
+ submit_button()}
+ end,
+
+ %% It should be ok to have no config-file!
+ Body =
+ %% case State#state.config of %% [] -> %% p("ALIGN=center",
+ %% href("./config_frame","Please select one or
+ %% more config files")); %% _ ->
+ table(
+ "WIDTH=100%",
+ [tr(td(ConfigFiles)),
+ tr(td("")),
+ tr(td(AddDirForm)),
+ tr(td("")),
+ tr(td(LoadedTestsTable)),
+ tr(td(Submit))]),
+ %% end,
+
+ [h1("ALIGN=center","Run Test"), Body].
+
+create_testdir_entries([{Dir,Suite,Case}|Tests],N) ->
+ [testdir_entry(Dir,Suite,Case,N)|create_testdir_entries(Tests,N+1)];
+create_testdir_entries([],_N) ->
+ [].
+
+testdir_entry(Dir,Suite,Case,N) ->
+ NStr = integer_to_list(N),
+ tr([td(delete_button(NStr)),
+ td(Dir),
+ td(suite_select(Dir,Suite,NStr)),
+ td(case_select(Dir,Suite,Case,NStr))]).
+
+delete_button(N) ->
+ form(["NAME=remove_dir_form METHOD=post ACTION=\"./remove_test_dir\""],
+ [input(["TYPE=hidden NAME=dir VALUE=\'",N,"\'"]),
+ input(["TYPE=submit VALUE=X"])]).
+
+suite_select(Dir,Suite,N) ->
+ case filelib:wildcard(filename:join(Dir,"*_SUITE.erl")) of
+ [] ->
+ select("NAME=suite TITLE=\"Select suite\"","");
+ Suites0 ->
+ Suites = [filename:basename(filename:rootname(S)) || S <- Suites0],
+ select("NAME=suite TITLE=\"Select suite\"",
+ options(["all"|Suites],atom_to_list(Suite),N,"select_suite"))
+ end.
+
+case_select(_Dir,all,_,N) ->
+ select("NAME=case TITLE=\"Select case\"",
+ options(["all"],"all",N,"select_case"));
+case_select(Dir,Suite,Case,N) ->
+ MakeResult =
+ case application:get_env(common_test, auto_compile) of
+ {ok,false} ->
+ ok;
+ _ ->
+ UserInclude =
+ case application:get_env(common_test, include) of
+ {ok,UserInclDirs} when length(UserInclDirs) > 0 ->
+ [{i,UserInclDir} || UserInclDir <- UserInclDirs];
+ _ ->
+ []
+ end,
+ ct_run:run_make(Dir,Suite,UserInclude)
+ end,
+ case MakeResult of
+ ok ->
+ code:add_pathz(Dir),
+ case catch apply(Suite,all,[]) of
+ {'EXIT',Reason} ->
+ io:format("\n~p\n",[Reason]),
+ red(["COULD NOT READ TESTCASES!!",br(),
+ "See erlang shell for info"]);
+ {skip,_Reason} ->
+ select("NAME=case TITLE=\"Select case\"",
+ options(["all"],"all",N,"select_case"));
+ AllCasesAtoms ->
+ AllCases = [atom_to_list(C) || C <- AllCasesAtoms,
+ is_atom(C)],
+ select("NAME=case TITLE=\"Select case\"",
+ options(["all"|AllCases],atom_to_list(Case),
+ N,"select_case"))
+ end;
+ _Error ->
+ red(["COMPILATION ERROR!!",br(),
+ "See erlang shell for info",br(),
+ "Reload this page when errors are fixed"])
+ end.
+
+
+options([Selected|Elements],Selected,N,Func) ->
+ [option(["SELECTED ",
+ "onClick=\"document.location.href=\'./",Func,"?n=",N,
+ "&selected=",Selected,"\';\""],
+ Selected)|
+ options(Elements,Selected,N,Func)];
+options([Element|Elements],Selected,N,Func) ->
+ [option(["onClick=\"document.location.href=\'./",Func,"?n=",N,
+ "&selected=",Element,"\';\""],
+ Element)|
+ options(Elements,Selected,N,Func)];
+options([],_Selected,_N,_Func) ->
+ [].
+
+add_test_dir1(Input,State) ->
+ State1 =
+ case get_input_data(Input,"dir") of
+ "" -> State;
+ Dir0 ->
+ Dir = case ct_util:is_test_dir(Dir0) of
+ true ->
+ Dir0;
+ false -> filename:join(Dir0,"test")
+ end,
+ case filelib:is_dir(Dir) of
+ true ->
+ Test = ct_run:tests(Dir),
+ State#state{tests=State#state.tests++Test};
+ false ->
+ State
+ end
+ end,
+ Return = run_frame1(State1),
+ {Return,State1}.
+
+
+
+remove_test_dir1(Input,State) ->
+ N = list_to_integer(get_input_data(Input,"dir")),
+ State1 = State#state{tests=delete_test(N,State#state.tests)},
+ Return = run_frame1(State1),
+ {Return,State1}.
+
+delete_test(1,[_|T]) ->
+ T;
+delete_test(N,[H|T]) ->
+ [H|delete_test(N-1,T)].
+
+select_suite1(Input,State) ->
+ N = list_to_integer(get_input_data(Input,"n")),
+ Suite = list_to_atom(get_input_data(Input,"selected")),
+ Tests1 = replace_suite(N,Suite,State#state.tests),
+ State1 = State#state{tests=Tests1},
+ Return = run_frame1(State1),
+ {Return,State1}.
+
+replace_suite(1,Suite,[{Dir,_,_}|T]) ->
+ [Test] = ct_run:tests(Dir,Suite),
+ [Test|T];
+replace_suite(N,Suite,[H|T]) ->
+ [H|replace_suite(N-1,Suite,T)].
+
+select_case1(Input,State) ->
+ N = list_to_integer(get_input_data(Input,"n")),
+ Case = list_to_atom(get_input_data(Input,"selected")),
+ Tests1 = replace_case(N,Case,State#state.tests),
+ State1 = State#state{tests=Tests1},
+ Return = run_frame1(State1),
+ {Return,State1}.
+
+replace_case(1,Case,[{Dir,Suite,_}|T]) ->
+ [Test] = ct_run:tests(Dir,Suite,Case),
+ [Test|T];
+replace_case(N,Case,[H|T]) ->
+ [H|replace_case(N-1,Case,T)].
+
+
+submit_button() ->
+ form(["NAME=run_test_form METHOD=post ACTION=\"./run_test\""],
+ [input("TYPE=submit VALUE=\"Run Test\"")]).
+
+
+redirect_to_result_frameset1() ->
+ Head =
+ ["<META HTTP-EQUIV=\"refresh\" CONTENT=\"1; URL=./result_frameset\">"],
+ [header("",Head,body("Please wait..."))].
+
+result_frameset1(State) ->
+ header("Results",result_frameset2(State)).
+
+result_frameset2(State) ->
+ ResultLog =
+ case {State#state.current_log_dir,State#state.running} of
+ {undefined,0} ->
+ "./no_result_log_frame";
+ {undefined,_} ->
+ "./redirect_to_result_log_frame";
+ {_Dir,0} ->
+ filename:join(["/log_dir","index.html"]);
+ {_Dir,_} ->
+ {_,CurrentLog} = hd(State#state.testruns),
+ CurrentLog
+ end,
+ frameset(
+ "COLS=\"200,*\"",
+ [frame(["NAME=\"result_summary\" SRC=\"./result_summary_frame\""]),
+ frame(["NAME=\"result_log\" SRC=\"",ResultLog,"\""])]).
+
+redirect_to_result_log_frame1(State) ->
+ ResultLog =
+ case {State#state.testruns,State#state.running} of
+ {[],0} ->
+ "./no_result_log_frame";
+ {[],_} ->
+ "./redirect_to_result_log_frame";
+ {[{_,CurrentLog}|_],_} ->
+ CurrentLog
+ end,
+ Head = ["<META HTTP-EQUIV=\"refresh\" CONTENT=\"1; URL=",ResultLog,"\">"],
+ [header("",Head,body("Please wait..."))].
+
+result_summary_frame1(State) ->
+ case {State#state.running,State#state.reload_results} of
+ {0,false} ->
+ header("Result Summary",body(result_summary_body(State)));
+ _ ->
+ Head =
+ "<SCRIPT LANGUAGE=\"JavaScript1.2\">\n"
+ "\n"
+ "function startReloadInterval() {\n"
+ " intervalId = setInterval(\"reloadPage()\",5000)\n"
+ "}\n"
+ "\n"
+ "function reloadPage() {\n"
+ " location.reload()\n"
+ " parent.result_log.location.reload()\n"
+% " parent.result_log.scrollBy(0, window.innerHeight)\n"
+ "}\n"
+ "</SCRIPT>\n",
+ header("Result Summary",Head,
+ body("onLoad=\"startReloadInterval()\" BGCOLOR=\"#FFFFFF\"",
+ result_summary_body(State)))
+ end.
+
+result_summary_body(State) ->
+ N = State#state.ok + State#state.fail + State#state.skip,
+ [h2("Result Summary"),
+ p([b(integer_to_list(N))," cases executed (of ",
+ b(integer_to_list(State#state.total)),")"]),
+ p([green([b(integer_to_list(State#state.ok))," successful"]),br(),
+ red([b(integer_to_list(State#state.fail))," failed"]),br(),
+ orange([b(integer_to_list(State#state.skip))," skipped"])]),
+ executed_test_list(State)].
+
+executed_test_list(#state{testruns=[]}) ->
+ [];
+executed_test_list(#state{testruns=TestRuns}) ->
+ [h2("Executed Tests"),
+ table(
+ "",
+ [tr(td(href("TARGET=\"result_log\"",Log,Name))) ||
+ {Name,Log} <- lists:reverse(TestRuns)])].
+
+
+no_result_log_frame1() ->
+ header("Test Results",body(no_result_log_body())).
+
+no_result_log_body() ->
+ [h1("ALIGN=center","Test Results"),
+ p(["There are currently no test results available. ",
+ br(),href("TARGET=\"main\"","./run_frame","You can run tests here")])].
+
+report1(tests_start,{TestName,_N},State) ->
+ {ok,LogDir} = ct_logs:get_log_dir(),
+ TestRuns =
+ case State#state.testruns of
+ [{TestName,_}|_]=TR ->
+ TR;
+ TR ->
+ [{TestName,get_test_log(TestName,LogDir)}|TR]
+ end,
+ State#state{testruns=TestRuns};
+report1(tests_done,{_Ok,_Fail,_Skip},State) ->
+ timer:send_after(5000, self(),stop_reload_results),
+ State#state{running=State#state.running-1,reload_results=true};
+report1(tc_start,{_Suite,_Case},State) ->
+ State;
+report1(tc_done,{_Suite,init_per_suite,_},State) ->
+ State;
+report1(tc_done,{_Suite,end_per_suite,_},State) ->
+ State;
+report1(tc_done,{_Suite,_Case,ok},State) ->
+ State#state{ok=State#state.ok+1};
+report1(tc_done,{_Suite,_Case,{failed,_Reason}},State) ->
+ State#state{fail=State#state.fail+1};
+report1(tc_done,{_Suite,_Case,{skipped,_Reason}},State) ->
+ State#state{skip=State#state.skip+1};
+report1(tc_user_skip,{_Suite,_Case,_Reason},State) ->
+ State#state{skip=State#state.skip+1}.
+
+get_test_log(TestName,LogDir) ->
+ [Log] =
+ filelib:wildcard(
+ filename:join([TestName++".logs","run*","suite.log.html"])),
+ filename:join(["/log_dir",LogDir,Log]).
+
+
+
+%get_description(Suite,Case) ->
+% case erlang:function_exported(Suite,Case,0) of
+% true ->
+% case catch apply(Suite,Case,[]) of
+% {'EXIT',_Reason} ->
+% "-";
+% Info ->
+% case lists:keysearch(doc,1,Info) of
+% {value,{doc,Doc}} when is_list(Doc) ->
+% Doc;
+% _ ->
+% "-"
+% end
+% end;
+% false ->
+% "-"
+% end.
+
+%%%-----------------------------------------------------------------
+%%% Internal library
+header(Body) ->
+ header("","",Body).
+header(Title,Body) ->
+ header(Title,"",Body).
+header(Title,Head,Body) ->
+ ["Pragma:no-cache\r\n",
+ "Content-type: text/html\r\n\r\n",
+ html_header(Title,Head,Body)].
+
+html_header(Title,Head,Body) ->
+ ["<HTML>\n",
+ "<HEAD>\n",
+ "<TITLE>", Title, "</TITLE>\n",
+ Head,
+ "</HEAD>\n",
+ Body,
+ "</HTML>"].
+
+body(Text) ->
+ ["<BODY BGCOLOR=\"#FFFFFF\">\n",Text,"<\BODY>\n"].
+body(Args,Text) ->
+ ["<BODY ", Args, ">\n", Text,"<\BODY>\n"].
+
+
+frameset(Args,Frames) ->
+ ["<FRAMESET ",Args,">\n", Frames, "\n</FRAMESET>\n"].
+frame(Args) ->
+ ["<FRAME ",Args, ">\n"].
+
+table(Args,Text) ->
+ ["<TABLE ", Args, ">\n", Text, "\n</TABLE>\n"].
+tr(Text) ->
+ ["<TR>\n", Text, "\n</TR>\n"].
+tr(Args,Text) ->
+ ["<TR ", Args, ">\n", Text, "\n</TR>\n"].
+th(Text) ->
+ ["<TH>", Text, "</TH>"].
+th(Args,Text) ->
+ ["<TH ", Args, ">\n", Text, "\n</TH>\n"].
+td(Text) ->
+ ["<TD>", Text, "</TD>"].
+td(Args,Text) ->
+ ["<TD ", Args, ">", Text, "</TD>"].
+
+b(Text) ->
+ ["<B>",Text,"</B>"].
+%em(Text) ->
+% ["<EM>",Text,"</EM>\n"].
+%pre(Text) ->
+% ["<PRE>",Text,"</PRE>"].
+href(Link,Text) ->
+ ["<A HREF=\"",Link,"\">",Text,"</A>"].
+href(Args,Link,Text) ->
+ ["<A HREF=\"",Link,"\" ",Args,">",Text,"</A>"].
+form(Args,Text) ->
+ ["<FORM ",Args,">\n",Text,"\n</FORM>\n"].
+input(Args) ->
+ ["<INPUT ", Args, ">\n"].
+select(Args,Text) ->
+ ["<SELECT ", Args, ">\n", Text, "\n</SELECT>\n"].
+option(Text) ->
+ ["<OPTION>\n", Text, "\n</OPTION>\n"].
+option(Args,Text) ->
+ ["<OPTION ", Args, ">\n", Text, "\n</OPTION>\n"].
+h1(Args,Text) ->
+ ["<H1 ", Args, ">",Text,"</H1>\n"].
+h2(Text) ->
+ ["<H2>",Text,"</H2>\n"].
+h3(Text) ->
+ ["<H3>",Text,"</H3>\n"].
+font(Args,Text) ->
+ ["<FONT ",Args,">\n",Text,"\n</FONT>\n"].
+p(Text) ->
+ ["<P>",Text,"</P>\n"].
+p(Args, Text) ->
+ ["<P ", Args, ">",Text,"</P>\n"].
+ul(Text) ->
+ ["<UL>", Text, "</UL>\n"].
+li(Text) ->
+ ["<LI>", Text, "</LI>\n"].
+br() ->
+ "<BR>\n".
+
+red(Text) -> color(red,Text).
+green(Text) -> color(green,Text).
+orange(Text) -> color(orange,Text).
+color(Color,Text) when is_atom(Color) ->
+ font(["COLOR=",atom_to_list(Color)],Text).
+
+get_all_input_data(Input,Key)->
+ List = parse(Input),
+ get_all_input_data(List,Key,[]).
+get_all_input_data([{Key,Value}|List],Key,Acc) ->
+ get_all_input_data(List,Key,[Value|Acc]);
+get_all_input_data([{_OtherKey,_Value}|List],Key,Acc) ->
+ get_all_input_data(List,Key,Acc);
+get_all_input_data([],_Key,Acc) ->
+ Acc.
+
+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).
+