From 7a05d84c9d6664573a34485e3441b3c9542ed25b Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Fri, 22 Jan 2016 02:05:30 +0100 Subject: Make sure special characters are escaped in e.g. pal and log printouts --- lib/common_test/src/ct.erl | 75 ++++++++--- lib/common_test/src/ct_framework.erl | 33 ++--- lib/common_test/src/ct_logs.erl | 225 +++++++++++++++++++------------ lib/common_test/src/ct_master_logs.erl | 13 +- lib/test_server/src/erl2html2.erl | 2 +- lib/test_server/src/test_server.erl | 54 +++++--- lib/test_server/src/test_server_ctrl.erl | 57 ++++++-- lib/test_server/src/test_server_gl.erl | 10 +- lib/test_server/test/erl2html2_SUITE.erl | 2 +- 9 files changed, 316 insertions(+), 155 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 7958a349b4..1c1b46c2af 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -64,7 +64,8 @@ -export([require/1, require/2, get_config/1, get_config/2, get_config/3, reload_config/1, - log/1, log/2, log/3, log/4, + escape_chars/1, escape_chars/2, + log/1, log/2, log/3, log/4, log/5, print/1, print/2, print/3, print/4, pal/1, pal/2, pal/3, pal/4, capture_start/0, capture_stop/0, capture_get/0, capture_get/1, @@ -508,45 +509,89 @@ get_testspec_terms(Tags) -> end. +%%%----------------------------------------------------------------- +%%% @spec escape_chars(IoList1) -> IoList2 | {error,Reason} +%%% IoList1 = iolist() +%%% IoList2 = iolist() +%%% +%%% @doc Escape special characters to be printed in html log +%%% +escape_chars(IoList) -> + ct_logs:escape_chars(IoList). + +%%%----------------------------------------------------------------- +%%% @spec escape_chars(Format, Args) -> IoList | {error,Reason} +%%% Format = string() +%%% Args = list() +%%% +%%% @doc Escape special characters to be printed in html log +%%% +escape_chars(Format, Args) -> + try io_lib:format(Format, Args) of + IoList -> + ct_logs:escape_chars(IoList) + catch + _:Reason -> + {error,Reason} + end. + %%%----------------------------------------------------------------- %%% @spec log(Format) -> ok -%%% @equiv log(default,50,Format,[]) +%%% @equiv log(default,50,Format,[],[]) log(Format) -> - log(default,?STD_IMPORTANCE,Format,[]). + log(default,?STD_IMPORTANCE,Format,[],[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2) -> ok %%% X1 = Category | Importance | Format %%% X2 = Format | Args -%%% @equiv log(Category,Importance,Format,Args) +%%% @equiv log(Category,Importance,Format,Args,[]) log(X1,X2) -> {Category,Importance,Format,Args} = if is_atom(X1) -> {X1,?STD_IMPORTANCE,X2,[]}; is_integer(X1) -> {default,X1,X2,[]}; is_list(X1) -> {default,?STD_IMPORTANCE,X1,X2} end, - log(Category,Importance,Format,Args). + log(Category,Importance,Format,Args,[]). %%%----------------------------------------------------------------- %%% @spec log(X1,X2,X3) -> ok +%%% X1 = Category | Importance | Format +%%% X2 = Importance | Format | Args +%%% X3 = Format | Args | Opts +%%% @equiv log(Category,Importance,Format,Args,Opts) +log(X1,X2,X3) -> + {Category,Importance,Format,Args,Opts} = + if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[],[]}; + is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3,[]}; + is_integer(X1) -> {default,X1,X2,X3,[]}; + is_list(X1), is_list(X2) -> {default,?STD_IMPORTANCE,X1,X2,X3} + end, + log(Category,Importance,Format,Args,Opts). + +%%%----------------------------------------------------------------- +%%% @spec log(X1,X2,X3,X4) -> ok %%% X1 = Category | Importance %%% X2 = Importance | Format %%% X3 = Format | Args -%%% @equiv log(Category,Importance,Format,Args) -log(X1,X2,X3) -> - {Category,Importance,Format,Args} = - if is_atom(X1), is_integer(X2) -> {X1,X2,X3,[]}; - is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3}; - is_integer(X1) -> {default,X1,X2,X3} +%%% X4 = Args | Opts +%%% @equiv log(Category,Importance,Format,Args,Opts) +log(X1,X2,X3,X4) -> + {Category,Importance,Format,Args,Opts} = + if is_atom(X1), is_integer(X2) -> {X1,X2,X3,X4,[]}; + is_atom(X1), is_list(X2) -> {X1,?STD_IMPORTANCE,X2,X3,X4}; + is_integer(X1) -> {default,X1,X2,X3,X4} end, - log(Category,Importance,Format,Args). + log(Category,Importance,Format,Args,Opts). %%%----------------------------------------------------------------- -%%% @spec log(Category,Importance,Format,Args) -> ok +%%% @spec log(Category,Importance,Format,Args,Opts) -> ok %%% Category = atom() %%% Importance = integer() %%% Format = string() %%% Args = list() +%%% Opts = [Opt] +%%% Opt = esc_chars %%% %%% @doc Printout from a test case to the log file. %%% @@ -558,8 +603,8 @@ log(X1,X2,X3) -> %%% and default value for Args is [].

