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/ct.erl37
-rw-r--r--lib/common_test/src/ct_framework.erl321
-rw-r--r--lib/common_test/src/ct_hooks.erl9
-rw-r--r--lib/common_test/src/ct_logs.erl119
-rw-r--r--lib/common_test/src/ct_repeat.erl2
-rw-r--r--lib/common_test/src/ct_run.erl142
-rw-r--r--lib/common_test/src/ct_testspec.erl18
-rw-r--r--lib/common_test/src/ct_util.erl23
-rw-r--r--lib/common_test/src/ct_util.hrl3
-rw-r--r--lib/common_test/src/vts.erl4
10 files changed, 380 insertions, 298 deletions
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index e0e82283c4..63a8adbc63 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]).
@@ -585,8 +585,16 @@ capture_get([]) ->
%%% @doc Terminate a test case with the given error
%%% <code>Reason</code>.
fail(Reason) ->
- exit({test_case_failed,Reason}).
-
+ try
+ exit({test_case_failed,Reason})
+ catch
+ Class:R ->
+ case erlang:get_stacktrace() of
+ [{?MODULE,fail,1,_}|Stk] -> ok;
+ Stk -> ok
+ end,
+ erlang:raise(Class, R, Stk)
+ end.
%%%-----------------------------------------------------------------
%%% @spec fail(Format, Args) -> void()
@@ -599,13 +607,21 @@ fail(Reason) ->
fail(Format, Args) ->
try io_lib:format(Format, Args) of
Str ->
- exit({test_case_failed,lists:flatten(Str)})
+ try
+ exit({test_case_failed,lists:flatten(Str)})
+ catch
+ Class:R ->
+ case erlang:get_stacktrace() of
+ [{?MODULE,fail,2,_}|Stk] -> ok;
+ Stk -> ok
+ end,
+ erlang:raise(Class, R, Stk)
+ end
catch
_:BadArgs ->
exit({BadArgs,{?MODULE,fail,[Format,Args]}})
end.
-
%%%-----------------------------------------------------------------
%%% @spec comment(Comment) -> void()
%%% Comment = term()
@@ -657,6 +673,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}
@@ -845,6 +870,8 @@ get_status() ->
get_testdata(Key) ->
case catch ct_util:get_testdata(Key) of
+ {error,ct_util_server_not_running} ->
+ no_tests_running;
Error = {error,_Reason} ->
Error;
{'EXIT',_Reason} ->
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index c24a7c238b..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,
@@ -806,31 +760,36 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end
end,
- io:format(user, "~n- - - - - - - - - - - - - - - - "
- "- - - - - - - - - -~n", []),
+ PrintErr = fun(ErrFormat, ErrArgs) ->
+ Div = "~n- - - - - - - - - - - - - - - - "
+ "- - - - - - - - - -~n",
+ io:format(user, lists:concat([Div,ErrFormat,Div,"~n"]),
+ ErrArgs),
+ ct_logs:tc_log(ct_error_notify, "CT Error Notification",
+ ErrFormat, ErrArgs)
+ end,
case Loc of
- %% we don't use the line parse transform as we compile this
- %% module so location will be on form {M,F}
[{?MODULE,error_in_suite}] ->
- io:format(user, "Error in suite detected: ~s", [ErrStr]);
+ PrintErr("Error in suite detected: ~s", [ErrStr]);
- R when R == unknown; R == undefined ->
- io:format(user, "Error detected: ~s", [ErrStr]);
+ R when R == unknown; R == undefined ->
+ PrintErr("Error detected: ~s", [ErrStr]);
%% if a function specified by all/0 does not exist, we
%% pick up undef here
- [{LastMod,LastFunc}] ->
- io:format(user, "~w:~w could not be executed~n",
- [LastMod,LastFunc]),
- io:format(user, "Reason: ~s", [ErrStr]);
+ [{LastMod,LastFunc}|_] when ErrStr == "undef" ->
+ PrintErr("~w:~w could not be executed~nReason: ~s",
+ [LastMod,LastFunc,ErrStr]);
+
+ [{LastMod,LastFunc}|_] ->
+ PrintErr("~w:~w failed~nReason: ~s", [LastMod,LastFunc,ErrStr]);
[{LastMod,LastFunc,LastLine}|_] ->
%% print error to console, we are only
%% interested in the last executed expression
- io:format(user, "~w:~w failed on line ~w~n",
- [LastMod,LastFunc,LastLine]),
- io:format(user, "Reason: ~s", [ErrStr]),
-
+ PrintErr("~w:~w failed on line ~w~nReason: ~s",
+ [LastMod,LastFunc,LastLine,ErrStr]),
+
case ct_util:read_suite_data({seq,Mod,Func}) of
undefined ->
ok;
@@ -839,8 +798,6 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
mark_as_failed(Seq,Mod,Func,SeqTCs)
end
end,
- io:format(user, "~n- - - - - - - - - - - - - - - - "
- "- - - - - - - - - -~n~n", []),
ok.
%% cases in seq that have already run
@@ -861,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,[]};
@@ -1206,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}.
@@ -1467,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
@@ -1559,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,_}}}} ->
@@ -1599,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_logs.erl b/lib/common_test/src/ct_logs.erl
index 5f0626c0b0..0cd9b5f7cb 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -38,7 +38,7 @@
-export([get_ts_html_wrapper/3]).
%% Logging stuff directly from testcase
--export([tc_log/3,tc_log_async/3,tc_print/3,tc_pal/3,ct_log/3,
+-export([tc_log/3,tc_log/4,tc_log_async/3,tc_print/3,tc_pal/3,ct_log/3,
basic_html/0]).
%% Simulate logger process for use without ct environment running
@@ -333,7 +333,10 @@ add_link(Heading,File,Type) ->
%%% stuff directly from a testcase (i.e. not from within the CT
%%% framework).</p>
tc_log(Category,Format,Args) ->
- cast({log,sync,self(),group_leader(),[{div_header(Category),[]},
+ tc_log(Category,"User",Format,Args).
+
+tc_log(Category,Printer,Format,Args) ->
+ cast({log,sync,self(),group_leader(),[{div_header(Category,Printer),[]},
{Format,Args},
{div_footer(),[]}]}),
ok.
@@ -369,19 +372,18 @@ tc_log_async(Category,Format,Args) ->
%%% <p>This function is called by <code>ct</code> when printing
%%% stuff a testcase on the user console.</p>
tc_print(Category,Format,Args) ->
- print_heading(Category),
- io:format(user,Format,Args),
- io:format(user,"\n\n",[]),
+ Head = get_heading(Category),
+ io:format(user, lists:concat([Head,Format,"\n\n"]), Args),
ok.
-print_heading(default) ->
- io:format(user,
- "----------------------------------------------------\n~s\n",
- [log_timestamp(now())]);
-print_heading(Category) ->
- io:format(user,
- "----------------------------------------------------\n~s ~w\n",
- [log_timestamp(now()),Category]).
+get_heading(default) ->
+ io_lib:format("-----------------------------"
+ "-----------------------\n~s\n",
+ [log_timestamp(now())]);
+get_heading(Category) ->
+ io_lib:format("-----------------------------"
+ "-----------------------\n~s ~w\n",
+ [log_timestamp(now()),Category]).
%%%-----------------------------------------------------------------
@@ -428,8 +430,10 @@ int_footer() ->
"</div>".
div_header(Class) ->
- "<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** User " ++
- log_timestamp(now()) ++ " ***</b>".
+ div_header(Class,"User").
+div_header(Class,Printer) ->
+ "<div class=\"" ++ atom_to_list(Class) ++ "\"><b>*** " ++ Printer ++
+ " " ++ log_timestamp(now()) ++ " ***</b>".
div_footer() ->
"</div>".
@@ -916,33 +920,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
@@ -1694,8 +1713,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)
@@ -1703,6 +1722,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,[]) ->
@@ -1994,21 +2019,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_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..72124f6f21 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").
@@ -63,6 +63,7 @@
stylesheet,
multiply_timetraps = 1,
scale_timetraps = false,
+ create_priv_dir,
testspecs = [],
tests}).
@@ -178,6 +179,10 @@ 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,
@@ -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]] |"
@@ -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.erl b/lib/common_test/src/ct_util.erl
index 3b6ad6f98d..e9bfb2590b 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -827,15 +827,20 @@ get_profile_data(Profile, Key, StartDir) ->
%%%-----------------------------------------------------------------
%%% Internal functions
call(Msg) ->
- MRef = erlang:monitor(process,whereis(ct_util_server)),
- Ref = make_ref(),
- ct_util_server ! {Msg,{self(),Ref}},
- receive
- {Ref, Result} ->
- erlang:demonitor(MRef, [flush]),
- Result;
- {'DOWN',MRef,process,_,Reason} ->
- {error,{ct_util_server_down,Reason}}
+ case whereis(ct_util_server) of
+ undefined ->
+ {error,ct_util_server_not_running};
+ Pid ->
+ MRef = erlang:monitor(process, Pid),
+ Ref = make_ref(),
+ ct_util_server ! {Msg,{self(),Ref}},
+ receive
+ {Ref, Result} ->
+ erlang:demonitor(MRef, [flush]),
+ Result;
+ {'DOWN',MRef,process,_,Reason} ->
+ {error,{ct_util_server_down,Reason}}
+ end
end.
return({To,Ref},Result) ->
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/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) ->