aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src/ct_framework.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src/ct_framework.erl')
-rw-r--r--lib/common_test/src/ct_framework.erl367
1 files changed, 248 insertions, 119 deletions
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index 6066470233..dcf7c97244 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework callback module.
+%%% Common Test Framework callback module.
%%%
-%%% <p>This module exports framework callback functions which are
-%%% called from the test_server.</p>
+%%% This module exports framework callback functions which are
+%%% called from the test_server.
-module(ct_framework).
@@ -42,7 +42,7 @@
-define(rev(L), lists:reverse(L)).
%%%-----------------------------------------------------------------
-%%% @spec init_tc(Mod,Func,Args) -> {ok,NewArgs} | {error,Reason} |
+%%% -spec init_tc(Mod,Func,Args) -> {ok,NewArgs} | {error,Reason} |
%%% {skip,Reason} | {auto_skip,Reason}
%%% Mod = atom()
%%% Func = atom()
@@ -50,7 +50,7 @@
%%% NewArgs = list()
%%% Reason = term()
%%%
-%%% @doc Test server framework callback, called by the test_server
+%%% Test server framework callback, called by the test_server
%%% when a new test case is started.
init_tc(_,{end_per_testcase_not_run,_},[Config]) ->
%% Testcase is completed (skipped or failed), but end_per_testcase
@@ -649,7 +649,7 @@ try_set_default(Name,Key,Info,Where) ->
%%%-----------------------------------------------------------------
-%%% @spec end_tc(Mod,Func,Args) -> {ok,NewArgs}| {error,Reason} |
+%%% -spec end_tc(Mod,Func,Args) -> {ok,NewArgs}| {error,Reason} |
%%% {skip,Reason} | {auto_skip,Reason}
%%% Mod = atom()
%%% Func = atom()
@@ -657,7 +657,7 @@ try_set_default(Name,Key,Info,Where) ->
%%% NewArgs = list()
%%% Reason = term()
%%%
-%%% @doc Test server framework callback, called by the test_server
+%%% Test server framework callback, called by the test_server
%%% when a test case is finished.
end_tc(Mod, Fun, Args) ->
%% Have to keep end_tc/3 for backwards compatibility issues
@@ -696,9 +696,16 @@ end_tc(Mod,IPTC={init_per_testcase,_Func},_TCPid,Result,Args,Return) ->
end
end;
-end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
+end_tc(Mod,Func00,TCPid,Result,Args,Return) ->
%% in case Mod == ct_framework, lookup the suite name
Suite = get_suite_name(Mod, Args),
+ {OnlyCleanup,Func0} =
+ case Func00 of
+ {cleanup,F0} ->
+ {true,F0};
+ _ ->
+ {false,Func00}
+ end,
{Func,FuncSpec,HookFunc} =
case Func0 of
{end_per_testcase_not_run,F} ->
@@ -742,6 +749,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
case HookFunc of
undefined ->
{ok,Result};
+ _ when OnlyCleanup ->
+ {ok,Result};
_ ->
case ct_hooks:end_tc(Suite,HookFunc,Args,Result,Return) of
'$ct_no_change' ->
@@ -752,6 +761,8 @@ end_tc(Mod,Func0,TCPid,Result,Args,Return) ->
end,
FinalResult =
case get('$test_server_framework_test') of
+ _ when OnlyCleanup ->
+ Result1;
undefined ->
%% send sync notification so that event handlers may print
%% in the log file before it gets closed
@@ -903,15 +914,15 @@ tag(_Other) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec error_notification(Mod,Func,Args,Error) -> ok
+%%% -spec error_notification(Mod,Func,Args,Error) -> ok
%%% Mod = atom()
%%% Func = atom()
%%% Args = list()
%%% Error = term()
%%%
-%%% @doc This function is called as the result of testcase
-%%% <code>Func</code> in suite <code>Mod</code> crashing.
-%%% <code>Error</code> specifies the reason for failing.
+%%% This function is called as the result of testcase
+%%% Func in suite Mod crashing.
+%%% Error specifies the reason for failing.
error_notification(Mod,Func,_Args,{Error,Loc}) ->
ErrorSpec = case Error of
{What={_E,_R},Trace} when is_list(Trace) ->
@@ -921,9 +932,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) ->
end,
ErrorStr = case ErrorSpec of
{badmatch,Descr} ->
- Descr1 = lists:flatten(io_lib:format("~tP",[Descr,10])),
- if length(Descr1) > 50 ->
- Descr2 = string:substr(Descr1,1,50),
+ Descr1 = io_lib:format("~tP",[Descr,10]),
+ DescrLength = string:length(Descr1),
+ if DescrLength > 50 ->
+ Descr2 = string:slice(Descr1,0,50),
io_lib:format("{badmatch,~ts...}",[Descr2]);
true ->
io_lib:format("{badmatch,~ts}",[Descr1])
@@ -1048,28 +1060,47 @@ group_or_func(Func, _Config) ->
Func.
%%%-----------------------------------------------------------------
-%%% @spec get_suite(Mod, Func) -> Tests
+%%% -spec get_suite(Mod, Func) -> Tests
%%%
-%%% @doc Called from test_server for every suite (<code>Func==all</code>)
-%%% and every test case. If the former, all test cases in the suite
-%%% should be returned.
+%%% Called from test_server for every suite (Func==all)
+%%% and every test case. If the former, all test cases in the suite
+%%% should be returned.
get_suite(Mod, all) ->
- case catch apply(Mod, groups, []) of
- {'EXIT',_} ->
- get_all(Mod, []);
- GroupDefs when is_list(GroupDefs) ->
- case catch ct_groups:find_groups(Mod, all, all, GroupDefs) of
- {error,_} = Error ->
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Error properly
- [{?MODULE,error_in_suite,[[Error]]}];
- ConfTests ->
- get_all(Mod, ConfTests)
- end;
- _ ->
+ case safe_apply_groups_0(Mod,{ok,[]}) of
+ {ok,GroupDefs} ->
+ try ct_groups:find_groups(Mod, all, all, GroupDefs) of
+ ConfTests when is_list(ConfTests) ->
+ get_all(Mod, ConfTests)
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ {error,{bad_return,_Bad}} ->
E = "Bad return value from "++atom_to_list(Mod)++":groups/0",
- [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]
+ [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}];
+ {error,{bad_hook_return,Bad}} ->
+ E = "Bad return value from post_groups/2 hook function",
+ [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}];
+ {error,{failed,ExitReason}} ->
+ case ct_util:get_testdata({error_in_suite,Mod}) of
+ undefined ->
+ ErrStr = io_lib:format("~n*** ERROR *** "
+ "~w:groups/0 failed: ~p~n",
+ [Mod,ExitReason]),
+ io:format(?def_gl, ErrStr, []),
+ %% save the error info so it doesn't get printed twice
+ ct_util:set_testdata_async({{error_in_suite,Mod},
+ ExitReason});
+ _ExitReason ->
+ ct_util:delete_testdata({error_in_suite,Mod})
+ end,
+ Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"),
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end;
%%!============================================================
@@ -1079,54 +1110,74 @@ get_suite(Mod, all) ->
%% group
get_suite(Mod, Group={conf,Props,_Init,TCs,_End}) ->
- Name = ?val(name, Props),
- case catch apply(Mod, groups, []) of
- {'EXIT',_} ->
- [Group];
- GroupDefs when is_list(GroupDefs) ->
- case catch ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of
- {error,_} = Error ->
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Error properly
- [{?MODULE,error_in_suite,[[Error]]}];
- [] ->
- [];
- ConfTests ->
- case lists:member(skipped, Props) of
- true ->
- %% a *subgroup* specified *only* as skipped (and not
- %% as an explicit test) should not be returned, or
- %% init/end functions for top groups will be executed
- case catch ?val(name, element(2, hd(ConfTests))) of
- Name -> % top group
- ct_groups:delete_subs(ConfTests, ConfTests);
- _ ->
- []
- end;
- false ->
- ConfTests1 = ct_groups:delete_subs(ConfTests,
- ConfTests),
- case ?val(override, Props) of
- undefined ->
- ConfTests1;
- [] ->
- ConfTests1;
- ORSpec ->
- ORSpec1 = if is_tuple(ORSpec) -> [ORSpec];
- true -> ORSpec end,
- ct_groups:search_and_override(ConfTests1,
- ORSpec1, Mod)
- end
- end
- end;
- _ ->
+ case safe_apply_groups_0(Mod,{ok,[Group]}) of
+ {ok,GroupDefs} ->
+ Name = ?val(name, Props),
+ try ct_groups:find_groups(Mod, Name, TCs, GroupDefs) of
+ [] ->
+ [];
+ ConfTests when is_list(ConfTests) ->
+ case lists:member(skipped, Props) of
+ true ->
+ %% a *subgroup* specified *only* as skipped (and not
+ %% as an explicit test) should not be returned, or
+ %% init/end functions for top groups will be executed
+ try ?val(name, element(2, hd(ConfTests))) of
+ Name -> % top group
+ ct_groups:delete_subs(ConfTests, ConfTests);
+ _ -> []
+ catch
+ _:_ -> []
+ end;
+ false ->
+ ConfTests1 = ct_groups:delete_subs(ConfTests,
+ ConfTests),
+ case ?val(override, Props) of
+ undefined ->
+ ConfTests1;
+ [] ->
+ ConfTests1;
+ ORSpec ->
+ ORSpec1 = if is_tuple(ORSpec) -> [ORSpec];
+ true -> ORSpec end,
+ ct_groups:search_and_override(ConfTests1,
+ ORSpec1, Mod)
+ end
+ end
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ {error,{bad_return,_Bad}} ->
E = "Bad return value from "++atom_to_list(Mod)++":groups/0",
- [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}]
+ [{?MODULE,error_in_suite,[[{error,list_to_atom(E)}]]}];
+ {error,{bad_hook_return,Bad}} ->
+ E = "Bad return value from post_groups/2 hook function",
+ [{?MODULE,error_in_suite,[[{error,{list_to_atom(E),Bad}}]]}];
+ {error,{failed,ExitReason}} ->
+ case ct_util:get_testdata({error_in_suite,Mod}) of
+ undefined ->
+ ErrStr = io_lib:format("~n*** ERROR *** "
+ "~w:groups/0 failed: ~p~n",
+ [Mod,ExitReason]),
+ io:format(?def_gl, ErrStr, []),
+ %% save the error info so it doesn't get printed twice
+ ct_util:set_testdata_async({{error_in_suite,Mod},
+ ExitReason});
+ _ExitReason ->
+ ct_util:delete_testdata({error_in_suite,Mod})
+ end,
+ Reason = list_to_atom(atom_to_list(Mod)++":groups/0 failed"),
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end;
%% testcase
get_suite(Mod, Name) ->
- get_seq(Mod, Name).
+ get_seq(Mod, Name).
%%%-----------------------------------------------------------------
@@ -1160,21 +1211,48 @@ get_all_cases1(_, []) ->
%%%-----------------------------------------------------------------
-get_all(Mod, ConfTests) ->
- case catch apply(Mod, all, []) of
- {'EXIT',{undef,[{Mod,all,[],_} | _]}} ->
+get_all(Mod, ConfTests) ->
+ case safe_apply_all_0(Mod) of
+ {ok,AllTCs} ->
+ %% expand group references using ConfTests
+ try ct_groups:expand_groups(AllTCs, ConfTests, Mod) of
+ {error,_} = Error ->
+ [{?MODULE,error_in_suite,[[Error]]}];
+ Tests0 ->
+ Tests = ct_groups:delete_subs(Tests0, Tests0),
+ expand_tests(Mod, Tests)
+ catch
+ throw:{error,Error} ->
+ [{?MODULE,error_in_suite,[[{error,Error}]]}];
+ _:Error:S ->
+ [{?MODULE,error_in_suite,[[{error,{Error,S}}]]}]
+ end;
+ Skip = {skip,_Reason} ->
+ Skip;
+ {error,undef} ->
+ Reason =
+ case code:which(Mod) of
+ non_existing ->
+ list_to_atom(
+ atom_to_list(Mod)++
+ " cannot be compiled or loaded");
+ _ ->
+ list_to_atom(
+ atom_to_list(Mod)++":all/0 is missing")
+ end,
+ %% this makes test_server call error_in_suite as first
+ %% (and only) test case so we can report Reason properly
+ [{?MODULE,error_in_suite,[[{error,Reason}]]}];
+ {error,{bad_return,_Bad}} ->
Reason =
- case code:which(Mod) of
- non_existing ->
- list_to_atom(atom_to_list(Mod)++
- " can not be compiled or loaded");
- _ ->
- list_to_atom(atom_to_list(Mod)++":all/0 is missing")
- end,
- %% this makes test_server call error_in_suite as first
- %% (and only) test case so we can report Reason properly
+ list_to_atom("Bad return value from "++
+ atom_to_list(Mod)++":all/0"),
[{?MODULE,error_in_suite,[[{error,Reason}]]}];
- {'EXIT',ExitReason} ->
+ {error,{bad_hook_return,Bad}} ->
+ Reason =
+ list_to_atom("Bad return value from post_all/3 hook function"),
+ [{?MODULE,error_in_suite,[[{error,{Reason,Bad}}]]}];
+ {error,{failed,ExitReason}} ->
case ct_util:get_testdata({error_in_suite,Mod}) of
undefined ->
ErrStr = io_lib:format("~n*** ERROR *** "
@@ -1191,28 +1269,8 @@ get_all(Mod, ConfTests) ->
%% this makes test_server call error_in_suite as first
%% (and only) test case so we can report Reason properly
[{?MODULE,error_in_suite,[[{error,Reason}]]}];
- AllTCs when is_list(AllTCs) ->
- case catch save_seqs(Mod,AllTCs) of
- {error,What} ->
- [{?MODULE,error_in_suite,[[{error,What}]]}];
- SeqsAndTCs ->
- %% expand group references in all() using ConfTests
- case catch ct_groups:expand_groups(SeqsAndTCs,
- ConfTests,
- Mod) of
- {error,_} = Error ->
- [{?MODULE,error_in_suite,[[Error]]}];
- Tests ->
- ct_groups:delete_subs(Tests, Tests)
- end
- end;
- Skip = {skip,_Reason} ->
- Skip;
- _ ->
- Reason =
- list_to_atom("Bad return value from "++
- atom_to_list(Mod)++":all/0"),
- [{?MODULE,error_in_suite,[[{error,Reason}]]}]
+ {error,What} ->
+ [{?MODULE,error_in_suite,[[{error,What}]]}]
end.
%%!============================================================
@@ -1354,7 +1412,7 @@ end_per_group(GroupName, _) ->
ok.
%%%-----------------------------------------------------------------
-%%% @spec report(What,Data) -> ok
+%%% -spec report(What,Data) -> ok
report(What,Data) ->
case What of
loginfo ->
@@ -1518,14 +1576,14 @@ add_to_stats(Result) ->
ct_util:update_testdata(stats, Update).
%%%-----------------------------------------------------------------
-%%% @spec warn(What) -> true | false
+%%% -spec warn(What) -> true | false
warn(What) when What==nodes; What==processes ->
false;
warn(_What) ->
true.
%%%-----------------------------------------------------------------
-%%% @spec add_data_dir(File0, Config) -> File1
+%%% -spec add_data_dir(File0, Config) -> File1
add_data_dir(File,Config) when is_atom(File) ->
add_data_dir(atom_to_list(File),Config);
@@ -1544,7 +1602,7 @@ add_data_dir(File,Config) when is_list(File) ->
end.
%%%-----------------------------------------------------------------
-%%% @spec get_logopts() -> [LogOpt]
+%%% -spec get_logopts() -> [LogOpt]
get_logopts() ->
case ct_util:get_testdata(logopts) of
undefined ->
@@ -1554,12 +1612,12 @@ get_logopts() ->
end.
%%%-----------------------------------------------------------------
-%%% @spec format_comment(Comment) -> HtmlComment
+%%% -spec format_comment(Comment) -> HtmlComment
format_comment(Comment) ->
"<font color=\"green\">" ++ Comment ++ "</font>".
%%%-----------------------------------------------------------------
-%%% @spec get_html_wrapper(TestName, PrintLabel, Cwd) -> Header
+%%% -spec get_html_wrapper(TestName, PrintLabel, Cwd) -> Header
get_html_wrapper(TestName, PrintLabel, Cwd, TableCols) ->
get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, utf8).
@@ -1567,6 +1625,77 @@ get_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding) ->
ct_logs:get_ts_html_wrapper(TestName, PrintLabel, Cwd, TableCols, Encoding).
%%%-----------------------------------------------------------------
-%%% @spec get_log_dir() -> {ok,LogDir}
+%%% -spec get_log_dir() -> {ok,LogDir}
get_log_dir() ->
ct_logs:get_log_dir(true).
+
+%%%-----------------------------------------------------------------
+%%% Call all and group callbacks and post_* hooks with error handling
+safe_apply_all_0(Mod) ->
+ try apply(Mod, all, []) of
+ AllTCs0 when is_list(AllTCs0) ->
+ try save_seqs(Mod,AllTCs0) of
+ SeqsAndTCs when is_list(SeqsAndTCs) ->
+ all_hook(Mod,SeqsAndTCs)
+ catch throw:{error,What} ->
+ {error,What}
+ end;
+ {skip,_}=Skip ->
+ all_hook(Mod,Skip);
+ Bad ->
+ {error,{bad_return,Bad}}
+ catch
+ _:Reason:Stacktrace ->
+ handle_callback_crash(Reason,Stacktrace,Mod,all,{error,undef})
+ end.
+
+all_hook(Mod, All) ->
+ case ct_hooks:all(Mod, All) of
+ AllTCs when is_list(AllTCs) ->
+ {ok,AllTCs};
+ {skip,_}=Skip ->
+ Skip;
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end.
+
+safe_apply_groups_0(Mod,Default) ->
+ try apply(Mod, groups, []) of
+ GroupDefs when is_list(GroupDefs) ->
+ case ct_hooks:groups(Mod, GroupDefs) of
+ GroupDefs1 when is_list(GroupDefs1) ->
+ {ok,GroupDefs1};
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end;
+ Bad ->
+ {error,{bad_return,Bad}}
+ catch
+ _:Reason:Stacktrace ->
+ handle_callback_crash(Reason,Stacktrace,Mod,groups,Default)
+ end.
+
+handle_callback_crash(undef,[{Mod,Func,[],_}|_],Mod,Func,Default) ->
+ case ct_hooks:Func(Mod, []) of
+ [] ->
+ Default;
+ List when is_list(List) ->
+ {ok,List};
+ {fail,Reason} ->
+ {error,Reason};
+ Bad ->
+ {error,{bad_hook_return,Bad}}
+ end;
+handle_callback_crash(Reason,Stacktrace,_Mod,_Func,_Default) ->
+ {error,{failed,{Reason,Stacktrace}}}.
+
+expand_tests(Mod, [{testcase,Case,[Prop]}|Tests]) ->
+ [{repeat,{Mod,Case},Prop}|expand_tests(Mod,Tests)];
+expand_tests(Mod,[Test|Tests]) ->
+ [Test|expand_tests(Mod,Tests)];
+expand_tests(_Mod,[]) ->
+ [].