diff options
Diffstat (limited to 'lib/common_test/src')
-rw-r--r-- | lib/common_test/src/Makefile | 3 | ||||
-rw-r--r-- | lib/common_test/src/common_test.app.src | 6 | ||||
-rw-r--r-- | lib/common_test/src/ct_framework.erl | 284 | ||||
-rw-r--r-- | lib/common_test/src/ct_hooks.erl | 9 | ||||
-rw-r--r-- | lib/common_test/src/ct_run.erl | 5 | ||||
-rw-r--r-- | lib/common_test/src/cth_surefire.erl | 199 | ||||
-rw-r--r-- | lib/common_test/src/vts.erl | 4 |
7 files changed, 330 insertions, 180 deletions
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile index 125aa828fb..e9555de35a 100644 --- a/lib/common_test/src/Makefile +++ b/lib/common_test/src/Makefile @@ -69,7 +69,8 @@ MODULES= \ ct_slave \ ct_hooks\ ct_hooks_lock\ - cth_log_redirect + cth_log_redirect\ + cth_surefire TARGET_MODULES= $(MODULES:%=$(EBIN)/%) BEAM_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src index 7fba484b18..bdd48fbc6b 100644 --- a/lib/common_test/src/common_test.app.src +++ b/lib/common_test/src/common_test.app.src @@ -25,6 +25,8 @@ ct_framework, ct_ftp, ct_gen_conn, + ct_hooks, + ct_hooks_lock, ct_logs, ct_make, ct_master, @@ -45,7 +47,9 @@ ct_config, ct_config_plain, ct_config_xml, - ct_slave + ct_slave, + cth_log_redirect, + cth_surefire ]}, {registered, [ct_logs, ct_util_server, diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index cdd8a6a596..187794e78b 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -29,7 +29,8 @@ -export([get_logopts/0, format_comment/1, get_html_wrapper/3]). --export([error_in_suite/1, ct_init_per_group/2, ct_end_per_group/2]). +-export([error_in_suite/1, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2]). -export([make_all_conf/3, make_conf/5]). @@ -54,6 +55,7 @@ init_tc(Mod,Func,Config) -> %% in case Mod == ct_framework, lookup the suite name Suite = get_suite_name(Mod, Config), + %% check if previous testcase was interpreted and has left %% a "dead" trace window behind - if so, kill it case ct_util:get_testdata(interpret) of @@ -63,27 +65,17 @@ init_tc(Mod,Func,Config) -> _ -> ok end, - %% check if we need to add defaults explicitly because - %% there's no init_per_suite exported from Mod - {InitFailed,DoInit} = - case ct_util:get_testdata(curr_tc) of - {Suite,{suite0_failed,_}=Failure} -> - {Failure,false}; - {?MODULE,_} -> % should not really happen - {false,false}; - {Suite,_} -> % Func is not 1st case in suite - {false,false}; - _ when Func == init_per_suite -> % defaults will be added anyway - {false,false}; - _ -> % first case in suite - {false,true} - end, - case InitFailed of - false -> + + case ct_util:get_testdata(curr_tc) of + {Suite,{suite0_failed,{require,Reason}}} -> + {skip,{require_failed_in_suite0,Reason}}; + {Suite,{suite0_failed,_}=Failure} -> + {skip,Failure}; + _ -> ct_util:set_testdata({curr_tc,{Suite,Func}}), case ct_util:read_suite_data({seq,Suite,Func}) of undefined -> - init_tc1(Mod,Func,Config,DoInit); + init_tc1(Mod,Suite,Func,Config); Seq when is_atom(Seq) -> case ct_util:read_suite_data({seq,Suite,Seq}) of [Func|TCs] -> % this is the 1st case in Seq @@ -96,17 +88,13 @@ init_tc(Mod,Func,Config) -> _ -> ok end, - init_tc1(Mod,Func,Config,DoInit); + init_tc1(Mod,Suite,Func,Config); {failed,Seq,BadFunc} -> {skip,{sequence_failed,Seq,BadFunc}} - end; - {_,{require,Reason}} -> - {skip,{require_failed_in_suite0,Reason}}; - _ -> - {skip,InitFailed} + 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(), @@ -117,14 +105,16 @@ init_tc1(?MODULE,error_in_suite,[Config0],_) when is_list(Config0) -> Reason -> {skip,Reason} end; -init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> + +init_tc1(Mod,Suite,Func,[Config0]) when is_list(Config0) -> Config1 = case ct_util:read_suite_data(last_saved_config) of - {{Mod,LastFunc},SavedConfig} -> % last testcase + {{Suite,LastFunc},SavedConfig} -> % last testcase [{saved_config,{LastFunc,SavedConfig}} | lists:keydelete(saved_config,1,Config0)]; - {{LastSuite,InitOrEnd},SavedConfig} when InitOrEnd == init_per_suite ; - InitOrEnd == end_per_suite -> + {{LastSuite,InitOrEnd}, + SavedConfig} when InitOrEnd == init_per_suite ; + InitOrEnd == end_per_suite -> %% last suite [{saved_config,{LastSuite,SavedConfig}} | lists:keydelete(saved_config,1,Config0)]; @@ -133,13 +123,14 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> end, ct_util:delete_suite_data(last_saved_config), Config = lists:keydelete(watchdog,1,Config1), - if Func /= init_per_suite, DoInit /= true -> - ok; - true -> + + if Func == init_per_suite -> %% delete all default values used in previous suite ct_config:delete_default_config(suite), %% release all name -> key bindings (once per suite) - ct_config:release_allocated() + ct_config:release_allocated(); + Func /= init_per_suite -> + ok end, GroupPath = ?val(tc_group_path, Config, []), @@ -154,9 +145,7 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> true -> ct_config:delete_default_config(testcase) end, - %% in case Mod == ct_framework, lookup the suite name - Suite = get_suite_name(Mod, Config), - case add_defaults(Mod,Func,AllGroups,DoInit) of + case add_defaults(Mod,Func,AllGroups) of Error = {suite0_failed,_} -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, @@ -166,35 +155,25 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> {error,Error}; {SuiteInfo,MergeResult} -> case MergeResult of - {error,Reason} when DoInit == false -> + {error,Reason} -> ct_logs:init_tc(false), ct_event:notify(#event{name=tc_start, node=node(), data={Mod,FuncSpec}}), {skip,Reason}; _ -> - init_tc2(Mod,Func,SuiteInfo,MergeResult, - Config,DoInit) + init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) end end; -init_tc1(_Mod,_Func,Args,_DoInit) -> - {ok,Args}. -init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> - %% if first testcase fails when there's no init_per_suite - %% we must do suite/0 configurations before skipping test - MergedInfo = - case MergeResult of - {error,_} when DoInit == true -> - SuiteInfo; - _ -> - MergeResult - end, +init_tc1(_Mod,_Suite,_Func,Args) -> + {ok,Args}. +init_tc2(Mod,Suite,Func,SuiteInfo,MergeResult,Config) -> %% timetrap must be handled before require - MergedInfo1 = timetrap_first(MergedInfo, [], []), + MergedInfo = timetrap_first(MergeResult, [], []), %% tell logger to use specified style sheet - case lists:keysearch(stylesheet,1,MergedInfo++Config) of + case lists:keysearch(stylesheet,1,MergeResult++Config) of {value,{stylesheet,SSFile}} -> ct_logs:set_stylesheet(Func,add_data_dir(SSFile,Config)); _ -> @@ -209,7 +188,7 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> %% list of {Type,Bool} tuples, e.g. {telnet,true}), case ct_util:get_overridden_silenced_connections() of undefined -> - case lists:keysearch(silent_connections,1,MergedInfo++Config) of + case lists:keysearch(silent_connections,1,MergeResult++Config) of {value,{silent_connections,Conns}} -> ct_util:silence_connections(Conns); _ -> @@ -218,18 +197,14 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> Conns -> ct_util:silence_connections(Conns) end, - if Func /= init_per_suite, DoInit /= true -> - ct_logs:init_tc(false); - true -> - ct_logs:init_tc(true) - end, + ct_logs:init_tc(Func == init_per_suite), FuncSpec = group_or_func(Func,Config), ct_event:notify(#event{name=tc_start, node=node(), data={Mod,FuncSpec}}), - case catch configure(MergedInfo1,MergedInfo1,SuiteInfo, - {FuncSpec,DoInit},Config) of + case catch configure(MergedInfo,MergedInfo,SuiteInfo, + FuncSpec,Config) of {suite0_failed,Reason} -> ct_util:set_testdata({curr_tc,{Mod,{suite0_failed,{require,Reason}}}}), {skip,{require_failed_in_suite0,Reason}}; @@ -246,7 +221,7 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> _ -> case get('$test_server_framework_test') of undefined -> - ct_suite_init(Mod, FuncSpec, FinalConfig); + ct_suite_init(Suite, FuncSpec, FinalConfig); Fun -> case Fun(init_tc, FinalConfig) of NewConfig when is_list(NewConfig) -> @@ -258,21 +233,21 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> end end. -ct_suite_init(Mod, Func, [Config]) when is_list(Config) -> - case ct_hooks:init_tc( Mod, Func, Config) of +ct_suite_init(Suite, Func, [Config]) when is_list(Config) -> + case ct_hooks:init_tc(Suite, Func, Config) of NewConfig when is_list(NewConfig) -> {ok, [NewConfig]}; Else -> Else end. -add_defaults(Mod,Func, GroupPath, DoInit) -> +add_defaults(Mod,Func, GroupPath) -> Suite = get_suite_name(Mod, GroupPath), case (catch Suite:suite()) of {'EXIT',{undef,_}} -> SuiteInfo = merge_with_suite_defaults(Suite,[]), SuiteInfoNoCTH = [I || I <- SuiteInfo, element(1,I) =/= ct_hooks], - case add_defaults1(Mod,Func, GroupPath, SuiteInfoNoCTH, DoInit) of + case add_defaults1(Mod,Func, GroupPath, SuiteInfoNoCTH) of Error = {error,_} -> {SuiteInfo,Error}; MergedInfo -> {SuiteInfo,MergedInfo} end; @@ -292,7 +267,7 @@ add_defaults(Mod,Func, GroupPath, DoInit) -> SuiteInfoNoCTH = [I || I <- SuiteInfo1, element(1,I) =/= ct_hooks], case add_defaults1(Mod,Func, GroupPath, - SuiteInfoNoCTH, DoInit) of + SuiteInfoNoCTH) of Error = {error,_} -> {SuiteInfo1,Error}; MergedInfo -> {SuiteInfo1,MergedInfo} end; @@ -313,7 +288,7 @@ add_defaults(Mod,Func, GroupPath, DoInit) -> {suite0_failed,bad_return_value} end. -add_defaults1(Mod,Func, GroupPath, SuiteInfo, DoInit) -> +add_defaults1(Mod,Func, GroupPath, SuiteInfo) -> Suite = get_suite_name(Mod, GroupPath), %% GroupPathInfo (for subgroup on level X) = %% [LevelXGroupInfo, LevelX-1GroupInfo, ..., TopLevelGroupInfo] @@ -325,8 +300,7 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo, DoInit) -> _ -> [] end end, GroupPath), - Args = if Func == init_per_group; Func == ct_init_per_group; - Func == end_per_group; Func == ct_end_per_group -> + Args = if Func == init_per_group ; Func == end_per_group -> [?val(name, hd(GroupPath))]; true -> [] @@ -347,7 +321,7 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo, DoInit) -> (default_config == element(1,SDDef)))], case check_for_clashes(TestCaseInfo, GroupPathInfo, SuiteReqs) of [] -> - add_defaults2(Mod,Func, TCAndGroupInfo,SuiteInfo,SuiteReqs, DoInit); + add_defaults2(Mod,Func, TCAndGroupInfo,SuiteInfo,SuiteReqs); Clashes -> {error,{config_name_already_in_use,Clashes}} end. @@ -421,34 +395,25 @@ keysmember([Key,Pos|Next], List) -> keysmember([], _) -> true. -add_defaults2(Mod,init_per_suite, IPSInfo, SuiteInfo,SuiteReqs, false) -> - add_defaults2(Mod,init_per_suite, IPSInfo, SuiteInfo,SuiteReqs, true); +add_defaults2(_Mod,init_per_suite, IPSInfo, SuiteInfo,SuiteReqs) -> + Info = lists:flatten([IPSInfo, SuiteReqs]), + lists:flatten([Info,remove_info_in_prev(Info, [SuiteInfo])]); -add_defaults2(_Mod,IPG, IPGAndGroupInfo, SuiteInfo,SuiteReqs, DoInit) when - IPG == init_per_group ; IPG == ct_init_per_group -> - %% If DoInit == true, we have to process the suite() list, otherwise - %% it has already been handled (see clause for init_per_suite) - case DoInit of - true -> - %% note: we know for sure this is a top level group - Info = lists:flatten([IPGAndGroupInfo, SuiteReqs]), - Info ++ remove_info_in_prev(Info, [SuiteInfo]); - false -> - SuiteInfo1 = - remove_info_in_prev(lists:flatten([IPGAndGroupInfo, - SuiteReqs]), [SuiteInfo]), - %% don't require terms in prev groups (already processed) - case IPGAndGroupInfo of - [IPGInfo] -> - lists:flatten([IPGInfo,SuiteInfo1]); - [IPGInfo | [CurrGroupInfo | PrevGroupInfo]] -> - PrevGroupInfo1 = delete_require_terms(PrevGroupInfo), - lists:flatten([IPGInfo,CurrGroupInfo,PrevGroupInfo1, - SuiteInfo1]) - end +add_defaults2(_Mod,init_per_group, IPGAndGroupInfo, SuiteInfo,SuiteReqs) -> + SuiteInfo1 = + remove_info_in_prev(lists:flatten([IPGAndGroupInfo, + SuiteReqs]), [SuiteInfo]), + %% don't require terms in prev groups (already processed) + case IPGAndGroupInfo of + [IPGInfo] -> + lists:flatten([IPGInfo,SuiteInfo1]); + [IPGInfo | [CurrGroupInfo | PrevGroupInfo]] -> + PrevGroupInfo1 = delete_require_terms(PrevGroupInfo), + lists:flatten([IPGInfo,CurrGroupInfo,PrevGroupInfo1, + SuiteInfo1]) end; -add_defaults2(_Mod,_Func, TCAndGroupInfo, SuiteInfo,SuiteReqs, false) -> +add_defaults2(_Mod,_Func, TCAndGroupInfo, SuiteInfo,SuiteReqs) -> %% Include require elements from test case info and current group, %% but not from previous groups or suite/0 (since we've already required %% those vars). Let test case info elements override group and suite @@ -463,14 +428,7 @@ add_defaults2(_Mod,_Func, TCAndGroupInfo, SuiteInfo,SuiteReqs, false) -> PrevGroupInfo1 = delete_require_terms(PrevGroupInfo), lists:flatten([TCInfo,CurrGroupInfo,PrevGroupInfo1, SuiteInfo1]) - end; - -add_defaults2(_Mod,_Func, TCInfo, SuiteInfo,SuiteReqs, true) -> - %% Here we have to process the suite info list also (no call to - %% init_per_suite before this first test case). This TC can't belong - %% to a group, or the clause for (ct_)init_per_group would've caught this. - Info = lists:flatten([TCInfo, SuiteReqs]), - lists:flatten([Info,remove_info_in_prev(Info, [SuiteInfo])]). + end. delete_require_terms([Info | Prev]) -> Info1 = [T || T <- Info, @@ -558,18 +516,9 @@ configure([],_,_,_,Config) -> %% function and be scoped 'group', or come from the testcase %% info function and then be scoped 'testcase' -required_default(Name,Key,Info,SuiteInfo,{FuncSpec,true}) -> - case try_set_default(Name,Key,SuiteInfo,suite) of - ok -> - ok; - _ -> - required_default(Name,Key,Info,[],{FuncSpec,false}) - end; -required_default(Name,Key,Info,_,{init_per_suite,_}) -> +required_default(Name,Key,Info,_,init_per_suite) -> try_set_default(Name,Key,Info,suite); -required_default(Name,Key,Info,_,{{init_per_group,GrName,_},_}) -> - try_set_default(Name,Key,Info,{group,GrName}); -required_default(Name,Key,Info,_,{{ct_init_per_group,GrName,_},_}) -> +required_default(Name,Key,Info,_,{init_per_group,GrName,_}) -> try_set_default(Name,Key,Info,{group,GrName}); required_default(Name,Key,Info,_,_FuncSpec) -> try_set_default(Name,Key,Info,testcase). @@ -621,6 +570,9 @@ end_tc(Mod,Func,{Result,[Args]}, Return) -> end_tc(Mod,Func,self(),Result,Args,Return). end_tc(Mod,Func,TCPid,Result,Args,Return) -> + %% in case Mod == ct_framework, lookup the suite name + Suite = get_suite_name(Mod, Args), + case lists:keysearch(watchdog,1,Args) of {value,{watchdog,Dog}} -> test_server:timetrap_cancel(Dog); false -> ok @@ -636,60 +588,62 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> end, ct_util:delete_testdata(comment), ct_util:delete_suite_data(last_saved_config), - FuncSpec = - case group_or_func(Func,Args) of - {_,GroupName,_Props} = Group -> - if Func == end_per_group; Func == ct_end_per_group -> - ct_config:delete_default_config({group,GroupName}); - true -> ok - end, - case lists:keysearch(save_config,1,Args) of - {value,{save_config,SaveConfig}} -> - ct_util:save_suite_data( - last_saved_config, - {Mod,{group,GroupName}}, - SaveConfig), - Group; - false -> - Group - end; - _ -> - case lists:keysearch(save_config,1,Args) of - {value,{save_config,SaveConfig}} -> - ct_util:save_suite_data(last_saved_config, - {Mod,Func},SaveConfig), - Func; - false -> - Func - end - end, - ct_util:reset_silent_connections(), + + FuncSpec = case group_or_func(Func,Args) of + {_,_GroupName,_} = Group -> Group; + _ -> Func + end, case get('$test_server_framework_test') of undefined -> {FinalResult,FinalNotify} = case ct_hooks:end_tc( - Mod, FuncSpec, Args, Result, Return) of + Suite, FuncSpec, Args, Result, Return) of '$ct_no_change' -> {ok,Result}; FinalResult1 -> {FinalResult1,FinalResult1} end, - % send sync notification so that event handlers may print - % in the log file before it gets closed + %% send sync notification so that event handlers may print + %% in the log file before it gets closed ct_event:sync_notify(#event{name=tc_done, node=node(), data={Mod,FuncSpec, tag_cth(FinalNotify)}}); Fun -> - % send sync notification so that event handlers may print - % in the log file before it gets closed + %% send sync notification so that event handlers may print + %% in the log file before it gets closed ct_event:sync_notify(#event{name=tc_done, node=node(), data={Mod,FuncSpec,tag(Result)}}), FinalResult = Fun(end_tc, Return) + end, + + case FuncSpec of + {_,GroupName,_Props} -> + if Func == end_per_group -> + ct_config:delete_default_config({group,GroupName}); + true -> ok + end, + case lists:keysearch(save_config,1,Args) of + {value,{save_config,SaveConfig}} -> + ct_util:save_suite_data(last_saved_config, + {Suite,{group,GroupName}}, + SaveConfig); + false -> + ok + end; + _ -> + case lists:keysearch(save_config,1,Args) of + {value,{save_config,SaveConfig}} -> + ct_util:save_suite_data(last_saved_config, + {Suite,Func},SaveConfig); + false -> + ok + end end, + ct_util:reset_silent_connections(), case FinalResult of {skip,{sequence_failed,_,_}} -> @@ -706,7 +660,7 @@ end_tc(Mod,Func,TCPid,Result,Args,Return) -> end, case Func of end_per_suite -> - ct_util:match_delete_suite_data({seq,Mod,'_'}); + ct_util:match_delete_suite_data({seq,Suite,'_'}); _ -> ok end, @@ -864,9 +818,7 @@ mark_as_failed1(_,_,_,[]) -> ok. group_or_func(Func, Config) when Func == init_per_group; - Func == end_per_group; - Func == ct_init_per_group; - Func == ct_end_per_group -> + Func == end_per_group -> case ?val(tc_group_properties, Config) of undefined -> {Func,unknown,[]}; @@ -1209,8 +1161,8 @@ make_conf(Mod, Name, Props, TestSpec) -> "end_per_group/2 missing for group " "~p in ~p, using default.", [Name,Mod]), - {{?MODULE,ct_init_per_group}, - {?MODULE,ct_end_per_group}, + {{?MODULE,init_per_group}, + {?MODULE,end_per_group}, [{suite,Mod}]} end, {conf,[{name,Name}|Props++ExtraProps],InitConf,TestSpec,EndConf}. @@ -1470,22 +1422,31 @@ error_in_suite(Config) -> Reason = test_server:lookup_config(error,Config), exit(Reason). +%% if init_per_suite and end_per_suite are missing in the suite, +%% these will be called instead (without any trace of them in the +%% log files), only so that it's possible to call hook functions +%% for configuration +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + %% if the group config functions are missing in the suite, %% use these instead -ct_init_per_group(GroupName, Config) -> +init_per_group(GroupName, Config) -> ct:comment(io_lib:format("start of ~p", [GroupName])), ct_logs:log("TEST INFO", "init_per_group/2 for ~w missing " "in suite, using default.", [GroupName]), Config. -ct_end_per_group(GroupName, _) -> +end_per_group(GroupName, _) -> ct:comment(io_lib:format("end of ~p", [GroupName])), ct_logs:log("TEST INFO", "end_per_group/2 for ~w missing " "in suite, using default.", [GroupName]), ok. - %%%----------------------------------------------------------------- %%% @spec report(What,Data) -> ok @@ -1562,10 +1523,6 @@ report(What,Data) -> ok; {end_per_group,_} -> ok; - {ct_init_per_group,_} -> - ok; - {ct_end_per_group,_} -> - ok; {_,ok} -> add_to_stats(ok); {_,{skipped,{failed,{_,init_per_testcase,_}}}} -> @@ -1602,8 +1559,7 @@ report(What,Data) -> data=Data}), ct_hooks:on_tc_skip(What, Data), if Case /= end_per_suite, - Case /= end_per_group, - Case /= ct_end_per_group -> + Case /= end_per_group -> add_to_stats(auto_skipped); true -> ok diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index c42adbbdd9..2a23cd992b 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -70,8 +70,7 @@ terminate(Hooks) -> {skip, Reason :: term()} | {auto_skip, Reason :: term()} | {fail, Reason :: term()}. -init_tc(ct_framework, _Func, Args) -> - Args; + init_tc(Mod, init_per_suite, Config) -> Info = try proplists:get_value(ct_hooks, Mod:suite(),[]) of List when is_list(List) -> @@ -104,27 +103,21 @@ init_tc(_Mod, TC, Config) -> {auto_skip, Reason :: term()} | {fail, Reason :: term()} | ok | '$ct_no_change'. -end_tc(ct_framework, _Func, _Args, Result, _Return) -> - Result; end_tc(Mod, init_per_suite, Config, _Result, Return) -> call(fun call_generic/3, Return, [post_init_per_suite, Mod, Config], '$ct_no_change'); - end_tc(Mod, end_per_suite, Config, Result, _Return) -> call(fun call_generic/3, Result, [post_end_per_suite, Mod, Config], '$ct_no_change'); - end_tc(_Mod, {init_per_group, GroupName, _}, Config, _Result, Return) -> call(fun call_generic/3, Return, [post_init_per_group, GroupName, Config], '$ct_no_change'); - end_tc(Mod, {end_per_group, GroupName, Opts}, Config, Result, _Return) -> Res = call(fun call_generic/3, Result, [post_end_per_group, GroupName, Config], '$ct_no_change'), maybe_stop_locker(Mod, GroupName,Opts), Res; - end_tc(_Mod, TC, Config, Result, _Return) -> call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config], '$ct_no_change'). diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 38632a89c2..717154667f 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1413,7 +1413,8 @@ do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) -> %% which framework it runs under. case os:getenv("TEST_SERVER_FRAMEWORK") of false -> - os:putenv("TEST_SERVER_FRAMEWORK", "ct_framework"); + os:putenv("TEST_SERVER_FRAMEWORK", "ct_framework"), + os:putenv("TEST_SERVER_FRAMEWORK_NAME", "common_test"); "ct_framework" -> ok; Other -> @@ -2328,7 +2329,7 @@ ct_hooks_args2opts(Args) -> Acc end,[],Args). -ct_hooks_args2opts([CTH,Arg,Prio,"and"| Rest],Acc) -> +ct_hooks_args2opts([CTH,Arg,Prio,"and"| Rest],Acc) when Arg /= "and" -> ct_hooks_args2opts(Rest,[{list_to_atom(CTH), parse_cth_args(Arg), parse_cth_args(Prio)}|Acc]); diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl new file mode 100644 index 0000000000..c42f956b3a --- /dev/null +++ b/lib/common_test/src/cth_surefire.erl @@ -0,0 +1,199 @@ +%%% @doc Common Test Framework functions handling test specifications. +%%% +%%% <p>This module creates a junit report of the test run if plugged in +%%% as a suite_callback.</p> + +-module(cth_surefire). + +%% Suite Callbacks +-export([id/1, init/2]). + +-export([pre_init_per_suite/3]). +-export([post_init_per_suite/4]). +-export([pre_end_per_suite/3]). +-export([post_end_per_suite/4]). + +-export([pre_init_per_group/3]). +-export([post_init_per_group/4]). +-export([pre_end_per_group/3]). +-export([post_end_per_group/4]). + +-export([pre_init_per_testcase/3]). +-export([post_end_per_testcase/4]). + +-export([on_tc_fail/3]). +-export([on_tc_skip/3]). + +-export([terminate/1]). + +-record(state, { filepath, axis, properties, package, hostname, + curr_suite, curr_suite_ts, curr_group = [], curr_tc, + curr_log_dir, timer, tc_log, + test_cases = [], + test_suites = [] }). + +-record(testcase, { log, group, classname, name, time, failure, timestamp }). +-record(testsuite, { errors, failures, hostname, name, tests, + time, timestamp, id, package, + properties, testcases }). + +id(Opts) -> + filename:absname(proplists:get_value(path, Opts, "junit_report.xml")). + +init(Path, Opts) -> + {ok, Host} = inet:gethostname(), + #state{ filepath = Path, + hostname = proplists:get_value(hostname,Opts,Host), + package = proplists:get_value(package,Opts), + axis = proplists:get_value(axis,Opts,[]), + properties = proplists:get_value(properties,Opts,[]), + timer = now() }. + +pre_init_per_suite(Suite,Config,State) -> + {Config, init_tc(State#state{ curr_suite = Suite, curr_suite_ts = now() }, + Config) }. + +post_init_per_suite(_Suite,Config, Result, State) -> + {Result, end_tc(init_per_suite,Config,Result,State)}. + +pre_end_per_suite(_Suite,Config,State) -> {Config, init_tc(State, Config)}. + +post_end_per_suite(_Suite,Config,Result,State) -> + NewState = end_tc(end_per_suite,Config,Result,State), + TCs = NewState#state.test_cases, + Suite = get_suite(NewState, TCs), + {Result, State#state{ test_cases = [], + test_suites = [Suite | State#state.test_suites]}}. + +pre_init_per_group(Group,Config,State) -> + {Config, init_tc(State#state{ curr_group = [Group|State#state.curr_group]}, + Config)}. + +post_init_per_group(_Group,Config,Result,State) -> + {Result, end_tc(init_per_group,Config,Result,State)}. + +pre_end_per_group(_Group,Config,State) -> {Config, init_tc(State, Config)}. + +post_end_per_group(_Group,Config,Result,State) -> + NewState = end_tc(end_per_group, Config, Result, State), + {Result, NewState#state{ curr_group = tl(NewState#state.curr_group)}}. + +pre_init_per_testcase(_TC,Config,State) -> {Config, init_tc(State, Config)}. + +post_end_per_testcase(TC,Config,Result,State) -> + {Result, end_tc(TC,Config, Result,State)}. + +on_tc_fail(_TC, Res, State) -> + TCs = State#state.test_cases, + TC = hd(State#state.test_cases), + NewTC = TC#testcase{ failure = + {fail,lists:flatten(io_lib:format("~p",[Res]))} }, + State#state{ test_cases = [NewTC | tl(TCs)]}. + +on_tc_skip(_Tc, Res, State) -> + TCs = State#state.test_cases, + TC = hd(State#state.test_cases), + NewTC = TC#testcase{ + failure = + {skipped,lists:flatten(io_lib:format("~p",[Res]))} }, + State#state{ test_cases = [NewTC | tl(TCs)]}. + +init_tc(State, Config) -> + State#state{ timer = now(), + tc_log = proplists:get_value(tc_logfile, Config)}. + +end_tc(Func, Config, Res, State) when is_atom(Func) -> + end_tc(atom_to_list(Func), Config, Res, State); +end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite, + curr_group = Groups, + timer = TS, tc_log = Log } ) -> + ClassName = atom_to_list(Suite), + PGroup = string:join([ atom_to_list(Group)|| + Group <- lists:reverse(Groups)],"."), + TimeTakes = io_lib:format("~f",[timer:now_diff(now(),TS) / 1000000]), + State#state{ test_cases = [#testcase{ log = Log, + timestamp = now_to_string(TS), + classname = ClassName, + group = PGroup, + name = Name, + time = TimeTakes, + failure = passed }| State#state.test_cases]}. + +get_suite(State, TCs) -> + Total = length(TCs), + Succ = length(lists:filter(fun(#testcase{ failure = F }) -> + F == passed + end,TCs)), + Fail = Total - Succ, + TimeTaken = timer:now_diff(now(),State#state.curr_suite_ts) / 1000000, + #testsuite{ name = atom_to_list(State#state.curr_suite), + package = State#state.package, + time = io_lib:format("~f",[TimeTaken]), + timestamp = now_to_string(State#state.curr_suite_ts), + errors = Fail, tests = Total, testcases = lists:reverse(TCs) }. + +terminate(State) -> + {ok,D} = file:open(State#state.filepath,[write]), + io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []), + io:format(D, to_xml(State), []), + catch file:sync(D), + catch file:close(D). + +to_xml(#testcase{ group = Group, classname = CL, log = L, name = N, time = T, timestamp = TS, failure = F}) -> + ["<testcase ", + [["group=\"",Group,"\""]||Group /= ""]," " + "name=\"",N,"\" " + "time=\"",T,"\" " + "timestamp=\"",TS,"\" " + "log=\"",L,"\">", + case F of + passed -> + []; + {skipped,Reason} -> + ["<skipped type=\"skip\" message=\"Test ",N," in ",CL, + " skipped!\">", sanitize(Reason),"</skipped>"]; + {fail,Reason} -> + ["<failure message=\"Test ",N," in ",CL," failed!\" type=\"crash\">", + sanitize(Reason),"</failure>"] + end,"</testcase>"]; +to_xml(#testsuite{ package = P, hostname = H, errors = E, time = Time, + timestamp = TS, tests = T, name = N, testcases = Cases }) -> + ["<testsuite ", + [["package=\"",P,"\" "]||P /= undefined], + [["hostname=\"",P,"\" "]||H /= undefined], + [["name=\"",N,"\" "]||N /= undefined], + [["time=\"",Time,"\" "]||Time /= undefined], + [["timestamp=\"",TS,"\" "]||TS /= undefined], + "errors=\"",integer_to_list(E),"\" " + "tests=\"",integer_to_list(T),"\">", + [to_xml(Case) || Case <- Cases], + "</testsuite>"]; +to_xml(#state{ test_suites = TestSuites, axis = Axis, properties = Props }) -> + ["<testsuites>",properties_to_xml(Axis,Props), + [to_xml(TestSuite) || TestSuite <- TestSuites],"</testsuites>"]. + +properties_to_xml(Axis,Props) -> + ["<properties>", + [["<property name=\"",Name,"\" axis=\"yes\" value=\"",Value,"\" />"] || {Name,Value} <- Axis], + [["<property name=\"",Name,"\" value=\"",Value,"\" />"] || {Name,Value} <- Props], + "</properties>" + ]. + +sanitize([$>|T]) -> + ">" ++ sanitize(T); +sanitize([$<|T]) -> + "<" ++ sanitize(T); +sanitize([$"|T]) -> + """ ++ sanitize(T); +sanitize([$'|T]) -> + "'" ++ sanitize(T); +sanitize([$&|T]) -> + "&" ++ sanitize(T); +sanitize([H|T]) -> + [H|sanitize(T)]; +sanitize([]) -> + []. + +now_to_string(Now) -> + {{YY,MM,DD},{HH,Mi,SS}} = calendar:now_to_local_time(Now), + io_lib:format("~p-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B",[YY,MM,DD,HH,Mi,SS]). diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index cc8a932887..9dfb0bd6b8 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -766,10 +766,6 @@ report1(tc_done,{_Suite,init_per_group,_},State) -> State; report1(tc_done,{_Suite,end_per_group,_},State) -> State; -report1(tc_done,{_Suite,ct_init_per_group,_},State) -> - State; -report1(tc_done,{_Suite,ct_end_per_group,_},State) -> - State; report1(tc_done,{_Suite,_Case,ok},State) -> State#state{ok=State#state.ok+1}; report1(tc_done,{_Suite,_Case,{failed,_Reason}},State) -> |