%%%

Please see the User's Guide for details on Category %%% and Importance.

-log(Category,Importance,Format,Args) -> - ct_logs:tc_log(Category,Importance,Format,Args). +log(Category,Importance,Format,Args,Opts) -> + ct_logs:tc_log(Category,Importance,Format,Args,Opts). %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index f792269c41..a293286053 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -831,13 +831,13 @@ tag(_Other) -> %%% Func in suite Mod crashing. %%% Error specifies the reason for failing. error_notification(Mod,Func,_Args,{Error,Loc}) -> - ErrSpec = case Error of + ErrorSpec = case Error of {What={_E,_R},Trace} when is_list(Trace) -> What; What -> What end, - ErrStr = case ErrSpec of + ErrorStr = case ErrorSpec of {badmatch,Descr} -> Descr1 = lists:flatten(io_lib:format("~P",[Descr,10])), if length(Descr1) > 50 -> @@ -859,7 +859,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> Other -> io_lib:format("~P", [Other,5]) end, - ErrorHtml = "" ++ ErrStr ++ "", + ErrorHtml = + "" ++ ct_logs:escape_chars(ErrorStr) ++ "", case {Mod,Error} of %% some notifications come from the main test_server process %% and for these cases the existing comment may not be modified @@ -887,41 +888,43 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> end end, - PrintErr = fun(ErrFormat, ErrArgs) -> + PrintError = fun(ErrorFormat, ErrorArgs) -> Div = "~n- - - - - - - - - - - - - - - - - - - " "- - - - - - - - - - - - - - - - - - - - -~n", - io:format(user, lists:concat([Div,ErrFormat,Div,"~n"]), - ErrArgs), + ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs), + io:format(user, lists:concat([Div,ErrorStr2,Div,"~n"]), + []), Link = "\n\n" "Full error description and stacktrace" "", + ErrorHtml2 = ct_logs:escape_chars(ErrorStr2), ct_logs:tc_log(ct_error_notify, ?MAX_IMPORTANCE, "CT Error Notification", - ErrFormat++Link, ErrArgs) + ErrorHtml2++Link, [], []) end, case Loc of [{?MODULE,error_in_suite}] -> - PrintErr("Error in suite detected: ~ts", [ErrStr]); + PrintError("Error in suite detected: ~ts", [ErrorStr]); R when R == unknown; R == undefined -> - PrintErr("Error detected: ~ts", [ErrStr]); + PrintError("Error detected: ~ts", [ErrorStr]); %% if a function specified by all/0 does not exist, we %% pick up undef here - [{LastMod,LastFunc}|_] when ErrStr == "undef" -> - PrintErr("~w:~w could not be executed~nReason: ~ts", - [LastMod,LastFunc,ErrStr]); + [{LastMod,LastFunc}|_] when ErrorStr == "undef" -> + PrintError("~w:~w could not be executed~nReason: ~ts", + [LastMod,LastFunc,ErrorStr]); [{LastMod,LastFunc}|_] -> - PrintErr("~w:~w failed~nReason: ~ts", [LastMod,LastFunc,ErrStr]); + PrintError("~w:~w failed~nReason: ~ts", [LastMod,LastFunc,ErrorStr]); [{LastMod,LastFunc,LastLine}|_] -> %% print error to console, we are only %% interested in the last executed expression - PrintErr("~w:~w failed on line ~w~nReason: ~ts", - [LastMod,LastFunc,LastLine,ErrStr]), + PrintError("~w:~w failed on line ~w~nReason: ~ts", + [LastMod,LastFunc,LastLine,ErrorStr]), case ct_util:read_suite_data({seq,Mod,Func}) of undefined -> diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 78081380e7..d9e9328bbd 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -37,15 +37,17 @@ -export([add_external_logs/1, add_link/3]). -export([make_last_run_index/0]). -export([make_all_suites_index/1,make_all_runs_index/1]). --export([get_ts_html_wrapper/5]). +-export([get_ts_html_wrapper/5, escape_chars/1]). -export([xhtml/2, locate_priv_file/1, make_relative/1]). -export([insert_javascript/1]). -export([uri/1]). %% Logging stuff directly from testcase --export([tc_log/3, tc_log/4, tc_log/5, tc_log_async/3, tc_log_async/5, +-export([tc_log/3, tc_log/4, tc_log/5, tc_log/6, + tc_log_async/3, tc_log_async/5, tc_print/3, tc_print/4, - tc_pal/3, tc_pal/4, ct_log/3, basic_html/0]). + tc_pal/3, tc_pal/4, ct_log/3, + basic_html/0]). %% Simulate logger process for use without ct environment running -export([simulate/0]). @@ -314,9 +316,10 @@ unregister_groupleader(Pid) -> %%% data to log (as in io:format(Format,Args)).

