aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/Makefile3
-rw-r--r--lib/common_test/src/common_test.app.src6
-rw-r--r--lib/common_test/src/ct.erl29
-rw-r--r--lib/common_test/src/ct_framework.erl317
-rw-r--r--lib/common_test/src/ct_hooks.erl9
-rw-r--r--lib/common_test/src/ct_logs.erl110
-rw-r--r--lib/common_test/src/ct_master.erl13
-rw-r--r--lib/common_test/src/ct_master_logs.erl175
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/ct_run.erl150
-rw-r--r--lib/common_test/src/ct_testspec.erl18
-rw-r--r--lib/common_test/src/ct_util.hrl3
-rw-r--r--lib/common_test/src/cth_surefire.erl199
-rw-r--r--lib/common_test/src/vts.erl4
14 files changed, 677 insertions, 361 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.erl b/lib/common_test/src/ct.erl
index 0a77527b2f..3c6e68101d 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -64,7 +64,7 @@
print/1, print/2, print/3,
pal/1, pal/2, pal/3,
capture_start/0, capture_stop/0, capture_get/0, capture_get/1,
- fail/1, fail/2, comment/1, comment/2,
+ fail/1, fail/2, comment/1, comment/2, make_priv_dir/0,
testcases/2, userdata/2, userdata/3,
timetrap/1, get_timetrap_info/0, sleep/1]).
@@ -146,7 +146,8 @@ run(TestDirs) ->
%%% {silent_connections,Conns} | {stylesheet,CSSFile} |
%%% {cover,CoverSpecFile} | {step,StepOpts} |
%%% {event_handler,EventHandlers} | {include,InclDirs} |
-%%% {auto_compile,Bool} | {multiply_timetraps,M} | {scale_timetraps,Bool} |
+%%% {auto_compile,Bool} | {create_priv_dir,CreatePrivDir} |
+%%% {multiply_timetraps,M} | {scale_timetraps,Bool} |
%%% {repeat,N} | {duration,DurTime} | {until,StopTime} |
%%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
%%% {refresh_logs,LogDir} | {logopts,LogOpts} | {basic_html,Bool} |
@@ -171,6 +172,7 @@ run(TestDirs) ->
%%% EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
%%% InitArgs = [term()]
%%% InclDirs = [string()] | string()
+%%% CreatePrivDir = auto_per_run | auto_per_tc | manual_per_tc
%%% M = integer()
%%% N = integer()
%%% DurTime = string(HHMMSS)
@@ -673,6 +675,15 @@ send_html_comment(Comment) ->
ct_util:set_testdata({comment,Html}),
test_server:comment(Html).
+%%%-----------------------------------------------------------------
+%%% @spec make_priv_dir() -> ok | {error,Reason}
+%%% Reason = term()
+%%% @doc If the test has been started with the create_priv_dir
+%%% option set to manual_per_tc, in order for the test case to use
+%%% the private directory, it must first create it by calling
+%%% this function.
+make_priv_dir() ->
+ test_server:make_priv_dir().
%%%-----------------------------------------------------------------
%%% @spec get_target_name(Handle) -> {ok,TargetName} | {error,Reason}
@@ -984,13 +995,21 @@ remove_config(Callback, Config) ->
%%%-----------------------------------------------------------------
%%% @spec timetrap(Time) -> ok
-%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity
+%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity | Func
%%% Hours = integer()
%%% Mins = integer()
%%% Secs = integer()
%%% Millisecs = integer() | float()
-%%%
-%%% @doc <p>Use this function to set a new timetrap for the running test case.</p>
+%%% Func = {M,F,A} | fun()
+%%% M = atom()
+%%% F = atom()
+%%% A = list()
+%%%
+%%% @doc <p>Use this function to set a new timetrap for the running test case.
+%%% If the argument is <code>Func</code>, the timetrap will be triggered
+%%% when this function returns. <code>Func</code> may also return a new
+%%% <code>Time</code> value, which in that case will be the value for the
+%%% new timetrap.</p>
timetrap(Time) ->
test_server:timetrap_cancel(),
test_server:timetrap(Time).
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index cdd8a6a596..e4fbf95fdd 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}};
@@ -237,42 +212,35 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) ->
{auto_skip,{require_failed,Reason}};
{'EXIT',Reason} ->
{auto_skip,Reason};
- {ok,FinalConfig} ->
- case MergeResult of
- {error,Reason} ->
- %% suite0 configure finished now, report that
- %% first test case actually failed
- {skip,Reason};
- _ ->
- case get('$test_server_framework_test') of
- undefined ->
- ct_suite_init(Mod, FuncSpec, FinalConfig);
- Fun ->
- case Fun(init_tc, FinalConfig) of
- NewConfig when is_list(NewConfig) ->
- {ok,NewConfig};
- Else ->
- Else
- end
+ {ok,Config1} ->
+ case get('$test_server_framework_test') of
+ undefined ->
+ ct_suite_init(Suite, FuncSpec, Config1);
+ Fun ->
+ case Fun(init_tc, Config1) of
+ NewConfig when is_list(NewConfig) ->
+ {ok,NewConfig};
+ Else ->
+ Else
end
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 +260,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 +281,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 +293,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 +314,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.
@@ -372,16 +339,12 @@ get_suite_name(Mod, _) ->
Mod.
%% Check that alias names are not already in use
-check_for_clashes(TCInfo, GrPathInfo, SuiteInfo) ->
- {CurrGrInfo,SearchIn} = case GrPathInfo of
- [] -> {[],[SuiteInfo]};
- [Curr|Path] -> {Curr,[SuiteInfo|Path]}
- end,
+check_for_clashes(TCInfo, [CurrGrInfo|Path], SuiteInfo) ->
ReqNames = fun(Info) -> [element(2,R) || R <- Info,
size(R) == 3,
require == element(1,R)]
end,
- ExistingNames = lists:flatten([ReqNames(L) || L <- SearchIn]),
+ ExistingNames = lists:flatten([ReqNames(L) || L <- [SuiteInfo|Path]]),
CurrGrReqNs = ReqNames(CurrGrInfo),
GrClashes = [Name || Name <- CurrGrReqNs,
true == lists:member(Name, ExistingNames)],
@@ -421,34 +384,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 +417,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 +505,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 +559,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 +577,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 +649,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 +807,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 +1150,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 +1411,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 +1512,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 +1548,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_logs.erl b/lib/common_test/src/ct_logs.erl
index b2f669fefe..012f947fdd 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -36,6 +36,7 @@
-export([make_last_run_index/0]).
-export([make_all_suites_index/1,make_all_runs_index/1]).
-export([get_ts_html_wrapper/3]).
+-export([xhtml/2, locate_default_css_file/0, make_relative/1]).
%% Logging stuff directly from testcase
-export([tc_log/3,tc_log/4,tc_log_async/3,tc_print/3,tc_pal/3,ct_log/3,
@@ -920,33 +921,48 @@ insert_dir(D,[D1|Ds]) ->
insert_dir(D,[]) ->
[D].
-make_last_run_index([Name|Rest], Result, TotSucc, TotFail, UserSkip, AutoSkip,
- TotNotBuilt, Missing) ->
- case last_test(Name) of
+make_last_run_index([Name|Rest], Result, TotSucc, TotFail,
+ UserSkip, AutoSkip, TotNotBuilt, Missing) ->
+ case get_run_dirs(Name) of
false ->
%% Silently skip.
- make_last_run_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip,
- TotNotBuilt, Missing);
- LastLogDir ->
+ make_last_run_index(Rest, Result, TotSucc, TotFail,
+ UserSkip, AutoSkip, TotNotBuilt, Missing);
+ LogDirs ->
SuiteName = filename:rootname(filename:basename(Name)),
- case make_one_index_entry(SuiteName, LastLogDir, "-", false, Missing) of
- {Result1,Succ,Fail,USkip,ASkip,NotBuilt} ->
- %% for backwards compatibility
- AutoSkip1 = case catch AutoSkip+ASkip of
- {'EXIT',_} -> undefined;
- Res -> Res
- end,
- make_last_run_index(Rest, [Result|Result1], TotSucc+Succ,
- TotFail+Fail, UserSkip+USkip, AutoSkip1,
- TotNotBuilt+NotBuilt, Missing);
- error ->
- make_last_run_index(Rest, Result, TotSucc, TotFail, UserSkip, AutoSkip,
- TotNotBuilt, Missing)
- end
+ {Result1,TotSucc1,TotFail1,UserSkip1,AutoSkip1,TotNotBuilt1} =
+ make_last_run_index1(SuiteName, LogDirs, Result,
+ TotSucc, TotFail,
+ UserSkip, AutoSkip,
+ TotNotBuilt, Missing),
+ make_last_run_index(Rest, Result1, TotSucc1, TotFail1,
+ UserSkip1, AutoSkip1,
+ TotNotBuilt1, Missing)
end;
+
make_last_run_index([], Result, TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, _) ->
{ok, [Result|total_row(TotSucc, TotFail, UserSkip, AutoSkip, TotNotBuilt, false)],
{TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}}.
+
+make_last_run_index1(SuiteName, [LogDir | LogDirs], Result, TotSucc, TotFail,
+ UserSkip, AutoSkip, TotNotBuilt, Missing) ->
+ case make_one_index_entry(SuiteName, LogDir, "-", false, Missing) of
+ {Result1,Succ,Fail,USkip,ASkip,NotBuilt} ->
+ %% for backwards compatibility
+ AutoSkip1 = case catch AutoSkip+ASkip of
+ {'EXIT',_} -> undefined;
+ Res -> Res
+ end,
+ make_last_run_index1(SuiteName, LogDirs, [Result|Result1], TotSucc+Succ,
+ TotFail+Fail, UserSkip+USkip, AutoSkip1,
+ TotNotBuilt+NotBuilt, Missing);
+ error ->
+ make_last_run_index1(SuiteName, LogDirs, Result, TotSucc, TotFail,
+ UserSkip, AutoSkip, TotNotBuilt, Missing)
+ end;
+make_last_run_index1(_, [], Result, TotSucc, TotFail,
+ UserSkip, AutoSkip, TotNotBuilt, _) ->
+ {Result,TotSucc,TotFail,UserSkip,AutoSkip,TotNotBuilt}.
make_one_index_entry(SuiteName, LogDir, Label, All, Missing) ->
case count_cases(LogDir) of
@@ -1231,18 +1247,18 @@ header1(Title, SubTitle) ->
["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n",
"\"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"]),
- "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n",
- "<head>\n",
- "<title>" ++ Title ++ " " ++ SubTitle ++ "</title>\n",
- "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
- xhtml("",
- ["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">"]),
- "</head>\n",
- body_tag(),
- "<center>\n",
- "<h1>" ++ Title ++ "</h1>\n",
- "</center>\n",
- SubTitleHTML,"\n"].
+ "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n",
+ "<head>\n",
+ "<title>" ++ Title ++ " " ++ SubTitle ++ "</title>\n",
+ "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
+ xhtml("",
+ ["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">"]),
+ "</head>\n",
+ body_tag(),
+ "<center>\n",
+ "<h1>" ++ Title ++ "</h1>\n",
+ "</center>\n",
+ SubTitleHTML,"\n"].
index_footer() ->
["</table>\n"
@@ -1698,8 +1714,8 @@ make_all_suites_index(NewTestData = {_TestName,DirName}) ->
sort_logdirs([Dir|Dirs],Groups) ->
TestName = filename:rootname(filename:basename(Dir)),
case filelib:wildcard(filename:join(Dir,"run.*")) of
- [RunDir] ->
- Groups1 = insert_test(TestName,{filename:basename(RunDir),RunDir},Groups),
+ RunDirs = [_|_] ->
+ Groups1 = sort_logdirs1(TestName,RunDirs,Groups),
sort_logdirs(Dirs,Groups1);
_ -> % ignore missing run directory
sort_logdirs(Dirs,Groups)
@@ -1707,6 +1723,12 @@ sort_logdirs([Dir|Dirs],Groups) ->
sort_logdirs([],Groups) ->
lists:keysort(1,sort_each_group(Groups)).
+sort_logdirs1(TestName,[RunDir|RunDirs],Groups) ->
+ Groups1 = insert_test(TestName,{filename:basename(RunDir),RunDir},Groups),
+ sort_logdirs1(TestName,RunDirs,Groups1);
+sort_logdirs1(_,[],Groups) ->
+ Groups.
+
insert_test(Test,IxDir,[{Test,IxDirs}|Groups]) ->
[{Test,[IxDir|IxDirs]}|Groups];
insert_test(Test,IxDir,[]) ->
@@ -1998,21 +2020,17 @@ notify_and_unlock_file(File) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec last_test(Dir) -> string() | false
+%%% @spec get_run_dirs(Dir) -> [string()] | false
%%%
%%% @doc
%%%
-last_test(Dir) ->
- last_test(filelib:wildcard(filename:join(Dir, "run.[1-2]*")), false).
-
-last_test([Run|Rest], false) ->
- last_test(Rest, Run);
-last_test([Run|Rest], Latest) when Run > Latest ->
- last_test(Rest, Run);
-last_test([_|Rest], Latest) ->
- last_test(Rest, Latest);
-last_test([], Latest) ->
- Latest.
+get_run_dirs(Dir) ->
+ case filelib:wildcard(filename:join(Dir, "run.[1-2]*")) of
+ [] ->
+ false;
+ RunDirs ->
+ lists:sort(RunDirs)
+ end.
%%%-----------------------------------------------------------------
%%% @spec xhtml(HTML, XHTML) -> HTML | XHTML
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index 2ea2ba106a..0d32bb0072 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -25,6 +25,7 @@
-export([run/1,run/3,run/4]).
-export([run_on_node/2,run_on_node/3]).
-export([run_test/1,run_test/2]).
+-export([basic_html/1]).
-export([abort/0,abort/1,progress/0]).
@@ -277,7 +278,17 @@ abort(Node) when is_atom(Node) ->
progress() ->
call(progress).
-
+%%%-----------------------------------------------------------------
+%%% @spec basic_html(Bool) -> ok
+%%% Bool = true | false
+%%%
+%%% @doc If set to true, the ct_master logs will be written on a
+%%% primitive html format, not using the Common Test CSS style
+%%% sheet.
+basic_html(Bool) ->
+ application:set_env(common_test_master, basic_html, Bool),
+ ok.
+
%%%-----------------------------------------------------------------
%%% MASTER, runs on central controlling node.
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index 244faace06..8fd346670f 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -23,7 +23,8 @@
%%% node.</p>
-module(ct_master_logs).
--export([start/2, make_all_runs_index/0, log/3, nodedir/2, stop/0]).
+-export([start/2, make_all_runs_index/0, log/3, nodedir/2,
+ stop/0]).
-record(state, {log_fd, start_time, logdir, rundir,
nodedir_ix_fd, nodes, nodedirs=[]}).
@@ -32,6 +33,7 @@
-define(all_runs_name, "master_runs.html").
-define(nodedir_index_name, "index.html").
-define(details_file_name,"details.info").
+-define(css_default, "ct_default.css").
-define(table_color,"lightblue").
%%%--------------------------------------------------------------------
@@ -87,6 +89,40 @@ init(Parent,LogDir,Nodes) ->
RunDirAbs = filename:join(LogDir,RunDir),
file:make_dir(RunDirAbs),
write_details_file(RunDirAbs,{node(),Nodes}),
+
+ case basic_html() of
+ true ->
+ put(basic_html, true);
+ BasicHtml ->
+ put(basic_html, BasicHtml),
+ %% copy stylesheet to log dir (both top dir and test run
+ %% dir) so logs are independent of Common Test installation
+ CTPath = code:lib_dir(common_test),
+ CSSFileSrc = filename:join(filename:join(CTPath, "priv"),
+ ?css_default),
+ CSSFileDestTop = filename:join(LogDir, ?css_default),
+ CSSFileDestRun = filename:join(RunDirAbs, ?css_default),
+ case file:copy(CSSFileSrc, CSSFileDestTop) of
+ {error,Reason0} ->
+ io:format(user, "ERROR! "++
+ "CSS file ~p could not be copied to ~p. "++
+ "Reason: ~p~n",
+ [CSSFileSrc,CSSFileDestTop,Reason0]),
+ exit({css_file_error,CSSFileDestTop});
+ _ ->
+ case file:copy(CSSFileSrc, CSSFileDestRun) of
+ {error,Reason1} ->
+ io:format(user, "ERROR! "++
+ "CSS file ~p could not be copied to ~p. "++
+ "Reason: ~p~n",
+ [CSSFileSrc,CSSFileDestRun,Reason1]),
+ exit({css_file_error,CSSFileDestRun});
+ _ ->
+ ok
+ end
+ end
+ end,
+
make_all_runs_index(LogDir),
CtLogFd = open_ct_master_log(RunDirAbs),
NodeStr =
@@ -164,8 +200,9 @@ open_ct_master_log(Dir) ->
"</style>\n",
[]),
io:format(Fd,
- "<br><h2>Progress Log</h2>\n"
- "<pre>\n",[]),
+ xhtml("<br><h2>Progress Log</h2>\n<pre>\n",
+ "<br /><h2>Progress Log</h2>\n<pre>\n"),
+ []),
Fd.
close_ct_master_log(Fd) ->
@@ -178,18 +215,10 @@ config_table(Vars) ->
config_table_header() ->
["<h2>Configuration</h2>\n",
- "<table border=\"3\" cellpadding=\"5\" bgcolor=\"",?table_color,
- "\"\n",
+ xhtml(["<table border=\"3\" cellpadding=\"5\" "
+ "bgcolor=\"",?table_color,"\"\n"], "<table>\n"),
"<tr><th>Key</th><th>Value</th></tr>\n"].
-%%
-%% keep for possible later use
-%%
-%%config_table1([{Key,Value}|Vars]) ->
-%% ["<tr><td>", atom_to_list(Key), "</td>\n",
-%% "<td><pre>",io_lib:format("~p",[Value]),"</pre></td></tr>\n" |
-%% config_table1(Vars)];
-
config_table1([]) ->
["</table>\n"].
@@ -210,10 +239,10 @@ open_nodedir_index(Dir,StartTime) ->
print_nodedir(Node,RunDir,Fd) ->
Index = filename:join(RunDir,"index.html"),
io:format(Fd,
- ["<TR>\n"
- "<TD ALIGN=center>",atom_to_list(Node),"</TD>\n",
- "<TD ALIGN=left><A HREF=\"",Index,"\">",Index,"</A></TD>\n",
- "</TR>\n"],[]),
+ ["<tr>\n"
+ "<td align=center>",atom_to_list(Node),"</td>\n",
+ "<td align=left><a href=\"",Index,"\">",Index,"</a></td>\n",
+ "</tr>\n"],[]),
ok.
close_nodedir_index(Fd) ->
@@ -222,12 +251,12 @@ close_nodedir_index(Fd) ->
nodedir_index_header(StartTime) ->
[header("Log Files " ++ format_time(StartTime)) |
- ["<CENTER>\n",
- "<P><A HREF=\"",?ct_master_log_name,"\">Common Test Master Log</A></P>",
- "<TABLE border=\"3\" cellpadding=\"5\" ",
- "BGCOLOR=\"",?table_color,"\">\n",
- "<th><B>Node</B></th>\n",
- "<th><B>Log</B></th>\n",
+ ["<center>\n",
+ "<p><a href=\"",?ct_master_log_name,"\">Common Test Master Log</a></p>",
+ xhtml(["<table border=\"3\" cellpadding=\"5\" "
+ "bgcolor=\"",?table_color,"\">\n"], "<table>\n"),
+ "<th><b>Node</b></th>\n",
+ "<th><b>Log</b></th>\n",
"\n"]].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -279,20 +308,20 @@ runentry(Dir) ->
{"unknown",""}
end,
Index = filename:join(Dir,?nodedir_index_name),
- ["<TR>\n"
- "<TD ALIGN=center><A HREF=\"",Index,"\">",timestamp(Dir),"</A></TD>\n",
- "<TD ALIGN=center>",MasterStr,"</TD>\n",
- "<TD ALIGN=center>",NodesStr,"</TD>\n",
- "</TR>\n"].
+ ["<tr>\n"
+ "<td align=center><a href=\"",Index,"\">",timestamp(Dir),"</a></td>\n",
+ "<td align=center>",MasterStr,"</td>\n",
+ "<td align=center>",NodesStr,"</td>\n",
+ "</tr>\n"].
all_runs_header() ->
[header("Master Test Runs") |
- ["<CENTER>\n",
- "<TABLE border=\"3\" cellpadding=\"5\" "
- "BGCOLOR=\"",?table_color,"\">\n"
- "<th><B>History</B></th>\n"
- "<th><B>Master Host</B></th>\n"
- "<th><B>Test Nodes</B></th>\n"
+ ["<center>\n",
+ xhtml(["<table border=\"3\" cellpadding=\"5\" "
+ "bgcolor=\"",?table_color,"\">\n"], "<table>\n"),
+ "<th><b>History</b></th>\n"
+ "<th><b>Master Host</b></th>\n"
+ "<th><b>Test Nodes</b></th>\n"
"\n"]].
timestamp(Dir) ->
@@ -318,44 +347,46 @@ read_details_file(Dir) ->
%%%--------------------------------------------------------------------
header(Title) ->
- ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
- "<!-- autogenerated by '"++atom_to_list(?MODULE)++"'. -->\n"
- "<HTML>\n",
- "<HEAD>\n",
-
- "<TITLE>" ++ Title ++ "</TITLE>\n",
- "<META HTTP-EQUIV=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE\">\n",
-
- "</HEAD>\n",
-
+ CSSFile = xhtml(fun() -> "" end,
+ fun() -> make_relative(locate_default_css_file()) end),
+ [xhtml(["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n",
+ "<html>\n"],
+ ["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n",
+ "\"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"]),
+ "<!-- autogenerated by '"++atom_to_list(?MODULE)++"' -->\n",
+ "<head>\n",
+ "<title>" ++ Title ++ "</title>\n",
+ "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n",
+ xhtml("",
+ ["<link rel=\"stylesheet\" href=\"",CSSFile,"\" type=\"text/css\">"]),
+ "</head>\n",
body_tag(),
-
- "<!-- ---- DOCUMENT TITLE ---- -->\n",
-
- "<CENTER>\n",
- "<H1>" ++ Title ++ "</H1>\n",
- "</CENTER>\n",
-
- "<!-- ---- CONTENT ---- -->\n"].
+ "<center>\n",
+ "<h1>" ++ Title ++ "</h1>\n",
+ "</center>\n"].
index_footer() ->
- ["</TABLE>\n"
- "</CENTER>\n" | footer()].
+ ["</table>\n"
+ "</center>\n" | footer()].
footer() ->
- ["<P><CENTER>\n"
- "<HR>\n"
- "<P><FONT SIZE=-1>\n"
+ ["<center>\n",
+ xhtml("<br><hr>\n", "<br />\n"),
+ xhtml("<p><font size=\"-1\">\n", "<div class=\"copyright\">"),
"Copyright &copy; ", year(),
- " <A HREF=\"http://erlang.ericsson.se\">Open Telecom Platform</A><BR>\n"
- "Updated: <!date>", current_time(), "<!/date><BR>\n"
- "</FONT>\n"
- "</CENTER>\n"
+ " <a href=\"http://www.erlang.org\">Open Telecom Platform</a>",
+ xhtml("<br>\n", "<br />\n"),
+ "Updated: <!date>", current_time(), "<!/date>",
+ xhtml("<br>\n", "<br />\n"),
+ xhtml("</font></p>\n", "</div>\n"),
+ "</center>\n"
"</body>\n"].
body_tag() ->
- "<body bgcolor=\"#FFFFFF\" text=\"#000000\" link=\"#0000FF\""
- "vlink=\"#800080\" alink=\"#FF0000\">\n".
+ xhtml("<body bgcolor=\"#FFFFFF\" text=\"#000000\" link=\"#0000FF\" "
+ "vlink=\"#800080\" alink=\"#FF0000\">\n",
+ "<body>\n").
current_time() ->
format_time(calendar:local_time()).
@@ -404,6 +435,23 @@ log_timestamp(Now) ->
lists:flatten(io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w",
[H,M,S])).
+basic_html() ->
+ case application:get_env(common_test_master, basic_html) of
+ {ok,true} ->
+ true;
+ _ ->
+ false
+ end.
+
+xhtml(HTML, XHTML) ->
+ ct_logs:xhtml(HTML, XHTML).
+
+locate_default_css_file() ->
+ ct_logs:locate_default_css_file().
+
+make_relative(Dir) ->
+ ct_logs:make_relative(Dir).
+
force_write_file(Name,Contents) ->
force_delete(Name),
file:write_file(Name,Contents).
@@ -452,3 +500,4 @@ cast(Msg) ->
_Pid ->
?MODULE ! Msg
end.
+
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index be3c485b75..e6eb135ae8 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -116,7 +116,7 @@ spawn_tester(script,Ctrl,Args) ->
spawn_tester(func,Ctrl,Opts) ->
Tester = fun() ->
- case catch ct_run:run_test1(Opts) of
+ case catch ct_run:run_test2(Opts) of
{'EXIT',Reason} ->
exit(Reason);
Result ->
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 05b10bca32..717154667f 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -37,7 +37,7 @@
%% Misc internal functions
--export([variables_file_name/1,script_start1/2,run_test1/1]).
+-export([variables_file_name/1,script_start1/2,run_test2/1]).
-include("ct_event.hrl").
-include("ct_util.hrl").
@@ -57,12 +57,13 @@
config = [],
event_handlers = [],
ct_hooks = [],
- enable_builtin_hooks = true,
+ enable_builtin_hooks,
include = [],
silent_connections,
stylesheet,
multiply_timetraps = 1,
scale_timetraps = false,
+ create_priv_dir,
testspecs = [],
tests}).
@@ -178,12 +179,16 @@ script_start1(Parent, Args) ->
fun([CT]) -> list_to_atom(CT);
([]) -> true
end, false, Args),
+ CreatePrivDir = get_start_opt(create_priv_dir,
+ fun([PD]) -> list_to_atom(PD);
+ ([]) -> auto_per_tc
+ end, Args),
EvHandlers = event_handler_args2opts(Args),
CTHooks = ct_hooks_args2opts(Args),
EnableBuiltinHooks = get_start_opt(enable_builtin_hooks,
fun([CT]) -> list_to_atom(CT);
- ([]) -> true
- end, true, Args),
+ ([]) -> undefined
+ end, undefined, Args),
%% check flags and set corresponding application env variables
@@ -255,7 +260,8 @@ script_start1(Parent, Args) ->
silent_connections = SilentConns,
stylesheet = Stylesheet,
multiply_timetraps = MultTT,
- scale_timetraps = ScaleTT},
+ scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir},
%% check if log files should be refreshed or go on to run tests...
Result = run_or_refresh(StartOpts, Args),
@@ -322,12 +328,21 @@ script_start2(StartOpts = #opts{vts = undefined,
Cover = choose_val(StartOpts#opts.cover,
SpecStartOpts#opts.cover),
- MultTT = choose_val(StartOpts#opts.multiply_timetraps,
- SpecStartOpts#opts.multiply_timetraps),
- ScaleTT = choose_val(StartOpts#opts.scale_timetraps,
- SpecStartOpts#opts.scale_timetraps),
- AllEvHs = merge_vals([StartOpts#opts.event_handlers,
- SpecStartOpts#opts.event_handlers]),
+ MultTT =
+ choose_val(StartOpts#opts.multiply_timetraps,
+ SpecStartOpts#opts.multiply_timetraps),
+ ScaleTT =
+ choose_val(StartOpts#opts.scale_timetraps,
+ SpecStartOpts#opts.scale_timetraps),
+
+ CreatePrivDir =
+ choose_val(StartOpts#opts.create_priv_dir,
+ SpecStartOpts#opts.create_priv_dir),
+
+ AllEvHs =
+ merge_vals([StartOpts#opts.event_handlers,
+ SpecStartOpts#opts.event_handlers]),
+
AllCTHooks = merge_vals(
[StartOpts#opts.ct_hooks,
SpecStartOpts#opts.ct_hooks]),
@@ -354,7 +369,8 @@ script_start2(StartOpts = #opts{vts = undefined,
EnableBuiltinHooks,
include = AllInclude,
multiply_timetraps = MultTT,
- scale_timetraps = ScaleTT}}
+ scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir}}
end;
_ ->
{undefined,StartOpts}
@@ -567,6 +583,7 @@ script_usage() ->
"\n\t[-no_auto_compile]"
"\n\t[-multiply_timetraps N]"
"\n\t[-scale_timetraps]"
+ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
"\n\t[-basic_html]\n\n"),
io:format("Run tests from command line:\n\n"
"\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |"
@@ -586,6 +603,7 @@ script_usage() ->
"\n\t[-no_auto_compile]"
"\n\t[-multiply_timetraps N]"
"\n\t[-scale_timetraps]"
+ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
"\n\t[-basic_html]"
"\n\t[-repeat N [-force_stop]] |"
"\n\t[-duration HHMMSS [-force_stop]] |"
@@ -606,6 +624,7 @@ script_usage() ->
"\n\t[-no_auto_compile]"
"\n\t[-multiply_timetraps N]"
"\n\t[-scale_timetraps]"
+ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
"\n\t[-basic_html]"
"\n\t[-repeat N [-force_stop]] |"
"\n\t[-duration HHMMSS [-force_stop]] |"
@@ -763,7 +782,7 @@ run_test2(StartOpts) ->
fun(EBH) when EBH == true;
EBH == false ->
EBH
- end, true, StartOpts),
+ end, undefined, StartOpts),
%% silent connections
SilentConns = get_start_opt(silent_connections,
@@ -782,6 +801,9 @@ run_test2(StartOpts) ->
MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts),
ScaleTT = get_start_opt(scale_timetraps, value, false, StartOpts),
+ %% create unique priv dir names
+ CreatePrivDir = get_start_opt(create_priv_dir, value, StartOpts),
+
%% auto compile & include files
Include =
case proplists:get_value(auto_compile, StartOpts) of
@@ -842,7 +864,8 @@ run_test2(StartOpts) ->
silent_connections = SilentConns,
stylesheet = Stylesheet,
multiply_timetraps = MultiplyTT,
- scale_timetraps = ScaleTT},
+ scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir},
%% test specification
case proplists:get_value(spec, StartOpts) of
@@ -889,6 +912,8 @@ run_spec_file(Relaxed,
SpecOpts#opts.multiply_timetraps),
ScaleTT = choose_val(Opts#opts.scale_timetraps,
SpecOpts#opts.scale_timetraps),
+ CreatePrivDir = choose_val(Opts#opts.create_priv_dir,
+ SpecOpts#opts.create_priv_dir),
AllEvHs = merge_vals([Opts#opts.event_handlers,
SpecOpts#opts.event_handlers]),
AllInclude = merge_vals([Opts#opts.include,
@@ -912,6 +937,7 @@ run_spec_file(Relaxed,
testspecs = AbsSpecs,
multiply_timetraps = MultTT,
scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir,
ct_hooks = AllCTHooks,
enable_builtin_hooks = EnableBuiltinHooks
},
@@ -1170,7 +1196,8 @@ get_data_for_node(#testspec{label = Labels,
enable_builtin_hooks = EnableBuiltinHooks,
include = Incl,
multiply_timetraps = MTs,
- scale_timetraps = STs}, Node) ->
+ scale_timetraps = STs,
+ create_priv_dir = PDs}, Node) ->
Label = proplists:get_value(Node, Labels),
Profile = proplists:get_value(Node, Profiles),
LogDir = case proplists:get_value(Node, LogDirs) of
@@ -1184,6 +1211,7 @@ get_data_for_node(#testspec{label = Labels,
Cover = proplists:get_value(Node, CoverFs),
MT = proplists:get_value(Node, MTs),
ST = proplists:get_value(Node, STs),
+ CreatePrivDir = proplists:get_value(Node, PDs),
ConfigFiles = [{?ct_config_txt,F} || {N,F} <- Cfgs, N==Node] ++
[CBF || {N,CBF} <- UsrCfgs, N==Node],
EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node],
@@ -1200,7 +1228,8 @@ get_data_for_node(#testspec{label = Labels,
enable_builtin_hooks = EnableBuiltinHooks,
include = Include,
multiply_timetraps = MT,
- scale_timetraps = ST}.
+ scale_timetraps = ST,
+ create_priv_dir = CreatePrivDir}.
refresh_logs(LogDir) ->
{ok,Cwd} = file:get_cwd(),
@@ -1384,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 ->
@@ -1746,25 +1776,31 @@ set_group_leader_same_as_shell() ->
false
end.
-check_and_add([{TestDir0,M,_} | Tests], Added) ->
+check_and_add([{TestDir0,M,_} | Tests], Added, PA) ->
case locate_test_dir(TestDir0, M) of
{ok,TestDir} ->
case lists:member(TestDir, Added) of
true ->
- check_and_add(Tests, Added);
+ check_and_add(Tests, Added, PA);
false ->
- true = code:add_patha(TestDir),
- check_and_add(Tests, [TestDir|Added])
+ case lists:member(rm_trailing_slash(TestDir),
+ code:get_path()) of
+ false ->
+ true = code:add_patha(TestDir),
+ check_and_add(Tests, [TestDir|Added], [TestDir|PA]);
+ true ->
+ check_and_add(Tests, [TestDir|Added], PA)
+ end
end;
{error,_} ->
{error,{invalid_directory,TestDir0}}
end;
-check_and_add([], _) ->
- ok.
+check_and_add([], _, PA) ->
+ {ok,PA}.
do_run_test(Tests, Skip, Opts) ->
- case check_and_add(Tests, []) of
- ok ->
+ case check_and_add(Tests, [], []) of
+ {ok,AddedToPath} ->
ct_util:set_testdata({stats,{0,0,{0,0}}}),
ct_util:set_testdata({cover,undefined}),
test_server_ctrl:start_link(local),
@@ -1842,6 +1878,8 @@ do_run_test(Tests, Skip, Opts) ->
test_server_ctrl:multiply_timetraps(Opts#opts.multiply_timetraps),
test_server_ctrl:scale_timetraps(Opts#opts.scale_timetraps),
+ test_server_ctrl:create_priv_dir(choose_val(Opts#opts.create_priv_dir,
+ auto_per_run)),
ct_event:notify(#event{name=start_info,
node=node(),
data={NoOfTests,NoOfSuites,NoOfCases}}),
@@ -1858,7 +1896,9 @@ do_run_test(Tests, Skip, Opts) ->
end,
lists:foreach(fun(Suite) ->
maybe_cleanup_interpret(Suite, Opts#opts.step)
- end, CleanUp);
+ end, CleanUp),
+ [code:del_path(Dir) || Dir <- AddedToPath],
+ ok;
Error ->
Error
end.
@@ -2289,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]);
@@ -2347,31 +2387,38 @@ event_handler_init_args2opts([]) ->
%% relative dirs "post run_test erl_args" is not kept!
rel_to_abs(CtArgs) ->
{PA,PZ} = get_pa_pz(CtArgs, [], []),
- io:format(user, "~n", []),
[begin
- code:del_path(filename:basename(D)),
- Abs = filename:absname(D),
- code:add_pathz(Abs),
- if D /= Abs ->
+ Dir = rm_trailing_slash(D),
+ Abs = make_abs(Dir),
+ if Dir /= Abs ->
+ code:del_path(Dir),
+ code:del_path(Abs),
io:format(user, "Converting ~p to ~p and re-inserting "
"with add_pathz/1~n",
- [D, Abs]);
+ [Dir, Abs]);
true ->
- ok
- end
+ code:del_path(Dir)
+ end,
+ code:add_pathz(Abs)
end || D <- PZ],
[begin
- code:del_path(filename:basename(D)),
- Abs = filename:absname(D),
- code:add_patha(Abs),
- if D /= Abs ->
+ Dir = rm_trailing_slash(D),
+ Abs = make_abs(Dir),
+ if Dir /= Abs ->
+ code:del_path(Dir),
+ code:del_path(Abs),
io:format(user, "Converting ~p to ~p and re-inserting "
"with add_patha/1~n",
- [D, Abs]);
- true ->ok
- end
+ [Dir, Abs]);
+ true ->
+ code:del_path(Dir)
+ end,
+ code:add_patha(Abs)
end || D <- PA],
- io:format(user, "~n", []).
+ io:format(user, "~n", []).
+
+rm_trailing_slash(Dir) ->
+ filename:join(filename:split(Dir)).
get_pa_pz([{pa,Dirs} | Args], PA, PZ) ->
get_pa_pz(Args, PA ++ Dirs, PZ);
@@ -2382,6 +2429,19 @@ get_pa_pz([_ | Args], PA, PZ) ->
get_pa_pz([], PA, PZ) ->
{PA,PZ}.
+make_abs(RelDir) ->
+ Tokens = filename:split(filename:absname(RelDir)),
+ filename:join(lists:reverse(make_abs1(Tokens, []))).
+
+make_abs1([".."|Dirs], [_Dir|Path]) ->
+ make_abs1(Dirs, Path);
+make_abs1(["."|Dirs], Path) ->
+ make_abs1(Dirs, Path);
+make_abs1([Dir|Dirs], Path) ->
+ make_abs1(Dirs, [Dir|Path]);
+make_abs1([], Path) ->
+ Path.
+
%% This function translates ct:run_test/1 start options
%% to ct_run start arguments (on the init arguments format) -
%% this is useful mainly for testing the ct_run start functions.
@@ -2419,6 +2479,10 @@ opts2args(EnvStartOpts) ->
[{scale_timetraps,[]}];
({scale_timetraps,false}) ->
[];
+ ({create_priv_dir,auto_per_run}) ->
+ [];
+ ({create_priv_dir,PD}) when is_atom(PD) ->
+ [{create_priv_dir,[atom_to_list(PD)]}];
({force_stop,true}) ->
[{force_stop,[]}];
({force_stop,false}) ->
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index b68cbd3aa1..5b197c0c81 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -568,6 +568,21 @@ add_tests([{scale_timetraps,Node,ST}|Ts],Spec) ->
add_tests([{scale_timetraps,ST}|Ts],Spec) ->
add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec);
+%% --- create_priv_dir ---
+add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec) ->
+ Tests = lists:map(fun(N) -> {create_priv_dir,N,PD} end, list_nodes(Spec)),
+ add_tests(Tests++Ts,Spec);
+add_tests([{create_priv_dir,Nodes,PD}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,create_priv_dir,[PD],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{create_priv_dir,Node,PD}|Ts],Spec) ->
+ PDs = Spec#testspec.create_priv_dir,
+ PDs1 = [{ref2node(Node,Spec#testspec.nodes),PD} |
+ lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,PDs)],
+ add_tests(Ts,Spec#testspec{create_priv_dir=PDs1});
+add_tests([{create_priv_dir,PD}|Ts],Spec) ->
+ add_tests([{create_priv_dir,all_nodes,PD}|Ts],Spec);
+
%% --- config ---
add_tests([{config,all_nodes,Files}|Ts],Spec) ->
Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)),
@@ -1158,7 +1173,8 @@ valid_terms() ->
{skip_groups,6},
{skip_groups,7},
{skip_cases,5},
- {skip_cases,6}
+ {skip_cases,6},
+ {create_priv_dir,2}
].
%% this function "guesses" if the user has misspelled a term name
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index bde832811a..082599a9c6 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -43,9 +43,10 @@
include=[],
multiply_timetraps=[],
scale_timetraps=[],
+ create_priv_dir=[],
alias=[],
tests=[],
- merge_tests = true }).
+ merge_tests=true}).
-record(cover, {app=none,
level=details,
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]) ->
+ "&gt;" ++ sanitize(T);
+sanitize([$<|T]) ->
+ "&lt;" ++ sanitize(T);
+sanitize([$"|T]) ->
+ "&quot;" ++ sanitize(T);
+sanitize([$'|T]) ->
+ "&apos;" ++ sanitize(T);
+sanitize([$&|T]) ->
+ "&amp;" ++ 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) ->