diff options
Diffstat (limited to 'lib/common_test/src')
-rw-r--r-- | lib/common_test/src/ct.erl | 75 | ||||
-rw-r--r-- | lib/common_test/src/ct_framework.erl | 120 | ||||
-rw-r--r-- | lib/common_test/src/ct_groups.erl | 121 | ||||
-rw-r--r-- | lib/common_test/src/ct_hooks.erl | 24 | ||||
-rw-r--r-- | lib/common_test/src/ct_logs.erl | 229 | ||||
-rw-r--r-- | lib/common_test/src/ct_master_logs.erl | 13 | ||||
-rw-r--r-- | lib/common_test/src/ct_run.erl | 7 | ||||
-rw-r--r-- | lib/common_test/src/ct_testspec.erl | 109 | ||||
-rw-r--r-- | lib/common_test/src/ct_util.erl | 6 | ||||
-rw-r--r-- | lib/common_test/src/cth_log_redirect.erl | 9 |
10 files changed, 455 insertions, 258 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, @@ -509,44 +510,88 @@ get_testspec_terms(Tags) -> %%%----------------------------------------------------------------- +%%% @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 <c>Args</c> is <c>[]</c>.</p> %%% <p>Please see the User's Guide for details on <c>Category</c> %%% and <c>Importance</c>.</p> -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 8fb7a03bb0..19a3a51b88 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -52,9 +52,23 @@ %%% %%% @doc Test server framework callback, called by the test_server %%% when a new test case is started. -init_tc(Mod,Func,Config) -> +init_tc(Mod,EPTC={end_per_testcase,_},[Config]) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Config), + case ct_hooks:init_tc(Suite,EPTC,Config) of + NewConfig when is_list(NewConfig) -> + {ok,[NewConfig]}; + Other-> + Other + end; + +init_tc(Mod,Func0,Args) -> + %% in case Mod == ct_framework, lookup the suite name + Suite = get_suite_name(Mod, Args), + {Func,HookFunc} = case Func0 of + {init_per_testcase,F} -> {F,Func0}; + _ -> {Func0,Func0} + end, %% check if previous testcase was interpreted and has left %% a "dead" trace window behind - if so, kill it @@ -86,7 +100,7 @@ init_tc(Mod,Func,Config) -> end, [create]), case ct_util:read_suite_data({seq,Suite,Func}) of undefined -> - init_tc1(Mod,Suite,Func,Config); + init_tc1(Mod,Suite,Func,HookFunc,Args); Seq when is_atom(Seq) -> case ct_util:read_suite_data({seq,Suite,Seq}) of [Func|TCs] -> % this is the 1st case in Seq @@ -102,27 +116,27 @@ init_tc(Mod,Func,Config) -> _ -> ok end, - init_tc1(Mod,Suite,Func,Config); + init_tc1(Mod,Suite,Func,HookFunc,Args); {failed,Seq,BadFunc} -> {auto_skip,{sequence_failed,Seq,BadFunc}} end end - end. + end. -init_tc1(?MODULE,_,error_in_suite,[Config0]) when is_list(Config0) -> +init_tc1(?MODULE,_,error_in_suite,_,[Config0]) when is_list(Config0) -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, node=node(), data={?MODULE,error_in_suite}}), - ct_suite_init(?MODULE, error_in_suite, [], Config0), - case ?val(error, Config0) of + ct_suite_init(?MODULE,error_in_suite,[],Config0), + case ?val(error,Config0) of undefined -> {fail,"unknown_error_in_suite"}; Reason -> {fail,Reason} end; -init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> +init_tc1(Mod,Suite,Func,HookFunc,[Config0]) when is_list(Config0) -> Config1 = case ct_util:read_suite_data(last_saved_config) of {{Suite,LastFunc},SavedConfig} -> % last testcase @@ -156,11 +170,13 @@ init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> %% testcase info function (these should only survive the %% testcase, not the whole suite) FuncSpec = group_or_func(Func,Config0), - if is_tuple(FuncSpec) -> % group - ok; - true -> - ct_config:delete_default_config(testcase) - end, + HookFunc1 = + if is_tuple(FuncSpec) -> % group + FuncSpec; + true -> + ct_config:delete_default_config(testcase), + HookFunc + end, Initialize = fun() -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, @@ -184,14 +200,15 @@ init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> Initialize(), {fail,Reason}; _ -> - init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) + init_tc2(Mod,Suite,Func,HookFunc1, + SuiteInfo,MergeResult,Config) end end; -init_tc1(_Mod,_Suite,_Func,Args) -> +init_tc1(_Mod,_Suite,_Func,_HookFunc,Args) -> {ok,Args}. -init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> +init_tc2(Mod,Suite,Func,HookFunc,SuiteInfo,MergeResult,Config) -> %% timetrap must be handled before require MergedInfo = timetrap_first(MergeResult, [], []), %% tell logger to use specified style sheet @@ -238,7 +255,7 @@ init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> {ok,PostInitHook,Config1} -> case get('$test_server_framework_test') of undefined -> - ct_suite_init(Suite, FuncSpec, PostInitHook, Config1); + ct_suite_init(Suite,HookFunc,PostInitHook,Config1); Fun -> PostInitHookResult = do_post_init_hook(PostInitHook, Config1), @@ -251,16 +268,16 @@ init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> end end. -ct_suite_init(Suite, FuncSpec, PostInitHook, Config) when is_list(Config) -> - case ct_hooks:init_tc(Suite, FuncSpec, Config) of +ct_suite_init(Suite,HookFunc,PostInitHook,Config) when is_list(Config) -> + case ct_hooks:init_tc(Suite,HookFunc,Config) of NewConfig when is_list(NewConfig) -> - PostInitHookResult = do_post_init_hook(PostInitHook, NewConfig), + PostInitHookResult = do_post_init_hook(PostInitHook,NewConfig), {ok, [PostInitHookResult ++ NewConfig]}; Else -> Else end. -do_post_init_hook(PostInitHook, Config) -> +do_post_init_hook(PostInitHook,Config) -> lists:flatmap(fun({Tag,Fun}) -> case lists:keysearch(Tag,1,Config) of {value,_} -> @@ -657,9 +674,23 @@ end_tc(Mod,Func,{TCPid,Result,[Args]}, Return) when is_pid(TCPid) -> end_tc(Mod,Func,{Result,[Args]}, Return) -> end_tc(Mod,Func,self(),Result,Args,Return). -end_tc(Mod,Func,TCPid,Result,Args,Return) -> +end_tc(Mod,IPTC={init_per_testcase,_Func},_TCPid,Result,Args,Return) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Args), + case ct_hooks:end_tc(Suite,IPTC,Args,Result,Return) of + '$ct_no_change' -> + ok; + HookResult -> + HookResult + end; + +end_tc(Mod,Func0,TCPid,Result,Args,Return) -> + %% in case Mod == ct_framework, lookup the suite name + Suite = get_suite_name(Mod, Args), + {EPTC,Func} = case Func0 of + {end_per_testcase,F} -> {true,F}; + _ -> {false,Func0} + end, test_server:timetrap_cancel(), @@ -686,11 +717,15 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> end, ct_util:delete_suite_data(last_saved_config), - FuncSpec = group_or_func(Func,Args), - + {FuncSpec,HookFunc} = + if not EPTC -> + FS = group_or_func(Func,Args), + {FS,FS}; + true -> + {Func,Func0} + end, {Result1,FinalNotify} = - case ct_hooks:end_tc( - Suite, FuncSpec, Args, Result, Return) of + case ct_hooks:end_tc(Suite,HookFunc,Args,Result,Return) of '$ct_no_change' -> {ok,Result}; HookResult -> @@ -831,13 +866,13 @@ tag(_Other) -> %%% <code>Func</code> in suite <code>Mod</code> crashing. %%% <code>Error</code> 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 +894,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> Other -> io_lib:format("~P", [Other,5]) end, - ErrorHtml = "<font color=\"brown\">" ++ ErrStr ++ "</font>", + ErrorHtml = + "<font color=\"brown\">" ++ ct_logs:escape_chars(ErrorStr) ++ "</font>", 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 +923,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<a href=\"#end\">" "Full error description and stacktrace" "</a>", + 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_groups.erl b/lib/common_test/src/ct_groups.erl index 7636f15f59..92640cf323 100644 --- a/lib/common_test/src/ct_groups.erl +++ b/lib/common_test/src/ct_groups.erl @@ -81,7 +81,7 @@ find(Mod, all, all, [{Name,Props,Tests} | Gs], Known, Defs, _) find(Mod, all, TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, []), + Tests1 = modify_tc_list(Tests, TCs, []), trim(make_conf(Mod, Name, Props, find(Mod, all, TCs, Tests1, [Name | Known], Defs, true))) ++ @@ -91,7 +91,7 @@ find(Mod, all, TCs, [{Name,Props,Tests} | Gs], Known, Defs, _) find(Mod, [Name|GrNames]=SPath, TCs, [{Name,Props,Tests} | Gs], Known, Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + Tests1 = modify_tc_list(Tests, TCs, GrNames), trim(make_conf(Mod, Name, Props, find(Mod, GrNames, TCs, Tests1, [Name|Known], Defs, FindAll))) ++ @@ -133,7 +133,7 @@ find(_Mod, [_|_], _TCs, [], _Known, _Defs, _) -> find(Mod, GrNames, TCs, [{Name,Props,Tests} | Gs], Known, Defs, FindAll) when is_atom(Name), is_list(Props), is_list(Tests) -> cyclic_test(Mod, Name, Known), - Tests1 = rm_unwanted_tcs(Tests, TCs, GrNames), + Tests1 = modify_tc_list(Tests, TCs, GrNames), trim(make_conf(Mod, Name, Props, find(Mod, GrNames, TCs, Tests1, [Name|Known], Defs, FindAll))) ++ @@ -284,70 +284,57 @@ trim_test(Test) -> %% GrNames is [] if the terminating group has been found. From %% that point, all specified test should be included (as well as %% sub groups for deeper search). -rm_unwanted_tcs(Tests, all, []) -> - Tests; - -rm_unwanted_tcs(Tests, TCs, []) -> - sort_tests(lists:flatmap(fun(Test) when is_tuple(Test), - (size(Test) > 2) -> - [Test]; - (Test={group,_}) -> - [Test]; - (Test={_M,TC}) -> - case lists:member(TC, TCs) of - true -> [Test]; - false -> [] - end; - (Test) when is_atom(Test) -> - case lists:keysearch(Test, 2, TCs) of - {value,_} -> - [Test]; - _ -> - case lists:member(Test, TCs) of - true -> [Test]; - false -> [] - end - end; - (Test) -> [Test] - end, Tests), TCs); - -rm_unwanted_tcs(Tests, _TCs, _) -> - [Test || Test <- Tests, not is_atom(Test)]. - -%% make sure the order of tests is according to the order in TCs -sort_tests(Tests, TCs) when is_list(TCs)-> - lists:sort(fun(T1, T2) -> - case {is_tc(T1),is_tc(T2)} of - {true,true} -> - (position(T1, TCs) =< - position(T2, TCs)); - {false,true} -> - (position(T2, TCs) == (length(TCs)+1)); - _ -> true - - end - end, Tests); -sort_tests(Tests, _) -> - Tests. - -is_tc(T) when is_atom(T) -> true; -is_tc({group,_}) -> false; -is_tc({_M,T}) when is_atom(T) -> true; -is_tc(_) -> false. - -position(T, TCs) -> - position(T, TCs, 1). - -position(T, [T|_TCs], Pos) -> - Pos; -position(T, [{_,T}|_TCs], Pos) -> - Pos; -position({M,T}, [T|_TCs], Pos) when M /= group -> - Pos; -position(T, [_|TCs], Pos) -> - position(T, TCs, Pos+1); -position(_, [], Pos) -> - Pos. +modify_tc_list(GrSpecTs, all, []) -> + GrSpecTs; + +modify_tc_list(GrSpecTs, TSCs, []) -> + modify_tc_list1(GrSpecTs, TSCs); + +modify_tc_list(GrSpecTs, _TSCs, _) -> + [Test || Test <- GrSpecTs, not is_atom(Test)]. + +modify_tc_list1(GrSpecTs, TSCs) -> + %% remove all cases in group tc list that should not be executed + GrSpecTs1 = + lists:flatmap(fun(Test) when is_tuple(Test), + (size(Test) > 2) -> + [Test]; + (Test={group,_}) -> + [Test]; + (Test={_M,TC}) -> + case lists:member(TC, TSCs) of + true -> [Test]; + false -> [] + end; + (Test) when is_atom(Test) -> + case lists:keysearch(Test, 2, TSCs) of + {value,_} -> + [Test]; + _ -> + case lists:member(Test, TSCs) of + true -> [Test]; + false -> [] + end + end; + (Test) -> [Test] + end, GrSpecTs), + {TSCs2,GrSpecTs3} = + lists:foldr( + fun(TC, {TSCs1,GrSpecTs2}) -> + case lists:member(TC,GrSpecTs1) of + true -> + {[TC|TSCs1],lists:delete(TC,GrSpecTs2)}; + false -> + case lists:keymember(TC, 2, GrSpecTs) of + {value,Test} -> + {[Test|TSCs1], + lists:keydelete(TC, 2, GrSpecTs2)}; + false -> + {TSCs1,GrSpecTs2} + end + end + end, {[],GrSpecTs1}, TSCs), + TSCs2 ++ GrSpecTs3. %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index 86d18696dc..83ad33fdd8 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -93,7 +93,11 @@ init_tc(Mod, {init_per_group, GroupName, Properties}, Config) -> call(fun call_generic/3, Config, [pre_init_per_group, GroupName]); init_tc(_Mod, {end_per_group, GroupName, _}, Config) -> call(fun call_generic/3, Config, [pre_end_per_group, GroupName]); -init_tc(_Mod, TC, Config) -> +init_tc(_Mod, {init_per_testcase,TC}, Config) -> + call(fun call_generic/3, Config, [pre_init_per_testcase, TC]); +init_tc(_Mod, {end_per_testcase,TC}, Config) -> + call(fun call_generic/3, Config, [pre_end_per_testcase, TC]); +init_tc(_Mod, TC = error_in_suite, Config) -> call(fun call_generic/3, Config, [pre_init_per_testcase, TC]). %% @doc Called as each test case is completed. This includes all configuration @@ -126,10 +130,17 @@ end_tc(Mod, {end_per_group, GroupName, Properties}, Config, Result, _Return) -> [post_end_per_group, GroupName, Config], '$ct_no_change'), maybe_stop_locker(Mod, GroupName, Properties), Res; -end_tc(_Mod, TC, Config, Result, _Return) -> +end_tc(_Mod, {init_per_testcase,TC}, Config, Result, _Return) -> + call(fun call_generic/3, Result, [post_init_per_testcase, TC, Config], + '$ct_no_change'); +end_tc(_Mod, {end_per_testcase,TC}, Config, Result, _Return) -> + call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], + '$ct_no_change'); +end_tc(_Mod, TC = error_in_suite, Config, Result, _Return) -> call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], '$ct_no_change'). + %% Case = TestCase | {TestCase,GroupName} on_tc_skip(How, {Suite, Case, Reason}) -> call(fun call_cleanup/3, {How, Reason}, [on_tc_skip, Suite, Case]). @@ -244,6 +255,8 @@ remove(_, Else) -> %% Translate scopes, i.e. init_per_group,group1 -> end_per_group,group1 etc scope([pre_init_per_testcase, TC|_]) -> + [post_init_per_testcase, TC]; +scope([pre_end_per_testcase, TC|_]) -> [post_end_per_testcase, TC]; scope([pre_init_per_group, GroupName|_]) -> [post_end_per_group, GroupName]; @@ -317,7 +330,8 @@ get_hooks() -> %% If we are doing a cleanup call i.e. {post,pre}_end_per_*, all priorities %% are reversed. Probably want to make this sorting algorithm pluginable %% as some point... -resort(Calls,Hooks,[F|_R]) when F == post_end_per_testcase; +resort(Calls,Hooks,[F|_R]) when F == pre_end_per_testcase; + F == post_end_per_testcase; F == pre_end_per_group; F == post_end_per_group; F == pre_end_per_suite; @@ -367,10 +381,10 @@ pos(Id,[_|Rest],Num) -> catch_apply(M,F,A, Default) -> try - apply(M,F,A) + erlang:apply(M,F,A) catch _:Reason -> case erlang:get_stacktrace() of - %% Return the default if it was the CTH module which did not have the function. + %% Return the default if it was the CTH module which did not have the function. [{M,F,A,_}|_] when Reason == undef -> Default; Trace -> diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 78081380e7..e3f995ad3f 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]). @@ -267,7 +269,7 @@ cast(Msg) -> %%% <p>This function is called by ct_framework:init_tc/3</p> init_tc(RefreshLog) -> call({init_tc,self(),group_leader(),RefreshLog}), - io:format(xhtml("", "<br />")), + io:format(["$tc_html",xhtml("", "<br />")]), ok. %%%----------------------------------------------------------------- @@ -314,9 +316,10 @@ unregister_groupleader(Pid) -> %%% data to log (as in <code>io:format(Format,Args)</code>).</p> 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. %%% %%% <p>This function is called by <code>ct</code> when logging %%% stuff directly from a testcase (i.e. not from within the CT %%% framework).</p> -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.</p> 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 %%% %%% <p>This function is called by internal ct functions to %%% force logging to the ct framework log</p> 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() -> - "<div class=\"ct_internal\"><b>*** CT ~s *** ~ts</b>". + "</pre>\n<div class=\"ct_internal\"><pre><b>*** CT ~s *** ~ts</b>". int_footer() -> - "</div>". + "</pre></div>\n<pre>". div_header(Class) -> div_header(Class,"User"). div_header(Class,Printer) -> - "\n<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** " ++ Printer ++ - " " ++ log_timestamp(?now) ++ " ***</b>". + "\n</pre>\n<div class=\"" ++ atom_to_list(Class) ++ "\"><pre><b>*** " + ++ Printer ++ " " ++ log_timestamp(?now) ++ " ***</b>". div_footer() -> - "</div>". + "</pre></div>\n<pre>". maybe_log_timestamp() -> @@ -568,7 +582,7 @@ maybe_log_timestamp() -> ok; _ -> cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, - [{"<i>~s</i>",[log_timestamp({MS,S,US})]}]}) + [{hd,"<i>~s</i>",[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,["$tc_html","~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()), @@ -894,25 +945,25 @@ 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 + try io:format(TCGL, ["$tc_html","~ts"], + [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, -%% "<link href=~p rel=\"stylesheet\" type=\"text/css\">", -%% [StyleSheet]). - print_style_error(Fd,StyleSheet,Reason) -> io:format(Fd,"\n<!-- Failed to load stylesheet ~ts: ~p -->\n", [StyleSheet,Reason]), @@ -1364,11 +1409,11 @@ total_row(Success, Fail, UserSkip, AutoSkip, NotBuilt, All) -> [xhtml("<tr valign=top>\n", ["</tbody>\n<tfoot>\n<tr class=\"",odd_or_even(),"\">\n"]), "<td><b>Total</b></td>\n", Label, TimestampCell, - "<td align=right><b>",integer_to_list(Success),"<b></td>\n", - "<td align=right><b>",integer_to_list(Fail),"<b></td>\n", + "<td align=right><b>",integer_to_list(Success),"</b></td>\n", + "<td align=right><b>",integer_to_list(Fail),"</b></td>\n", "<td align=right>",integer_to_list(AllSkip), " (",UserSkipStr,"/",AutoSkipStr,")</td>\n", - "<td align=right><b>",integer_to_list(NotBuilt),"<b></td>\n", + "<td align=right><b>",integer_to_list(NotBuilt),"</b></td>\n", AllInfo, "</tr>\n", xhtml("","</tfoot>\n")]. @@ -1560,10 +1605,12 @@ header1(Title, SubTitle, TableCols) -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>" ++ Title ++ " " ++ SubTitle ++ "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; " + "charset=utf-8\"></meta>\n", xhtml("", - ["<link rel=\"stylesheet\" href=\"",uri(CSSFile),"\" type=\"text/css\">\n"]), + ["<link rel=\"stylesheet\" href=\"",uri(CSSFile), + "\" type=\"text/css\"></link>\n"]), xhtml("", ["<script type=\"text/javascript\" src=\"",JQueryFile, "\"></script>\n"]), @@ -1610,7 +1657,7 @@ footer() -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">Open Telecom Platform</a>", xhtml("<br>\n", "<br />\n"), - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", xhtml("<br>\n", "<br />\n"), xhtml("</font></p>\n", "</div>\n"), "</center>\n" @@ -1985,9 +2032,9 @@ interactive_link() -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>Last interactive run</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", "<meta http-equiv=\"content-type\" content=\"text/html; " - "charset=utf-8\">\n", + "charset=utf-8\"></meta>\n", "</head>\n", "<body>\n", "Log from last interactive run: <a href=\"",uri(CtLog),"\">", @@ -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(), " <a href=\"http://www.erlang.org\">", "Open Telecom Platform</a><br>\n", - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", "<br>\n</font></p>\n"], {basic_html, ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n", "<html>\n", "<head><title>", TestName1, "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", "<meta http-equiv=\"content-type\" content=\"text/html; charset=", - html_encoding(Encoding),"\">\n", + html_encoding(Encoding),"\"></meta>\n", "</head>\n", "<body", Bgr, " bgcolor=\"white\" text=\"black\" ", "link=\"blue\" vlink=\"purple\" alink=\"red\">\n", @@ -3078,7 +3129,7 @@ get_ts_html_wrapper(TestName, Logdir, PrintLabel, Cwd, TableCols, Encoding) -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">", "Open Telecom Platform</a><br />\n", - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<!--/date-->", "<br />\n</div>\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", "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n", "<head>\n<title>", TestName1, "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", - "<link rel=\"stylesheet\" href=\"", uri(CSSFile), "\" type=\"text/css\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; ", + "charset=utf-8\"></meta>\n", + "<link rel=\"stylesheet\" href=\"", uri(CSSFile), + "\" type=\"text/css\"></link>\n", "<script type=\"text/javascript\" src=\"", JQueryFile, "\"></script>\n", "<script type=\"text/javascript\" src=\"", TableSorterFile, "\"></script>\n"] ++ TableSorterScript ++ ["</head>\n","<body>\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([]) -> ["</tbody>\n</table>\n"]. int_header() -> - "<div class=\"ct_internal\"><b>*** CT MASTER ~s *** ~ts</b>". + "</pre>\n<div class=\"ct_internal\"><pre><b>*** CT MASTER ~s *** ~ts</b>". int_footer() -> - "</div>". + "</pre></div>\n<pre>". %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% NodeDir Index functions %%% @@ -387,11 +387,12 @@ header(Title, TableCols) -> "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n", "<head>\n", "<title>" ++ Title ++ "</title>\n", - "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n", - "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n", + "<meta http-equiv=\"cache-control\" content=\"no-cache\"></meta>\n", + "<meta http-equiv=\"content-type\" content=\"text/html; ", + "charset=utf-8\"></meta>\n", xhtml("", ["<link rel=\"stylesheet\" href=\"",ct_logs:uri(CSSFile), - "\" type=\"text/css\">"]), + "\" type=\"text/css\"></link>\n"]), xhtml("", ["<script type=\"text/javascript\" src=\"",JQueryFile, "\"></script>\n"]), @@ -419,7 +420,7 @@ footer() -> "Copyright © ", year(), " <a href=\"http://www.erlang.org\">Open Telecom Platform</a>", xhtml("<br>\n", "<br />\n"), - "Updated: <!date>", current_time(), "<!/date>", + "Updated: <!--date-->", current_time(), "<--!/date-->", xhtml("<br>\n", "<br />\n"), xhtml("</font></p>\n", "</div>\n"), "</center>\n" diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index b4364b87ff..ceb94ceee5 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -2055,6 +2055,13 @@ final_tests1([{TestDir,Suite,GrsOrCs}|Tests], Final, Skip, Bad) when ({skipped,Group,TCs}) -> [ct_groups:make_conf(TestDir, Suite, Group, [skipped], TCs)]; + ({skipped,TC}) -> + case lists:member(TC, GrsOrCs) of + true -> + []; + false -> + [TC] + end; ({GrSpec = {GroupName,_},TCs}) -> Props = [{override,GrSpec}], [ct_groups:make_conf(TestDir, Suite, diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 5d5448f352..5cd52bd042 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -70,13 +70,17 @@ prepare_tests(TestSpec) when is_record(TestSpec,testspec) -> Tests = TestSpec#testspec.tests, %% Sort Tests into "flat" Run and Skip lists (not sorted per node). {Run,Skip} = get_run_and_skip(Tests,[],[]), + %% Create initial list of {Node,{Run,Skip}} tuples NodeList = lists:map(fun(N) -> {N,{[],[]}} end, list_nodes(TestSpec)), + %% Get all Run tests sorted per node basis. NodeList1 = run_per_node(Run,NodeList, - TestSpec#testspec.merge_tests), + TestSpec#testspec.merge_tests), + %% Get all Skip entries sorted per node basis. NodeList2 = skip_per_node(Skip,NodeList1), + %% Change representation. Result= lists:map(fun({Node,{Run1,Skip1}}) -> @@ -103,7 +107,7 @@ run_per_node([{{Node,Dir},Test}|Ts],Result,MergeTests) -> true -> merge_tests(Dir,Test,Run) end, - run_per_node(Ts,insert_in_order({Node,{Run1,Skip}},Result), + run_per_node(Ts,insert_in_order({Node,{Run1,Skip}},Result,replace), MergeTests); run_per_node([],Result,_) -> Result. @@ -140,7 +144,7 @@ merge_suites(Dir,Test,[]) -> skip_per_node([{{Node,Dir},Test}|Ts],Result) -> {value,{Node,{Run,Skip}}} = lists:keysearch(Node,1,Result), Skip1 = [{Dir,Test}|Skip], - skip_per_node(Ts,insert_in_order({Node,{Run,Skip1}},Result)); + skip_per_node(Ts,insert_in_order({Node,{Run,Skip1}},Result,replace)); skip_per_node([],Result) -> Result. @@ -156,7 +160,7 @@ skip_per_node([],Result) -> %% %% Skip entry: {Suites,Comment} or {Suite,Cases,Comment} %% -get_run_and_skip([{{Node,Dir},Suites}|Tests],Run,Skip) -> +get_run_and_skip([{{Node,Dir},Suites}|Tests],Run,Skip) -> TestDir = ct_util:get_testdir(Dir,catch element(1,hd(Suites))), case lists:keysearch(all,1,Suites) of {value,_} -> % all Suites in Dir @@ -183,18 +187,33 @@ prepare_suites(Node,Dir,[{Suite,Cases}|Suites],Run,Skip) -> [[{{Node,Dir},{Suite,all}}]|Run], [Skipped|Skip]); false -> - {RL,SL} = prepare_cases(Node,Dir,Suite,Cases), - prepare_suites(Node,Dir,Suites,[RL|Run],[SL|Skip]) + {Run1,Skip1} = prepare_cases(Node,Dir,Suite,Cases,Run,Skip), + prepare_suites(Node,Dir,Suites,Run1,Skip1) end; prepare_suites(_Node,_Dir,[],Run,Skip) -> {lists:flatten(lists:reverse(Run)), lists:flatten(lists:reverse(Skip))}. -prepare_cases(Node,Dir,Suite,Cases) -> +prepare_cases(Node,Dir,Suite,Cases,Run,Skip) -> case get_skipped_cases(Node,Dir,Suite,Cases) of - SkipAll=[{{Node,Dir},{Suite,_Cmt}}] -> % all cases to be skipped - %% note: this adds an 'all' test even if only skip is specified - {[{{Node,Dir},{Suite,all}}],SkipAll}; + [SkipAll={{Node,Dir},{Suite,_Cmt}}] -> % all cases to be skipped + case lists:any(fun({{N,D},{S,all}}) when N == Node, + D == Dir, + S == Suite -> + true; + ({{N,D},{S,Cs}}) when N == Node, + D == Dir, + S == Suite -> + lists:member(all,Cs); + (_) -> false + end, lists:flatten(Run)) of + true -> + {Run,[SkipAll|Skip]}; + false -> + %% note: this adds an 'all' test even if + %% only skip is specified + {[{{Node,Dir},{Suite,all}}|Run],[SkipAll|Skip]} + end; Skipped -> %% note: this adds a test even if only skip is specified PrepC = lists:foldr(fun({{G,Cs},{skip,_Cmt}}, Acc) when @@ -210,11 +229,11 @@ prepare_cases(Node,Dir,Suite,Cases) -> true -> Acc; false -> - [C|Acc] + [{skipped,C}|Acc] end; (C,Acc) -> [C|Acc] end, [], Cases), - {{{Node,Dir},{Suite,PrepC}},Skipped} + {[{{Node,Dir},{Suite,PrepC}}|Run],[Skipped|Skip]} end. get_skipped_suites(Node,Dir,Suites) -> @@ -431,6 +450,7 @@ collect_tests({Replace,Terms},TestSpec=#testspec{alias=As,nodes=Ns},Relaxed) -> merge_tests = MergeTestsDef}), TestSpec2 = get_all_nodes(Terms2,TestSpec1), {Terms3, TestSpec3} = filter_init_terms(Terms2, [], TestSpec2), + add_tests(Terms3,TestSpec3). %% replace names (atoms) in the testspec matching those in 'define' terms by @@ -1257,7 +1277,7 @@ insert_groups1(Suite,Groups,Suites0) -> Suites0; {value,{Suite,GrAndCases0}} -> GrAndCases = insert_groups2(Groups,GrAndCases0), - insert_in_order({Suite,GrAndCases},Suites0); + insert_in_order({Suite,GrAndCases},Suites0,replace); false -> insert_in_order({Suite,Groups},Suites0) end. @@ -1282,7 +1302,7 @@ insert_cases(Node,Dir,Suite,Cases,Tests,false) when is_list(Cases) -> insert_cases(Node,Dir,Suite,Cases,Tests,true) when is_list(Cases) -> {Tests1,Done} = lists:foldr(fun(All={{N,D},[{all,_}]},{Merged,_}) when N == Node, - D == Dir -> + D == Dir -> {[All|Merged],true}; ({{N,D},Suites0},{Merged,_}) when N == Node, D == Dir -> @@ -1312,7 +1332,7 @@ insert_cases1(Suite,Cases,Suites0) -> Suites0; {value,{Suite,Cases0}} -> Cases1 = insert_in_order(Cases,Cases0), - insert_in_order({Suite,Cases1},Suites0); + insert_in_order({Suite,Cases1},Suites0,replace); false -> insert_in_order({Suite,Cases},Suites0) end. @@ -1369,9 +1389,9 @@ skip_groups1(Suite,Groups,Cmt,Suites0) -> case lists:keysearch(Suite,1,Suites0) of {value,{Suite,GrAndCases0}} -> GrAndCases1 = GrAndCases0 ++ SkipGroups, - insert_in_order({Suite,GrAndCases1},Suites0); + insert_in_order({Suite,GrAndCases1},Suites0,replace); false -> - insert_in_order({Suite,SkipGroups},Suites0) + insert_in_order({Suite,SkipGroups},Suites0,replace) end. skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) -> @@ -1380,7 +1400,7 @@ skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,false) when is_list(Cases) -> skip_cases(Node,Dir,Suite,Cases,Cmt,Tests,true) when is_list(Cases) -> {Tests1,Done} = lists:foldr(fun({{N,D},Suites0},{Merged,_}) when N == Node, - D == Dir -> + D == Dir -> Suites1 = skip_cases1(Suite,Cases,Cmt,Suites0), {[{{N,D},Suites1}|Merged],true}; (T,{Merged,Match}) -> @@ -1401,32 +1421,55 @@ skip_cases1(Suite,Cases,Cmt,Suites0) -> case lists:keysearch(Suite,1,Suites0) of {value,{Suite,Cases0}} -> Cases1 = Cases0 ++ SkipCases, - insert_in_order({Suite,Cases1},Suites0); + insert_in_order({Suite,Cases1},Suites0,replace); false -> - insert_in_order({Suite,SkipCases},Suites0) + case Suites0 of + [{all,_}=All|Skips]-> + [All|Skips++[{Suite,SkipCases}]]; + _ -> + insert_in_order({Suite,SkipCases},Suites0,replace) + end end. append(Elem, List) -> List ++ [Elem]. -insert_in_order([E|Es],List) -> - List1 = insert_elem(E,List,[]), - insert_in_order(Es,List1); -insert_in_order([],List) -> +insert_in_order(Elems,Dest) -> + insert_in_order1(Elems,Dest,false). + +insert_in_order(Elems,Dest,replace) -> + insert_in_order1(Elems,Dest,true). + +insert_in_order1([_E|Es],all,Replace) -> + insert_in_order1(Es,all,Replace); + +insert_in_order1([E|Es],List,Replace) -> + List1 = insert_elem(E,List,[],Replace), + insert_in_order1(Es,List1,Replace); +insert_in_order1([],List,_Replace) -> List; -insert_in_order(E,List) -> - insert_elem(E,List,[]). +insert_in_order1(E,List,Replace) -> + insert_elem(E,List,[],Replace). + -%% replace an existing entry (same key) or add last in list -insert_elem({Key,_}=E,[{Key,_}|Rest],SoFar) -> +insert_elem({Key,_}=E,[{Key,_}|Rest],SoFar,true) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem({E,_},[E|Rest],SoFar) -> +insert_elem({E,_},[E|Rest],SoFar,true) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem(E,[E|Rest],SoFar) -> +insert_elem(E,[E|Rest],SoFar,true) -> + lists:reverse([E|SoFar]) ++ Rest; + +insert_elem({all,_}=E,_,SoFar,_Replace) -> + lists:reverse([E|SoFar]); +insert_elem(_E,[all|_],SoFar,_Replace) -> + lists:reverse(SoFar); +insert_elem(_E,[{all,_}],SoFar,_Replace) -> + lists:reverse(SoFar); +insert_elem({Key,_}=E,[{Key,[]}|Rest],SoFar,_Replace) -> lists:reverse([E|SoFar]) ++ Rest; -insert_elem(E,[E1|Rest],SoFar) -> - insert_elem(E,Rest,[E1|SoFar]); -insert_elem(E,[],SoFar) -> +insert_elem(E,[E1|Rest],SoFar,Replace) -> + insert_elem(E,Rest,[E1|SoFar],Replace); +insert_elem(E,[],SoFar,_Replace) -> lists:reverse([E|SoFar]). ref2node(all_nodes,_Refs) -> diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 445fce1db8..b7fa7947e2 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -485,6 +485,8 @@ loop(Mode,TestData,StartDir) -> {'EXIT',Pid,Reason} -> case ets:lookup(?conn_table,Pid) of [#conn{address=A,callback=CB}] -> + ErrorStr = io_lib:format("~tp", [Reason]), + ErrorHtml = ct_logs:escape_chars(ErrorStr), %% A connection crashed - remove the connection but don't die ct_logs:tc_log_async(ct_error_notify, ?MAX_IMPORTANCE, @@ -492,8 +494,8 @@ loop(Mode,TestData,StartDir) -> "Connection process died: " "Pid: ~w, Address: ~p, " "Callback: ~w\n" - "Reason: ~p\n\n", - [Pid,A,CB,Reason]), + "Reason: ~ts\n\n", + [Pid,A,CB,ErrorHtml]), catch CB:close(Pid), %% in case CB:close failed to do this: unregister_connection(Pid), diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl index e6970a2bad..a8c4a455e1 100644 --- a/lib/common_test/src/cth_log_redirect.erl +++ b/lib/common_test/src/cth_log_redirect.erl @@ -30,7 +30,8 @@ pre_init_per_suite/3, pre_end_per_suite/3, post_end_per_suite/4, pre_init_per_group/3, post_init_per_group/4, pre_end_per_group/3, post_end_per_group/4, - pre_init_per_testcase/3, post_end_per_testcase/4]). + pre_init_per_testcase/3, post_init_per_testcase/4, + pre_end_per_testcase/3, post_end_per_testcase/4]). %% Event handler Callbacks -export([init/1, @@ -89,6 +90,12 @@ pre_init_per_testcase(TC, Config, State) -> set_curr_func(TC, Config), {Config, State}. +post_init_per_testcase(_TC, _Config, Return, State) -> + {Return, State}. + +pre_end_per_testcase(_TC, Config, State) -> + {Config, State}. + post_end_per_testcase(_TC, _Config, Result, State) -> %% Make sure that the event queue is flushed %% before ending this test case. |