log(Heading,Format,Args) -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_header(),[log_timestamp(?now),Heading]}, + [{hd,int_header(),[log_timestamp(?now),Heading]}, {Format,Args}, - {int_footer(),[]}]}), + {ft,int_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- @@ -336,7 +339,7 @@ log(Heading,Format,Args) -> %%% @see end_log/0 start_log(Heading) -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_header(),[log_timestamp(?now),Heading]}]}), + [{hd,int_header(),[log_timestamp(?now),Heading]}],false}), ok. %%%----------------------------------------------------------------- @@ -351,7 +354,7 @@ cont_log([],[]) -> cont_log(Format,Args) -> maybe_log_timestamp(), cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{Format,Args}]}), + [{Format,Args}],true}), ok. %%%----------------------------------------------------------------- @@ -363,7 +366,7 @@ cont_log(Format,Args) -> %%% @see cont_log/2 end_log() -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{int_footer(), []}]}), + [{ft,int_footer(), []}],false}), ok. @@ -400,32 +403,40 @@ add_link(Heading,File,Type) -> %%% @spec tc_log(Category,Format,Args) -> ok %%% @equiv tc_log(Category,?STD_IMPORTANCE,Format,Args) tc_log(Category,Format,Args) -> - tc_log(Category,?STD_IMPORTANCE,Format,Args). + tc_log(Category,?STD_IMPORTANCE,"User",Format,Args,[]). %%%----------------------------------------------------------------- %%% @spec tc_log(Category,Importance,Format,Args) -> ok %%% @equiv tc_log(Category,Importance,"User",Format,Args) tc_log(Category,Importance,Format,Args) -> - tc_log(Category,Importance,"User",Format,Args). + tc_log(Category,Importance,"User",Format,Args,[]). %%%----------------------------------------------------------------- -%%% @spec tc_log(Category,Importance,Printer,Format,Args) -> ok +%%% @spec tc_log(Category,Importance,Format,Args) -> ok +%%% @equiv tc_log(Category,Importance,"User",Format,Args) +tc_log(Category,Importance,Format,Args,Opts) -> + tc_log(Category,Importance,"User",Format,Args,Opts). + +%%%----------------------------------------------------------------- +%%% @spec tc_log(Category,Importance,Printer,Format,Args,Opts) -> ok %%% Category = atom() %%% Importance = integer() %%% Printer = string() %%% Format = string() %%% Args = list() +%%% Opts = list() %%% %%% @doc Printout from a testcase. %%% %%%

This function is called by ct when logging %%% stuff directly from a testcase (i.e. not from within the CT %%% framework).

