diff options
Diffstat (limited to 'lib/test_server/src/ts_reports.erl')
-rw-r--r-- | lib/test_server/src/ts_reports.erl | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/lib/test_server/src/ts_reports.erl b/lib/test_server/src/ts_reports.erl new file mode 100644 index 0000000000..b41291d342 --- /dev/null +++ b/lib/test_server/src/ts_reports.erl @@ -0,0 +1,543 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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% +%% + +%%% Purpose : Produces reports in HTML from the outcome of test suite runs. + +-module(ts_reports). + +-export([make_index/0, make_master_index/2, make_progress_index/2]). +-export([count_cases/1, year/0, current_time/0]). + +-include_lib("kernel/include/file.hrl"). +-include("ts.hrl"). + +-import(filename, [basename/1, rootname/1]). +-import(ts_lib, [error/1]). + + +%% Make master index page which points out index pages for all platforms. + +make_master_index(Dir, Vars) -> + IndexName = filename:join(Dir, "index.html"), + {ok, Index0} = make_master_index1(directories(Dir), master_header(Vars)), + Index = [Index0|master_footer()], + io:put_chars("Updating " ++ IndexName ++ "... "), + ok = ts_lib:force_write_file(IndexName, Index), + io:put_chars("done\n"). + +make_master_index1([Dir|Rest], Result) -> + NewResult = + case catch read_variables(Dir) of + {'EXIT',{{bad_installation,Reason},_}} -> + io:put_chars("Failed to read " ++ filename:join(Dir,?variables)++ + ": " ++ Reason ++ " - Ignoring this directory\n"), + Result; + Vars -> + Platform = ts_lib:var(platform_label, Vars), + case make_index(Dir, Vars, false) of + {ok, Summary} -> + make_master_index(Platform, Dir, Summary, Result); + {error, _} -> + Result + end + end, + make_master_index1(Rest, NewResult); +make_master_index1([], Result) -> + {ok, Result}. + +make_progress_index(Dir, Vars) -> + IndexName = filename:join(Dir, "index.html"), + io:put_chars("Updating " ++ IndexName ++ "... "), + Index0=progress_header(Vars), + ts_lib:force_delete(IndexName), + Dirs=find_progress_runs(Dir), + Index1=[Index0|make_progress_links(Dirs, [])], + IndexF=[Index1|progress_footer()], + ok = ts_lib:force_write_file(IndexName, IndexF), + io:put_chars("done\n"). + +find_progress_runs(Dir) -> + case file:list_dir(Dir) of + {ok, Dirs0} -> + Dirs1= [filename:join(Dir,X) || X <- Dirs0, + filelib:is_dir(filename:join(Dir,X))], + lists:sort(Dirs1); + _ -> + [] + end. + +name_from_vars(Dir, Platform) -> + VarFile=filename:join([Dir, Platform, "variables"]), + case file:consult(VarFile) of + {ok, Vars} -> + ts_lib:var(platform_id, Vars); + _Other -> + Platform + end. + +make_progress_links([], Acc) -> + Acc; +make_progress_links([RDir|Rest], Acc) -> + Dir=filename:basename(RDir), + Platforms=[filename:basename(X) || + X <- find_progress_runs(RDir)], + PlatformLinks=["<A HREF=\""++filename:join([Dir,X,"index.html"]) + ++"\">"++name_from_vars(RDir, X)++"</A><BR>" || + X <- Platforms], + LinkName=Dir++"/index.html", + Link = + [ + "<TR valign=top>\n", + "<TD><A HREF=\"", LinkName, "\">", Dir, "</A></TD>", "\n", + "<TD>", PlatformLinks, "</TD>", "\n" + ], + make_progress_links(Rest, [Link|Acc]). + +read_variables(Dir) -> + case file:consult(filename:join(Dir, ?variables)) of + {ok, Vars} -> Vars; + {error, Reason} -> + erlang:error({bad_installation,file:format_error(Reason)}, [Dir]) + end. + +make_master_index(Platform, Dirname, {Succ, Fail, UserSkip,AutoSkip}, Result) -> + Link = filename:join(filename:basename(Dirname), "index.html"), + FailStr = + if Fail > 0 -> + ["<FONT color=\"red\">", + integer_to_list(Fail),"</FONT>"]; + true -> + integer_to_list(Fail) + end, + AutoSkipStr = + if AutoSkip > 0 -> + ["<FONT color=\"brown\">", + integer_to_list(AutoSkip),"</FONT>"]; + true -> integer_to_list(AutoSkip) + end, + [Result, + "<TR valign=top>\n", + "<TD><A HREF=\"", Link, "\">", Platform, "</A></TD>", "\n", + make_row(integer_to_list(Succ), false), + make_row(FailStr, false), + make_row(integer_to_list(UserSkip), false), + make_row(AutoSkipStr, false), + "</TR>\n"]. + +%% Make index page which points out individual test suites for a single platform. + +make_index() -> + {ok, Pwd} = file:get_cwd(), + Vars = read_variables(Pwd), + make_index(Pwd, Vars, true). + +make_index(Dir, Vars, IncludeLast) -> + IndexName = filename:absname("index.html", Dir), + io:put_chars("Updating " ++ IndexName ++ "... "), + case catch make_index1(Dir, IndexName, Vars, IncludeLast) of + {'EXIT', Reason} -> + io:put_chars("CRASHED!\n"), + io:format("~p~n", [Reason]), + {error, Reason}; + {error, Reason} -> + io:put_chars("FAILED\n"), + io:format("~p~n", [Reason]), + {error, Reason}; + {ok, Summary} -> + io:put_chars("done\n"), + {ok, Summary}; + Err -> + io:format("Unknown internal error. Please report.\n(Err: ~p, ID: 1)", + [Err]), + {error, Err} + end. + +make_index1(Dir, IndexName, Vars, IncludeLast) -> + Logs0 = ts_lib:interesting_logs(Dir), + Logs = + case IncludeLast of + true -> add_last_name(Logs0); + false -> Logs0 + end, + {ok, {Index0, Summary}} = make_index(Logs, header(Vars), 0, 0, 0, 0, 0), + Index = [Index0|footer()], + case ts_lib:force_write_file(IndexName, Index) of + ok -> + {ok, Summary}; + {error, Reason} -> + error({index_write_error, Reason}) + end. + +make_index([Name|Rest], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt) -> + case ts_lib:last_test(Name) of + false -> + %% Silently skip. + make_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt); + Last -> + case count_cases(Last) of + {Succ, Fail, USkip, ASkip} -> + Cov = + case file:read_file(filename:join(Last,?cover_total)) of + {ok,Bin} -> + TotCoverage = binary_to_term(Bin), + io_lib:format("~w %",[TotCoverage]); + _error -> + "" + end, + Link = filename:join(basename(Name), basename(Last)), + JustTheName = rootname(basename(Name)), + NotBuilt = not_built(JustTheName), + NewResult = [Result, make_index1(JustTheName, + Link, Succ, Fail, USkip, ASkip, + NotBuilt, Cov, false)], + make_index(Rest, NewResult, TotSucc+Succ, TotFail+Fail, + UserSkip+USkip, AutoSkip+ASkip, TotNotBuilt+NotBuilt); + error -> + make_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip, + TotNotBuilt) + end + end; +make_index([], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt) -> + {ok, {[Result|make_index1("Total", no_link, + TotSucc, TotFail, UserSkip, AutoSkip, + TotNotBuilt, "", true)], + {TotSucc, TotFail, UserSkip, AutoSkip}}}. + +make_index1(SuiteName, Link, Success, Fail, UserSkip, AutoSkip, NotBuilt, Coverage, Bold) -> + Name = test_suite_name(SuiteName), + FailStr = + if Fail > 0 -> + ["<FONT color=\"red\">", + integer_to_list(Fail),"</FONT>"]; + true -> + integer_to_list(Fail) + end, + AutoSkipStr = + if AutoSkip > 0 -> + ["<FONT color=\"brown\">", + integer_to_list(AutoSkip),"</FONT>"]; + true -> integer_to_list(AutoSkip) + end, + ["<TR valign=top>\n", + "<TD>", + case Link of + no_link -> + ["<B>", Name|"</B>"]; + _Other -> + CrashDumpName = SuiteName ++ "_erl_crash.dump", + CrashDumpLink = + case filelib:is_file(CrashDumpName) of + true -> + [" <A HREF=\"", CrashDumpName, + "\">(CrashDump)</A>"]; + false -> + "" + end, + LogFile = filename:join(Link, ?suitelog_name ++ ".html"), + ["<A HREF=\"", LogFile, "\">", Name, "</A>\n", CrashDumpLink, + "</TD>\n"] + end, + make_row(integer_to_list(Success), Bold), + make_row(FailStr, Bold), + make_row(integer_to_list(UserSkip), Bold), + make_row(AutoSkipStr, Bold), + make_row(integer_to_list(NotBuilt), Bold), + make_row(Coverage, Bold), + "</TR>\n"]. + +make_row(Row, true) -> + ["<TD ALIGN=right><B>", Row|"</B></TD>"]; +make_row(Row, false) -> + ["<TD ALIGN=right>", Row|"</TD>"]. + +not_built(BaseName) -> + Dir = filename:join("..", BaseName++"_test"), + Erl = length(filelib:wildcard(filename:join(Dir,"*_SUITE.erl"))), + Beam = length(filelib:wildcard(filename:join(Dir,"*_SUITE.beam"))), + Erl-Beam. + + +%% Add the log file directory for the very last test run (according to +%% last_name). + +add_last_name(Logs) -> + case file:read_file("last_name") of + {ok, Bin} -> + Name = filename:dirname(lib:nonl(binary_to_list(Bin))), + case lists:member(Name, Logs) of + true -> Logs; + false -> [Name|Logs] + end; + _ -> + Logs + end. + +term_to_text(Term) -> + lists:flatten(io_lib:format("~p.\n", [Term])). + +test_suite_name(Name) -> + ts_lib:initial_capital(Name) ++ " suite". + +directories(Dir) -> + {ok, Files} = file:list_dir(Dir), + [filename:join(Dir, X) || X <- Files, + filelib:is_dir(filename:join(Dir, X))]. + + +%%% Headers and footers. + +header(Vars) -> + Platform = ts_lib:var(platform_id, Vars), + ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" + "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" + "<HTML>\n", + "<HEAD>\n", + "<TITLE>Test Results for ", Platform, "</TITLE>\n", + "</HEAD>\n", + + body_tag(), + + "<!-- ---- DOCUMENT TITLE ---- -->\n", + + "<CENTER>\n", + "<H1>Test Results for ", Platform, "</H1>\n", + "</CENTER>\n", + + "<!-- ---- CONTENT ---- -->\n", + "<CENTER>\n", + + "<TABLE border=3 cellpadding=5>\n", + "<th><B>Family</B></th>\n", + "<th>Successful</th>\n", + "<th>Failed</th>\n", + "<th>User Skipped</th>\n" + "<th>Auto Skipped</th>\n" + "<th>Missing Suites</th>\n" + "<th>Coverage</th>\n" + "\n"]. + +footer() -> + ["</TABLE>\n" + "</CENTER>\n" + "<P><CENTER>\n" + "<HR>\n" + "<P><FONT SIZE=-1>\n" + "Copyright © ", year(), + " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n" + "Updated: <!date>", current_time(), "<!/date><BR>\n" + "</FONT>\n" + "</CENTER>\n" + "</body>\n" + "</HTML>\n"]. + +progress_header(Vars) -> + Release = ts_lib:var(erl_release, Vars), + ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" + "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" + "<HTML>\n", + "<HEAD>\n", + "<TITLE>", Release, " Progress Test Results</TITLE>\n", + "</HEAD>\n", + + body_tag(), + + "<!-- ---- DOCUMENT TITLE ---- -->\n", + + "<CENTER>\n", + "<H1>", Release, " Progress Test Results</H1>\n", + "<TABLE border=3 cellpadding=5>\n", + "<th><b>Test Run</b></th><th>Platforms</th>\n"]. + +progress_footer() -> + ["</TABLE>\n", + "</CENTER>\n", + "<P><CENTER>\n", + "<HR>\n", + "<P><FONT SIZE=-1>\n", + "Copyright © ", year(), + " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n", + "Updated: <!date>", current_time(), "<!/date><BR>\n", + "</FONT>\n", + "</CENTER>\n", + "</body>\n", + "</HTML>\n"]. + +master_header(Vars) -> + Release = ts_lib:var(erl_release, Vars), + Vsn = erlang:system_info(version), + ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n" + "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n" + "<HTML>\n", + "<HEAD>\n", + "<TITLE>", Release, " Test Results (", Vsn, ")</TITLE>\n", + "</HEAD>\n", + + body_tag(), + + "<!-- ---- DOCUMENT TITLE ---- -->\n", + + "<CENTER>\n", + "<H1>", Release, " Test Results (", Vsn, ")</H1>\n", + "</CENTER>\n", + + "<!-- ---- CONTENT ---- -->\n", + + "<CENTER>\n", + + "<TABLE border=3 cellpadding=5>\n", + "<th><b>Platform</b></th>\n", + "<th>Successful</th>\n", + "<th>Failed</th>\n", + "<th>User Skipped</th>\n" + "<th>Auto Skipped</th>\n" + "\n"]. + +master_footer() -> + ["</TABLE>\n", + "</CENTER>\n", + "<P><CENTER>\n", + "<HR>\n", + "<P><FONT SIZE=-1>\n", + "Copyright © ", year(), + " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n", + "Updated: <!date>", current_time(), "<!/date><BR>\n", + "</FONT>\n", + "</CENTER>\n", + "</body>\n", + "</HTML>\n"]. + +body_tag() -> + "<body bgcolor=\"#FFFFFF\" text=\"#000000\" link=\"#0000FF\"" + "vlink=\"#800080\" alink=\"#FF0000\">". + +year() -> + {Y, _, _} = date(), + integer_to_list(Y). + +current_time() -> + {{Y, Mon, D}, {H, Min, S}} = calendar:local_time(), + Weekday = weekday(calendar:day_of_the_week(Y, Mon, D)), + lists:flatten(io_lib:format("~s ~s ~p ~2.2.0w:~2.2.0w:~2.2.0w ~w", + [Weekday, month(Mon), D, H, Min, S, Y])). + +weekday(1) -> "Mon"; +weekday(2) -> "Tue"; +weekday(3) -> "Wed"; +weekday(4) -> "Thu"; +weekday(5) -> "Fri"; +weekday(6) -> "Sat"; +weekday(7) -> "Sun". + +month(1) -> "Jan"; +month(2) -> "Feb"; +month(3) -> "Mar"; +month(4) -> "Apr"; +month(5) -> "May"; +month(6) -> "Jun"; +month(7) -> "Jul"; +month(8) -> "Aug"; +month(9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +%% Count test cases in the given directory (a directory of the type +%% run.1997-08-04_09.58.52). + +count_cases(Dir) -> + SumFile = filename:join(Dir, ?run_summary), + case read_summary(SumFile, [summary]) of + {ok, [{Succ,Fail,Skip}]} -> + {Succ,Fail,Skip,0}; + {ok, [Summary]} -> + Summary; + {error, _} -> + LogFile = filename:join(Dir, ?suitelog_name), + case file:read_file(LogFile) of + {ok, Bin} -> + Summary = count_cases1(binary_to_list(Bin), {0, 0, 0, 0}), + write_summary(SumFile, Summary), + Summary; + {error, _Reason} -> + io:format("\nFailed to read ~p (skipped)\n", [LogFile]), + error + end + end. + +write_summary(Name, Summary) -> + File = [term_to_text({summary, Summary})], + ts_lib:force_write_file(Name, File). + +% XXX: This function doesn't do what the writer expect. It can't handle +% the case if there are several different keys and I had to add a special +% case for the empty file. The caller also expect just one tuple as +% a result so this function is written way to general for no reason. +% But it works sort of. /kgb + +read_summary(Name, Keys) -> + case file:consult(Name) of + {ok, []} -> + {error, "Empty summary file"}; + {ok, Terms} -> + {ok, lists:map(fun(Key) -> {value, {_, Value}} = + lists:keysearch(Key, 1, Terms), + Value end, + Keys)}; + {error, Reason} -> + {error, Reason} + end. + +count_cases1("=failed" ++ Rest, {Success, _Fail, UserSkip,AutoSkip}) -> + {NextLine, Count} = get_number(Rest), + count_cases1(NextLine, {Success, Count, UserSkip,AutoSkip}); +count_cases1("=successful" ++ Rest, {_Success, Fail, UserSkip,AutoSkip}) -> + {NextLine, Count} = get_number(Rest), + count_cases1(NextLine, {Count, Fail, UserSkip,AutoSkip}); +count_cases1("=skipped" ++ Rest, {Success, Fail, _UserSkip,AutoSkip}) -> + {NextLine, Count} = get_number(Rest), + count_cases1(NextLine, {Success, Fail, Count,AutoSkip}); +count_cases1("=user_skipped" ++ Rest, {Success, Fail, _UserSkip,AutoSkip}) -> + {NextLine, Count} = get_number(Rest), + count_cases1(NextLine, {Success, Fail, Count,AutoSkip}); +count_cases1("=auto_skipped" ++ Rest, {Success, Fail, UserSkip,_AutoSkip}) -> + {NextLine, Count} = get_number(Rest), + count_cases1(NextLine, {Success, Fail, UserSkip,Count}); +count_cases1([], Counters) -> + Counters; +count_cases1(Other, Counters) -> + count_cases1(skip_to_nl(Other), Counters). + +get_number([$\s|Rest]) -> + get_number(Rest); +get_number([Digit|Rest]) when $0 =< Digit, Digit =< $9 -> + get_number(Rest, Digit-$0). + +get_number([Digit|Rest], Acc) when $0 =< Digit, Digit =< $9 -> + get_number(Rest, Acc*10+Digit-$0); +get_number([$\n|Rest], Acc) -> + {Rest, Acc}; +get_number([_|Rest], Acc) -> + get_number(Rest, Acc). + +skip_to_nl([$\n|Rest]) -> + Rest; +skip_to_nl([_|Rest]) -> + skip_to_nl(Rest); +skip_to_nl([]) -> + []. |