-tc_log(Category,Importance,Printer,Format,Args) -> +tc_log(Category,Importance,Printer,Format,Args,Opts) -> cast({log,sync,self(),group_leader(),Category,Importance, - [{div_header(Category,Printer),[]}, + [{hd,div_header(Category,Printer),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + lists:member(esc_chars, Opts)}), ok. %%%----------------------------------------------------------------- @@ -451,9 +462,10 @@ tc_log_async(Category,Format,Args) -> %%% asks ct_logs for an html wrapper.

tc_log_async(Category,Importance,Printer,Format,Args) -> cast({log,async,self(),group_leader(),Category,Importance, - [{div_header(Category,Printer),[]}, + [{hd,div_header(Category,Printer),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- %%% @spec tc_print(Category,Format,Args) @@ -522,43 +534,45 @@ tc_pal(Category,Format,Args) -> tc_pal(Category,Importance,Format,Args) -> tc_print(Category,Importance,Format,Args), cast({log,sync,self(),group_leader(),Category,Importance, - [{div_header(Category),[]}, + [{hd,div_header(Category),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%----------------------------------------------------------------- -%%% @spec ct_pal(Category,Format,Args) -> ok +%%% @spec ct_log(Category,Format,Args) -> ok %%% Category = atom() %%% Format = string() %%% Args = list() %%% -%%% @doc Print and log to the ct framework log +%%% @doc Print to the ct framework log %%% %%%

This function is called by internal ct functions to %%% force logging to the ct framework log

ct_log(Category,Format,Args) -> - cast({ct_log,[{div_header(Category),[]}, + cast({ct_log,[{hd,div_header(Category),[]}, {Format,Args}, - {div_footer(),[]}]}), + {ft,div_footer(),[]}], + true}), ok. %%%================================================================= %%% Internal functions int_header() -> - "
*** CT ~s *** ~ts". + "\n
*** CT ~s *** ~ts".
 int_footer() ->
-    "
". + "
\n
".
 
 div_header(Class) ->
     div_header(Class,"User").
 div_header(Class,Printer) ->
-    "\n
*** " ++ Printer ++ - " " ++ log_timestamp(?now) ++ " ***". + "\n
\n
*** "
+	++ Printer ++ " " ++ log_timestamp(?now) ++ " ***".
 div_footer() ->
-    "
". + "\n
".
 
 
 maybe_log_timestamp() ->
@@ -568,7 +582,7 @@ maybe_log_timestamp() ->
 	    ok;
 	_ ->
 	    cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE,
-		  [{"~s",[log_timestamp({MS,S,US})]}]})
+		  [{hd,"~s",[log_timestamp({MS,S,US})]}],false})
     end.
 
 log_timestamp({MS,S,US}) ->
@@ -729,7 +743,7 @@ copy_priv_files([], []) ->
 
 logger_loop(State) ->
     receive
-	{log,SyncOrAsync,Pid,GL,Category,Importance,List} ->
+	{log,SyncOrAsync,Pid,GL,Category,Importance,Content,EscChars} ->
 	    VLvl = case Category of
 		       ct_internal ->
 			   ?MAX_VERBOSITY;
@@ -746,15 +760,15 @@ logger_loop(State) ->
 			    case erlang:is_process_alive(TCGL) of
 				true ->
 				    State1 = print_to_log(SyncOrAsync, Pid,
-							  Category,
-							  TCGL, List, State),
+							  Category, TCGL, Content,
+							  EscChars, State),
 				    logger_loop(State1#logger_state{
 						  tc_groupleaders = TCGLs});
 				false ->
 				    %% Group leader is dead, so write to the
 				    %% CtLog or unexpected_io log instead
-				    unexpected_io(Pid,Category,Importance,
-						  List,CtLogFd),
+				    unexpected_io(Pid, Category, Importance,
+						  Content, CtLogFd, EscChars),
 
 				    logger_loop(State)			    
 			    end;
@@ -762,7 +776,8 @@ logger_loop(State) ->
 			    %% If category is ct_internal then write
 			    %% to ct_log, else write to unexpected_io
 			    %% log
-			    unexpected_io(Pid,Category,Importance,List,CtLogFd),
+			    unexpected_io(Pid, Category, Importance, Content,
+					  CtLogFd, EscChars),
 			    logger_loop(State#logger_state{
 					  tc_groupleaders = TCGLs})
 		    end;
@@ -818,10 +833,17 @@ logger_loop(State) ->
 	    logger_loop(State);
 	{clear_stylesheet,_} ->
 	    logger_loop(State#logger_state{stylesheet = undefined});
-	{ct_log, List} ->
+	{ct_log,Content,EscChars} ->
+	    Str = lists:map(fun({_HdOrFt,Str,Args}) ->
+				    [io_lib:format(Str,Args),io_lib:nl()];
+			       ({Str,Args}) when EscChars ->
+				    Io = io_lib:format(Str,Args),
+				    [escape_chars(Io),io_lib:nl()];
+			       ({Str,Args}) ->
+				    [io_lib:format(Str,Args),io_lib:nl()]
+			    end, Content),
 	    Fd = State#logger_state.ct_log_fd,
-	    [begin io:format(Fd,Str,Args),io:nl(Fd) end ||
-				{Str,Args} <- List],
+	    io:format(Fd, "~ts", [Str]),
 	    logger_loop(State);
 	{'DOWN',Ref,_,_Pid,_} ->
 	    %% there might be print jobs executing in parallel with ct_logs
@@ -843,45 +865,74 @@ logger_loop(State) ->
 	    ok
     end.
 
-create_io_fun(FromPid, CtLogFd) ->
+create_io_fun(FromPid, CtLogFd, EscChars) ->
     %% we have to build one io-list of all strings
     %% before printing, or other io printouts (made in
     %% parallel) may get printed between this header 
     %% and footer
-    fun({Str,Args}, IoList) ->
-	    case catch io_lib:format(Str,Args) of
-		{'EXIT',_Reason} ->
+    fun(FormatData, IoList) ->
+	    {Escapable,Str,Args} =
+		case FormatData of
+		    {_HdOrFt,S,A} -> {false,S,A};
+		    {S,A}         -> {true,S,A}
+		end,
+	    try io_lib:format(Str,Args) of
+		IoStr when Escapable, EscChars, IoList == [] ->
+		    escape_chars(IoStr);
+		IoStr when Escapable, EscChars ->
+		    [IoList,"\n",escape_chars(IoStr)];
+		IoStr when IoList == [] ->
+		    IoStr;
+		IoStr ->
+		    [IoList,"\n",IoStr]
+	    catch
+		_:_Reason ->
 		    io:format(CtLogFd, "Logging fails! Str: ~p, Args: ~p~n",
 			      [Str,Args]),
 		    %% stop the testcase, we need to see the fault
 		    exit(FromPid, {log_printout_error,Str,Args}),
-		    [];
-		IoStr when IoList == [] ->
-		    [IoStr];
-		IoStr ->
-		    [IoList,"\n",IoStr]
+		    []
 	    end
     end.
 
-print_to_log(sync, FromPid, Category, TCGL, List, State) ->
+escape_chars([Bin | Io]) when is_binary(Bin) ->
+    [Bin | escape_chars(Io)];
+escape_chars([List | Io]) when is_list(List) ->
+    [escape_chars(List) | escape_chars(Io)];
+escape_chars([$< | Io]) ->
+    ["<" | escape_chars(Io)];
+escape_chars([$> | Io]) ->
+    [">" | escape_chars(Io)];
+escape_chars([$& | Io]) ->
+    ["&" | escape_chars(Io)];
+escape_chars([Char | Io]) when is_integer(Char) ->
+    [Char | escape_chars(Io)];
+escape_chars([]) ->
+    [];
+escape_chars(Bin) ->
+    Bin.
+
+print_to_log(sync, FromPid, Category, TCGL, Content, EscChars, State) ->
     %% in some situations (exceptions), the printout is made from the
     %% test server IO process and there's no valid group leader to send to
     CtLogFd = State#logger_state.ct_log_fd,
     if FromPid /= TCGL ->
-	    IoFun = create_io_fun(FromPid, CtLogFd),
-	    io:format(TCGL,"~ts", [lists:foldl(IoFun, [], List)]);
+	    IoFun = create_io_fun(FromPid, CtLogFd, EscChars),
+	    IoList = lists:foldl(IoFun, [], Content),
+	    io:format(TCGL,"~ts", [IoList]);
        true ->
-	    unexpected_io(FromPid,Category,?MAX_IMPORTANCE,List,CtLogFd)
+	    unexpected_io(FromPid, Category, ?MAX_IMPORTANCE, Content,
+			  CtLogFd, EscChars)
     end,
     State;
 
-print_to_log(async, FromPid, Category, TCGL, List, State) ->
+print_to_log(async, FromPid, Category, TCGL, Content, EscChars, State) ->
     %% in some situations (exceptions), the printout is made from the
     %% test server IO process and there's no valid group leader to send to
     CtLogFd = State#logger_state.ct_log_fd,
     Printer =
 	if FromPid /= TCGL ->
-		IoFun = create_io_fun(FromPid, CtLogFd),
+		IoFun = create_io_fun(FromPid, CtLogFd, EscChars),
 		fun() ->
 			test_server:permit_io(TCGL, self()),
 
@@ -895,24 +946,24 @@ print_to_log(async, FromPid, Category, TCGL, List, State) ->
 			case erlang:is_process_alive(TCGL) of
 			    true ->
 				try io:format(TCGL, "~ts",
-					      [lists:foldl(IoFun,[],List)]) of
+					      [lists:foldl(IoFun,[],Content)]) of
 				    _ -> ok
 				catch
 				    _:terminated ->
 					unexpected_io(FromPid, Category,
 						      ?MAX_IMPORTANCE,
-						      List, CtLogFd)
+						      Content, CtLogFd, EscChars)
 				end;
 			    false ->
 				unexpected_io(FromPid, Category,
 					      ?MAX_IMPORTANCE,
-					      List, CtLogFd)
+					      Content, CtLogFd, EscChars)
 			end
 		end;
 	   true ->
 		fun() ->
 			unexpected_io(FromPid, Category, ?MAX_IMPORTANCE,
-				      List, CtLogFd)
+				      Content, CtLogFd, EscChars)
 		end
 	end,
     case State#logger_state.async_print_jobs of
@@ -1087,12 +1138,6 @@ print_style(Fd,StyleSheet) ->
 	    print_style_error(Fd,StyleSheet,Reason)  
     end.
 
-%% Simple link version, doesn't work with all browsers unfortunately. :-(
-%% print_style(Fd, StyleSheet) ->
-%%    io:format(Fd,
-%%	      "",
-%%	      [StyleSheet]).
-
 print_style_error(Fd,StyleSheet,Reason) ->
     io:format(Fd,"\n\n",
 	      [StyleSheet,Reason]),
@@ -1364,11 +1409,11 @@ total_row(Success, Fail, UserSkip, AutoSkip, NotBuilt, All) ->
     [xhtml("\n", 
 	   ["\n\n\n"]),
      "Total\n", Label, TimestampCell,
-     "",integer_to_list(Success),"\n",
-     "",integer_to_list(Fail),"\n",
+     "",integer_to_list(Success),"\n",
+     "",integer_to_list(Fail),"\n",
      "",integer_to_list(AllSkip),
      " (",UserSkipStr,"/",AutoSkipStr,")\n",  
-     "",integer_to_list(NotBuilt),"\n",
+     "",integer_to_list(NotBuilt),"\n",
      AllInfo, "\n",
      xhtml("","\n")].
 
@@ -1560,10 +1605,12 @@ header1(Title, SubTitle, TableCols) ->
      "\n",
      "\n",
      "" ++ Title ++ " " ++ SubTitle ++ "\n",
-     "\n",
-     "\n",
+     "\n",
+     "\n",
      xhtml("",
-	   ["\n"]),
+	   ["\n"]),
      xhtml("",
 	   ["\n"]),
@@ -1610,7 +1657,7 @@ footer() ->
       "Copyright © ", year(),
       " Open Telecom Platform",
       xhtml("
\n", "
\n"), - "Updated: ", current_time(), "", + "Updated: ", current_time(), "", xhtml("
\n", "
\n"), xhtml("

\n", "\n"), "\n" @@ -1985,9 +2032,9 @@ interactive_link() -> "\n", "\n", "Last interactive run\n", - "\n", + "\n", "\n", + "charset=utf-8\">\n", "\n", "\n", "Log from last interactive run: ", @@ -2846,8 +2893,12 @@ simulate() -> simulate_logger_loop() -> receive - {log,_,_,_,_,_,List} -> - S = [[io_lib:format(Str,Args),io_lib:nl()] || {Str,Args} <- List], + {log,_,_,_,_,_,Content,_} -> + S = lists:map(fun({_,Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()]; + ({Str,Args}) -> + [io_lib:format(Str,Args),io_lib:nl()] + end, Content), io:format("~ts",[S]), simulate_logger_loop(); stop -> @@ -3053,15 +3104,15 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "Copyright © ", year(), " ", "Open Telecom Platform
\n", - "Updated: ", current_time(), "", + "Updated: ", current_time(), "", "
\n

\n"], {basic_html, ["\n", "\n", "", TestName1, "\n", - "\n", + "\n", "\n", + html_encoding(Encoding),"\">\n", "\n", "\n", @@ -3078,7 +3129,7 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "Copyright © ", year(), " ", "Open Telecom Platform
\n", - "Updated: ", current_time(), "", + "Updated: ", current_time(), "", "
\n\n"], CSSFile = xhtml(fun() -> "" end, @@ -3105,9 +3156,11 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n", "\n", "\n", TestName1, "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n"] ++ TableSorterScript ++ ["\n","\n", LabelStr, "\n"], @@ -3233,11 +3286,11 @@ html_encoding(latin1) -> html_encoding(utf8) -> "utf-8". -unexpected_io(Pid,ct_internal,_Importance,List,CtLogFd) -> - IoFun = create_io_fun(Pid,CtLogFd), - io:format(CtLogFd, "~ts", [lists:foldl(IoFun, [], List)]); -unexpected_io(Pid,_Category,_Importance,List,CtLogFd) -> - IoFun = create_io_fun(Pid,CtLogFd), - Data = io_lib:format("~ts", [lists:foldl(IoFun, [], List)]), +unexpected_io(Pid, ct_internal, _Importance, Content, CtLogFd, EscChars) -> + IoFun = create_io_fun(Pid, CtLogFd, EscChars), + io:format(CtLogFd, "~ts", [lists:foldl(IoFun, [], Content)]); +unexpected_io(Pid, _Category, _Importance, Content, CtLogFd, EscChars) -> + IoFun = create_io_fun(Pid, CtLogFd, EscChars), + Data = io_lib:format("~ts", [lists:foldl(IoFun, [], Content)]), test_server_io:print_unexpected(Data), ok. diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl index 2adced42ae..54190a8254 100644 --- a/lib/common_test/src/ct_master_logs.erl +++ b/lib/common_test/src/ct_master_logs.erl @@ -238,9 +238,9 @@ config_table1([]) -> ["\n\n"]. int_header() -> - "
*** CT MASTER ~s *** ~ts". + "
\n
*** CT MASTER ~s *** ~ts".
 int_footer() ->
-    "
". + "\n
".
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%% NodeDir Index functions %%%
@@ -387,11 +387,12 @@ header(Title, TableCols) ->
      "\n",
      "\n",
      "" ++ Title ++ "\n",
-     "\n",
-     "\n",
+     "\n",
+     "\n",
      xhtml("",
 	   [""]),
+	    "\" type=\"text/css\">\n"]),
      xhtml("",
 	   ["\n"]),
@@ -419,7 +420,7 @@ footer() ->
      "Copyright © ", year(),
      " Open Telecom Platform",
      xhtml("
\n", "
\n"), - "Updated: ", current_time(), "", + "Updated: ", current_time(), "<--!/date-->", xhtml("
\n", "
\n"), xhtml("

\n", "\n"), "\n" diff --git a/lib/test_server/src/erl2html2.erl b/lib/test_server/src/erl2html2.erl index 2c63103264..e69383acea 100644 --- a/lib/test_server/src/erl2html2.erl +++ b/lib/test_server/src/erl2html2.erl @@ -44,7 +44,7 @@ convert(File, Dest, InclPath) -> "\n" "\n" "\n" + "charset=",html_encoding(Encoding),"\"/>\n" "", to_raw_list(File,Encoding), "\n" "\n\n" " catch _:Why -> timer:sleep(1), + WhyStr = test_server_ctrl:escape_chars( + io_lib:format("~tp", [Why])), group_leader() ! {printout,12, "WARNING! " "~w:end_per_testcase(~w, ~p)" - " crashed!\n\tReason: ~p\n", - [Mod,Func,Conf,Why]} + " crashed!\n\tReason: ~ts\n", + [Mod,Func,Conf,WhyStr]} end, Supervisor ! {self(),end_conf} end, @@ -747,10 +749,12 @@ do_call_end_conf(Starter,Mod,Func,Data,Conf,TVal) -> {Pid,end_conf} -> Starter ! {self(),{call_end_conf,Data,ok}}; {'EXIT',Pid,Reason} -> + ReasonStr = test_server_ctrl:escape_chars( + io_lib:format("~tp", [Reason])), group_leader() ! {printout,12, "WARNING! ~w:end_per_testcase(~w, ~p)" - " failed!\n\tReason: ~p\n", - [Mod,Func,Conf,Reason]}, + " failed!\n\tReason: ~ts\n", + [Mod,Func,Conf,ReasonStr]}, Starter ! {self(),{call_end_conf,Data,{error,Reason}}}; {'EXIT',_OtherPid,Reason} -> %% Probably the parent - not much to do about that @@ -1199,9 +1203,11 @@ do_init_per_testcase(Mod, Args) -> [] -> {ok,NewConf}; Bad -> - group_leader() ! {printout,12, - "ERROR! init_per_testcase has returned " - "bad elements in Config: ~p\n",[Bad]}, + ErrorStr = + test_server_ctrl:escape_chars( + io_lib:format("ERROR! init_per_testcase has returned " + "bad elements in Config: ~p\n",[Bad])), + group_leader() ! {printout,12,ErrorStr,[]}, {skip,{failed,{Mod,init_per_testcase,bad_return}}} end; {fail,_Reason}=Res -> @@ -1220,10 +1226,13 @@ do_init_per_testcase(Mod, Args) -> set_loc(erlang:get_stacktrace()), Line = get_loc(), FormattedLoc = test_server_sup:format_loc(Line), - group_leader() ! {printout,12, - "ERROR! init_per_testcase thrown!\n" - "\tLocation: ~ts\n\tReason: ~p\n", - [FormattedLoc, Other]}, + ReasonStr = + test_server_ctrl:escape_chars(io_lib:format("~tp", [Other])), + ErrorStr = + io_lib:format("ERROR! init_per_testcase thrown!\n" + "\tLocation: ~ts\n\tReason: ~ts\n", + [FormattedLoc,ReasonStr]), + group_leader() ! {printout,12,ErrorStr,[]}, {skip,{failed,{Mod,init_per_testcase,Other}}}; _:Reason0 -> Stk = erlang:get_stacktrace(), @@ -1231,10 +1240,13 @@ do_init_per_testcase(Mod, Args) -> set_loc(Stk), Line = get_loc(), FormattedLoc = test_server_sup:format_loc(Line), - group_leader() ! {printout,12, - "ERROR! init_per_testcase crashed!\n" - "\tLocation: ~ts\n\tReason: ~p\n", - [FormattedLoc,Reason]}, + ReasonStr = + test_server_ctrl:escape_chars(io_lib:format("~tp", [Reason])), + ErrorStr = + io_lib:format("ERROR! init_per_testcase crashed!\n" + "\tLocation: ~ts\n\tReason: ~ts\n", + [FormattedLoc,ReasonStr]), + group_leader() ! {printout,12,ErrorStr,[]}, {skip,{failed,{Mod,init_per_testcase,Reason}}} end. @@ -1272,11 +1284,13 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) -> comment(io_lib:format("~ts" "WARNING: ~w thrown!" "\n",[Comment0,EndFunc])), + ReasonStr = + test_server_ctrl:escape_chars(io_lib:format("~tp", [Other])), group_leader() ! {printout,12, "WARNING: ~w thrown!\n" - "Reason: ~p\n" + "Reason: ~ts\n" "Line: ~ts\n", - [EndFunc, Other, + [EndFunc, ReasonStr, test_server_sup:format_loc(get_loc())]}, {failed,{Mod,end_per_testcase,Other}}; Class:Reason -> @@ -1294,11 +1308,13 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) -> comment(io_lib:format("~ts" "WARNING: ~w crashed!" "\n",[Comment0,EndFunc])), + ReasonStr = + test_server_ctrl:escape_chars(io_lib:format("~tp", [Reason])), group_leader() ! {printout,12, "WARNING: ~w crashed!\n" - "Reason: ~p\n" + "Reason: ~ts\n" "Line: ~ts\n", - [EndFunc, Reason, + [EndFunc, ReasonStr, test_server_sup:format_loc(get_loc())]}, {failed,{Mod,end_per_testcase,Why}} end. diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 8a46996bc3..958fe1a2b7 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -77,7 +77,7 @@ -export([handle_call/3, handle_cast/2, handle_info/2]). -export([do_test_cases/4]). -export([do_spec/2, do_spec_list/2]). --export([xhtml/2]). +-export([xhtml/2, escape_chars/1]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -3741,7 +3741,10 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, true -> ok end, - print(minor, "Config value:\n\n ~tp\n", [Args2Print]), + + print(minor, + escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print])), + []), print(minor, "Current directory is ~tp\n", [Cwd]), GrNameStr = case GrName of @@ -3756,7 +3759,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, "" ++ Col0 ++ "~w" ++ Col1 ++ "" "" ++ Col0 ++ "~ts" ++ Col1 ++ "" "~w" - "< >", + "< >", [num2str(Num),fw_name(Mod),GrNameStr,EncMinorBase,Func, EncMinorBase,EncMinorBase]), @@ -3933,7 +3936,7 @@ progress(skip, CaseNum, Mod, Func, GrName, Loc, Reason, Time, [get_info_str(Mod,Func, CaseNum, get(test_server_cases))]), test_server_sup:framework_call(report, [tc_done,{Mod,{Func,GrName}, {ReportTag,Reason1}}]), - ReasonStr = reason_to_string(Reason1), + ReasonStr = escape_chars(reason_to_string(Reason1)), ReasonStr1 = lists:flatten([string:strip(S,left) || S <- string:tokens(ReasonStr,[$\n])]), ReasonStr2 = @@ -4005,7 +4008,10 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T, [Comment]), FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== Location: ~ts", [FormatLoc]), - print(minor, "=== Reason: {testcase_aborted,~p}", [Reason]), + print(minor, + escape_chars(io_lib:format("=== Reason: {testcase_aborted,~p}", + [Reason])), + []), failed; progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time, @@ -4018,7 +4024,7 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time, TimeStr = io_lib:format(if is_float(Time) -> "~.3fs"; true -> "~w" end, [Time]), - ErrorReason = lists:flatten(io_lib:format("~p", [Reason])), + ErrorReason = escape_chars(lists:flatten(io_lib:format("~p", [Reason]))), ErrorReason1 = lists:flatten([string:strip(S,left) || S <- string:tokens(ErrorReason,[$\n])]), ErrorReason2 = @@ -4041,7 +4047,9 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time, [TimeStr,Comment]), print(minor, "=== Location: ~w", [unknown]), {FStr,FormattedReason} = format_exception(Reason), - print(minor, "=== Reason: " ++ FStr, [FormattedReason]), + print(minor, + escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason])), + []), failed; progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time, @@ -4075,7 +4083,8 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time, FormatLoc = test_server_sup:format_loc(LocMin), print(minor, "=== Location: ~ts", [FormatLoc]), {FStr,FormattedReason} = format_exception(Reason), - print(minor, "=== Reason: " ++ FStr, [FormattedReason]), + print(minor, "=== Reason: " ++ + escape_chars(io_lib:format(FStr, [FormattedReason])), []), failed; progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time, @@ -4104,11 +4113,36 @@ progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time, "Ok" "~ts\n", [Time,Comment]), - print(minor, "=== Returned value: ~p", [RetVal]), + print(minor, + escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal])), + []), ok. %%-------------------------------------------------------------------- %% various help functions +escape_chars(Term) when not is_list(Term), not is_binary(Term) -> + esc_chars_in_list(io_lib:format("~tp", [Term])); +escape_chars(List = [Term | _]) when not is_list(Term), not is_integer(Term) -> + esc_chars_in_list(io_lib:format("~tp", [List])); +escape_chars(List) -> + esc_chars_in_list(List). + +esc_chars_in_list([Bin | Io]) when is_binary(Bin) -> + [Bin | esc_chars_in_list(Io)]; +esc_chars_in_list([List | Io]) when is_list(List) -> + [esc_chars_in_list(List) | esc_chars_in_list(Io)]; +esc_chars_in_list([$< | Io]) -> + ["<" | esc_chars_in_list(Io)]; +esc_chars_in_list([$> | Io]) -> + [">" | esc_chars_in_list(Io)]; +esc_chars_in_list([$& | Io]) -> + ["&" | esc_chars_in_list(Io)]; +esc_chars_in_list([Char | Io]) when is_integer(Char) -> + [Char | esc_chars_in_list(Io)]; +esc_chars_in_list([]) -> + []; +esc_chars_in_list(Bin) -> + Bin. get_fw_mod(Mod) -> case get(test_server_framework) of @@ -5564,8 +5598,9 @@ html_header(Title) -> "\n" "\n" "", Title, "\n" - "\n" - "\n" + "\n" + "\n" "\n" "\n"]. diff --git a/lib/test_server/src/test_server_gl.erl b/lib/test_server/src/test_server_gl.erl index c5ec3ccbe6..356a3288b8 100644 --- a/lib/test_server/src/test_server_gl.erl +++ b/lib/test_server/src/test_server_gl.erl @@ -193,7 +193,15 @@ handle_info({io_request,From,ReplyAs,Req}=IoReq, St) -> #st{capture=CapturePid} -> CapturePid ! {captured,Data} end, - output(minor, Data, From, From, St) + + + %%! PROBLEM HERE! + %%! Data could come html tagged from CT!! + + %EscapedChars = test_server_ctrl:escape_chars(Data), + EscapedChars = Data, + + output(minor, EscapedChars, From, From, St) end, From ! {io_reply,ReplyAs,ok} catch diff --git a/lib/test_server/test/erl2html2_SUITE.erl b/lib/test_server/test/erl2html2_SUITE.erl index 9e6389109b..8e9f6e773a 100644 --- a/lib/test_server/test/erl2html2_SUITE.erl +++ b/lib/test_server/test/erl2html2_SUITE.erl @@ -31,7 +31,7 @@ "\n", "Module ", Src, "\n", "\n", + "content=\"no-cache\">\n", "\n", "\n"]). -- cgit v1.2.3