From c9c5497f7e7665b49062c33253c6d693e2a5e654 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Fri, 19 Feb 2010 14:59:39 +0100 Subject: Add support for user config in common_test Added: 1. ct_config, ct_config_plain and ct_config_xml modules. 2. support for {userconfig, {Callback, ConfigFiles}} parameter to ct:run_test/1 3. support for "-userconfig Callback ConfigFiles and OtherCallback ConfigFiles" parameter to the run_test script --- lib/common_test/src/Makefile | 7 +- lib/common_test/src/common_test.app.src | 7 +- lib/common_test/src/ct_config.erl | 117 ++++++++++++++++++++++++ lib/common_test/src/ct_config_plain.erl | 100 ++++++++++++++++++++ lib/common_test/src/ct_config_xml.erl | 96 ++++++++++++++++++++ lib/common_test/src/ct_run.erl | 156 ++++++++++++++++++++++---------- lib/common_test/src/ct_util.erl | 112 +---------------------- 7 files changed, 434 insertions(+), 161 deletions(-) create mode 100755 lib/common_test/src/ct_config.erl create mode 100755 lib/common_test/src/ct_config_plain.erl create mode 100755 lib/common_test/src/ct_config_xml.erl diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile index e7e2d1275d..4c4382b705 100644 --- a/lib/common_test/src/Makefile +++ b/lib/common_test/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2003-2009. All Rights Reserved. +# Copyright Ericsson AB 2003-2010. All Rights Reserved. # # The contents of this file are subject to the Erlang Public License, # Version 1.1, (the "License"); you may not use this file except in @@ -63,7 +63,10 @@ MODULES= \ ct_telnet_client \ ct_make \ vts \ - unix_telnet + unix_telnet \ + ct_config \ + ct_config_plain \ + ct_config_xml TARGET_MODULES= $(MODULES:%=$(EBIN)/%) diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src index 7b72932ad4..2f599a7e30 100644 --- a/lib/common_test/src/common_test.app.src +++ b/lib/common_test/src/common_test.app.src @@ -1,7 +1,7 @@ % This is an -*- erlang -*- file. %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009. All Rights Reserved. +%% Copyright Ericsson AB 2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -42,7 +42,10 @@ ct_testspec, ct_util, unix_telnet, - vts + vts, + ct_config, + ct_config_plain, + ct_config_xml ]}, {registered, [ct_logs, ct_util_server, diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl new file mode 100755 index 0000000000..f344813128 --- /dev/null +++ b/lib/common_test/src/ct_config.erl @@ -0,0 +1,117 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%%---------------------------------------------------------------------- +%% File : ct_config.erl +%% Description : CT module for reading and manipulating of configuration +%% data +%% +%% Created : 15 February 2010 +%%---------------------------------------------------------------------- +-module(ct_config). + +-export([read_config_files/1, + set_config/1, set_config/2, set_config/3, + delete_config/1, + get_config_file_list/1]). + +-include("ct_event.hrl"). +-include("ct_util.hrl"). + +-record(ct_conf,{key,value,ref,name='_UNDEF',default=false}). +%% default = {true,suite} | {true,testcase} | false + +read_config_files(Opts) -> + AddCallback = fun(CallBack, Files)-> + lists:map(fun(X)-> {CallBack, X} end, Files) + end, + ConfigFiles = case lists:keyfind(config, 1, Opts) of + {config, ConfigLists}-> + lists:foldr(fun({Callback,Files}, Acc)-> + AddCallback(Callback,Files) ++ Acc + end, + [], + ConfigLists); + false-> + [] + end, + read_config_files_int(ConfigFiles). + +read_config_files_int([{Callback, File}|Files])-> + case Callback:read_config_file(File) of + {ok, Config}-> + set_config(Config), + read_config_files_int(Files); + {error, ErrorName, ErrorDetail}-> + {user_error, {ErrorName, File, ErrorDetail}} + end; +read_config_files_int([])-> + ok. + +set_config(Config) -> + set_config('_UNDEF',Config,false). + +set_config(Config,Default) -> + set_config('_UNDEF',Config,Default). + +set_config(Name,Config,Default) -> + [ets:insert(?attr_table, + #ct_conf{key=Key,value=Val,ref=ct_util:ct_make_ref(), + name=Name,default=Default}) || + {Key,Val} <- Config]. + +delete_config(Default) -> + ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), + ok. + +process_user_configs(Opts, Acc)-> + case lists:keytake(userconfig, 1, Opts) of + false-> + Acc; + {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when + is_list(File)-> + process_user_configs(NewOpts, [{Callback, Files} | Acc]); + {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when + is_integer(C)-> + process_user_configs(NewOpts, [{Callback, [File]} | Acc]); + {value, {userconfig, {_Callback, []}}, NewOpts}-> + process_user_configs(NewOpts, Acc) + end. + +process_default_configs(Opts)-> + case lists:keysearch(config, 1, Opts) of + {value,{_,Files=[File|_]}} when is_list(File) -> + Files; + {value,{_,File=[C|_]}} when is_integer(C) -> + [File]; + {value,{_,[]}} -> + []; + false -> + [] + end. + +get_config_file_list(Opts)-> + DefaultConfigs = process_default_configs(Opts), + CfgFiles = + if + DefaultConfigs == []-> + []; + true-> + [{ct_config_plain, DefaultConfigs}] + end ++ + process_user_configs(Opts, []), + CfgFiles. diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl new file mode 100755 index 0000000000..a5947cafef --- /dev/null +++ b/lib/common_test/src/ct_config_plain.erl @@ -0,0 +1,100 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%%---------------------------------------------------------------------- +%% File : ct_config_plain.erl +%% Description : CT callback module for reading configs from text files +%% +%% Created : 15 February 2010 +%%---------------------------------------------------------------------- +-module(ct_config_plain). +-export([read_config_file/1]). + +read_config_file(ConfigFile) -> + case file:consult(ConfigFile) of + {ok,Config} -> + {ok, Config}; + {error,enoent} -> + {error, config_file_error, enoent}; + {error,Reason} -> + Key = + case application:get_env(common_test, decrypt) of + {ok,KeyOrFile} -> + case KeyOrFile of + {key,K} -> + K; + {file,F} -> + ct_util:get_crypt_key_from_file(F) + end; + _ -> + ct_util:get_crypt_key_from_file() + end, + case Key of + {error,no_crypt_file} -> + {error, config_file_error, Reason}; + {error,CryptError} -> + {error, decrypt_file_error, CryptError}; + _ when is_list(Key) -> + case ct_util:decrypt_config_file(ConfigFile, undefined, {key,Key}) of + {ok,CfgBin} -> + case read_config_terms(CfgBin) of + {error,ReadFail} -> + {error, config_file_error, ReadFail}; + Config -> + {ok, Config} + end; + {error,DecryptFail} -> + {error, decrypt_config_error, DecryptFail} + end; + _ -> + {error, bad_decrypt_key, Key} + end + end. + +read_config_terms(Bin) when is_binary(Bin) -> + case catch binary_to_list(Bin) of + {'EXIT',_} -> + {error,invalid_textfile}; + Lines -> + read_config_terms(Lines) + end; +read_config_terms(Lines) when is_list(Lines) -> + read_config_terms1(erl_scan:tokens([], Lines, 0), 1, [], []). + +read_config_terms1({done,{ok,Ts,EL},Rest}, L, Terms, _) -> + case erl_parse:parse_term(Ts) of + {ok,Term} when Rest == [] -> + lists:reverse([Term|Terms]); + {ok,Term} -> + read_config_terms1(erl_scan:tokens([], Rest, 0), + EL+1, [Term|Terms], Rest); + _ -> + {error,{bad_term,{L,EL}}} + end; +read_config_terms1({done,{eof,_},_}, _, Terms, Rest) when Rest == [] -> + lists:reverse(Terms); +read_config_terms1({done,{eof,EL},_}, L, _, _) -> + {error,{bad_term,{L,EL}}}; +read_config_terms1({done,{error,Info,EL},_}, L, _, _) -> + {error,{Info,{L,EL}}}; +read_config_terms1({more,_}, L, Terms, Rest) -> + case string:tokens(Rest, [$\n,$\r,$\t]) of + [] -> + lists:reverse(Terms); + _ -> + {error,{bad_term,L}} + end. diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl new file mode 100755 index 0000000000..ef991123f3 --- /dev/null +++ b/lib/common_test/src/ct_config_xml.erl @@ -0,0 +1,96 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%%---------------------------------------------------------------------- +%% File : ct_config_xml.erl +%% Description : CT callback module for reading configs from XML files +%% +%% Created : 16 February 2010 +%%---------------------------------------------------------------------- +-module(ct_config_xml). +-export([read_config_file/1, list_to_term/1]). + +read_config_file(ConfigFile) -> + case catch do_read_xml_config(ConfigFile) of + {ok, Config}-> + {ok, Config}; + {error, Error, ErroneousString}-> + {error, Error, ErroneousString} + end. + +do_read_xml_config(ConfigFile)-> + {ok, EntityList, _}= + xmerl_sax_parser:file(ConfigFile, + [{event_fun, fun event/3}, + {event_state, initial_state()}]), + {ok, transform_entity_list(EntityList)}. + + +initial_state() -> + []. + +event(Event, _LineNo, State) -> + tag(Event, State). + +tag(startDocument, State) -> + State; + +tag(endDocument, {_Tags, Result}) -> + Result; + +tag({startElement, _Uri, "config", _QName, _Attributes}, []) -> + [{"config", []}]; + +tag({endElement, _Uri, "config", _QName}, [{"config", Config}]) -> + Config; + +tag({startElement, _Uri, Name, _QName, _Attributes}, Tags) -> + [{Name, []}|Tags]; + +tag({endElement, _Uri, _Name, _QName}, + [Entity, {PrevEntityTag, PrevEntityValue}|Tags]) -> + NewHead = {PrevEntityTag, [Entity|PrevEntityValue]}, + [NewHead|Tags]; + +tag({characters, String}, [{Tag, _Value}|Tags]) -> + [{Tag, String}|Tags]; + +tag(_El, State) -> + State. + +transform_entity_list(EntityList)-> + lists:map(fun transform_entity/1, EntityList). + +transform_entity({Tag, [Value|Rest]}) when + is_tuple(Value)-> + {list_to_atom(Tag), transform_entity_list([Value|Rest])}; +transform_entity({Tag, String})-> + case list_to_term(String) of + {ok, Value}-> + {list_to_atom(Tag), Value}; + Error-> + throw(Error) + end. + +list_to_term(String) -> + {ok, T, _} = erl_scan:string(String++"."), + case erl_parse:parse_term(T) of + {ok, Term} -> + {ok, Term}; + Error -> + {error, Error, String} + end. diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 6b1063f74c..b86dd8fe2f 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -208,47 +208,95 @@ script_start1(Parent, Args) -> end end; false -> - case lists:keysearch(ct_config, 1, Args) of - {value,{ct_config,ConfigFiles}} -> + ConfigFiles = case lists:keysearch(ct_config, 1, Args) of + {value,{ct_config,Files}}-> + [{ct_config_plain, Files}]; + false-> + [] + end, + UserConfigs = case lists:keysearch(userconfig, 1, Args) of + {value,{userconfig,UserConfigFiles}}-> + prepare_user_configs(UserConfigFiles, [], new); + false-> + [] + end, + Config = ConfigFiles ++ UserConfigs, + case Config of + false -> + case install([{config,[]}, + {event_handler,EvHandlers}], + LogDir) of + ok -> + script_start2(VtsOrShell, [], EvHandlers, + Args, LogDir, Cover); + Error -> + Error + end; + Config -> case lists:keysearch(spec, 1, Args) of false -> - case get_configfiles(ConfigFiles, [], LogDir, - EvHandlers) of + case check_and_install_configfiles(Config, + LogDir, EvHandlers) of ok -> - script_start2(VtsOrShell, ConfigFiles, + script_start2(VtsOrShell, Config, EvHandlers, Args, LogDir, Cover); Error -> Error end; _ -> - script_start2(VtsOrShell, ConfigFiles, + script_start2(VtsOrShell, Config, EvHandlers, Args, LogDir, Cover) - end; - false -> - case install([{config,[]}, - {event_handler,EvHandlers}], - LogDir) of - ok -> - script_start2(VtsOrShell, [], EvHandlers, - Args, LogDir, Cover); - Error -> - Error end end end, Parent ! {self(), Result}. -get_configfiles([File|Files], Acc, LogDir, EvHandlers) -> - case filelib:is_file(File) of - true -> - get_configfiles(Files, [?abs(File)|Acc], - LogDir, EvHandlers); - false -> - {error,{cant_read_config_file,File}} - end; -get_configfiles([], Acc, LogDir, EvHandlers) -> - install([{config,lists:reverse(Acc)}, {event_handler,EvHandlers}], LogDir). +prepare_user_configs([ConfigString|UserConfigs], Acc, new)-> + prepare_user_configs(UserConfigs, + [{list_to_atom(ConfigString), []}|Acc], + cur); +prepare_user_configs(["and"|UserConfigs], Acc, _)-> + prepare_user_configs(UserConfigs, Acc, new); +prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur)-> + prepare_user_configs(UserConfigs, + [{LastMod, [ConfigString|LastList]}|Acc], + cur); +prepare_user_configs([], Acc, _)-> + Acc. + +check_and_install_configfiles(Configs, LogDir, EvHandlers) -> + % Configs is list of tuples such as {Callback=atom(), Files=list()} + % The ugly code below checks: + % 1. that all config files are present + % 2. thar all callback modules are loadable + case lists:keysearch(nok, 1, + lists:flatten( + lists:map(fun({Callback, Files})-> + case code:load_file(Callback) of + {module, Callback}-> + lists:map(fun(File)-> + case filelib:is_file(File) of + true-> + {ok, File}; + false-> + {nok, {config, File}} + end + end, + Files); + {error, _}-> + {nok, {callback, Callback}} + end + end, + Configs))) of + false-> + install([{config,Configs}, + {event_handler,EvHandlers}], LogDir); + {value, {nok, {config, File}}} -> + {error,{cant_read_config_file,File}}; + {value, {nok, {callback, File}}} -> + {error,{cant_load_callback_module,File}} + end. script_start2(false, ConfigFiles, EvHandlers, Args, LogDir, Cover) -> case lists:keysearch(spec, 1, Args) of @@ -275,8 +323,8 @@ script_start2(false, ConfigFiles, EvHandlers, Args, LogDir, Cover) -> {_,undef} -> [Cover]; {false,_} -> [{cover,TSCoverFile}] end, - case get_configfiles(ConfigFiles++ConfigFiles1, - [], LogDir2, + case check_and_install_configfiles( + ConfigFiles++ConfigFiles1, LogDir2, EvHandlers++EvHandlers1) of ok -> {Run,Skip} = ct_testspec:prepare_tests(TS, node()), @@ -404,6 +452,7 @@ script_usage() -> "\n\t[-suite Suite1 Suite2 .. SuiteN [-case Case1 Case2 .. CaseN]]" "\n\t[-step [config | keep_inactive]]" "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" + "\n\t[-userconfig CallbackModule ConfigFile1 .. ConfigFileN]" "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" "\n\t[-logdir LogDir]" "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" @@ -534,17 +583,7 @@ run_test1(Opts) -> {value,{_,LD}} when is_list(LD) -> LD; false -> "." end, - CfgFiles = - case lists:keysearch(config, 1, Opts) of - {value,{_,Files=[File|_]}} when is_list(File) -> - Files; - {value,{_,File=[C|_]}} when is_integer(C) -> - [File]; - {value,{_,[]}} -> - []; - false -> - [] - end, + CfgFiles = ct_config:get_config_file_list(Opts), EvHandlers = case lists:keysearch(event_handler, 1, Opts) of {value,{_,H}} when is_atom(H) -> @@ -681,7 +720,7 @@ run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover, Opts {_,undef} -> Cover; {[],_} -> [{cover,TSCoverFile}] end, - case get_configfiles(CfgFiles++CfgFiles1, [], LogDir2, + case check_and_install_configfiles(CfgFiles++CfgFiles1, LogDir2, EvHandlers++EvHandlers1) of ok -> {Run,Skip} = ct_testspec:prepare_tests(TS, node()), @@ -694,23 +733,40 @@ run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover, Opts end. run_prepared(LogDir, CfgFiles, EvHandlers, Run, Skip, Cover, Opts) -> - case get_configfiles(CfgFiles, [], LogDir, EvHandlers) of + case check_and_install_configfiles(CfgFiles, LogDir, EvHandlers) of ok -> do_run(Run, Skip, Cover, Opts, LogDir); {error,Reason} -> exit(Reason) end. +check_config_file(File)-> + AbsName = ?abs(File), + case filelib:is_file(AbsName) of + true -> AbsName; + false -> exit({no_such_file,AbsName}) + end. + run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> AbsCfgFiles = - lists:map(fun(F) -> - AbsName = ?abs(F), - case filelib:is_file(AbsName) of - true -> AbsName; - false -> exit({no_such_file,AbsName}) - end - end, CfgFiles), - + lists:map(fun({Callback, FileList})-> + case code:is_loaded(Callback) of + {file, _Path}-> + ok; + false-> + case code:load_file(Callback) of + {module, Callback}-> + ok; + {error, _}-> + exit({no_such_module, Callback}) + end + end, + {Callback, + lists:map(fun(File)-> + check_config_file(File) + end, FileList)} + end, + CfgFiles), case install([{config,AbsCfgFiles},{event_handler,EvHandlers}], LogDir) of ok -> ok; {error,IReason} -> exit(IReason) @@ -799,7 +855,7 @@ run_testspec1(TestSpec) -> CoverOpt = if TSCoverFile == undef -> []; true -> [{cover,TSCoverFile}] end, - case get_configfiles(CfgFiles,[],LogDir,EvHandlers) of + case check_and_install_configfiles(CfgFiles,LogDir,EvHandlers) of ok -> {Run,Skip} = ct_testspec:prepare_tests(TS,node()), do_run(Run,Skip,CoverOpt,[],LogDir); diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index ba3d789f8d..814bd97840 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -59,7 +59,7 @@ -export([encrypt_config_file/2, encrypt_config_file/3, decrypt_config_file/2, decrypt_config_file/3]). --export([kill_attached/2, get_attached/1]). +-export([kill_attached/2, get_attached/1, ct_make_ref/0]). -export([warn_duplicates/1]). @@ -148,7 +148,7 @@ do_start(Parent,Mode,LogDir) -> ct_event:add_handler([{vts,VtsPid}]) end end, - case read_config_files(Opts) of + case ct_config:read_config_files(Opts) of ok -> %% add user handlers case lists:keysearch(event_handler,1,Opts) of @@ -197,94 +197,6 @@ read_opts() -> {error,{bad_installation,Error}} end. -read_config_files(Opts) -> - ConfigFiles = - lists:foldl(fun({config,Files},Acc) -> - Acc ++ Files; - (_,Acc) -> - Acc - end,[],Opts), - read_config_files1(ConfigFiles). - -read_config_files1([ConfigFile|Files]) -> - case file:consult(ConfigFile) of - {ok,Config} -> - set_config(Config), - read_config_files1(Files); - {error,enoent} -> - {user_error,{config_file_error,ConfigFile,enoent}}; - {error,Reason} -> - Key = - case application:get_env(common_test, decrypt) of - {ok,KeyOrFile} -> - case KeyOrFile of - {key,K} -> - K; - {file,F} -> - get_crypt_key_from_file(F) - end; - _ -> - get_crypt_key_from_file() - end, - case Key of - {error,no_crypt_file} -> - {user_error,{config_file_error,ConfigFile,Reason}}; - {error,CryptError} -> - {user_error,{decrypt_file_error,ConfigFile,CryptError}}; - _ when is_list(Key) -> - case decrypt_config_file(ConfigFile, undefined, {key,Key}) of - {ok,CfgBin} -> - case read_config_terms(CfgBin) of - {error,ReadFail} -> - {user_error,{config_file_error,ConfigFile,ReadFail}}; - Config -> - set_config(Config), - read_config_files1(Files) - end; - {error,DecryptFail} -> - {user_error,{decrypt_config_error,ConfigFile,DecryptFail}} - end; - _ -> - {user_error,{bad_decrypt_key,ConfigFile,Key}} - end - end; -read_config_files1([]) -> - ok. - -read_config_terms(Bin) when is_binary(Bin) -> - case catch binary_to_list(Bin) of - {'EXIT',_} -> - {error,invalid_textfile}; - Lines -> - read_config_terms(Lines) - end; -read_config_terms(Lines) when is_list(Lines) -> - read_config_terms1(erl_scan:tokens([], Lines, 0), 1, [], []). - -read_config_terms1({done,{ok,Ts,EL},Rest}, L, Terms, _) -> - case erl_parse:parse_term(Ts) of - {ok,Term} when Rest == [] -> - lists:reverse([Term|Terms]); - {ok,Term} -> - read_config_terms1(erl_scan:tokens([], Rest, 0), - EL+1, [Term|Terms], Rest); - _ -> - {error,{bad_term,{L,EL}}} - end; -read_config_terms1({done,{eof,_},_}, _, Terms, Rest) when Rest == [] -> - lists:reverse(Terms); -read_config_terms1({done,{eof,EL},_}, L, _, _) -> - {error,{bad_term,{L,EL}}}; -read_config_terms1({done,{error,Info,EL},_}, L, _, _) -> - {error,{Info,{L,EL}}}; -read_config_terms1({more,_}, L, Terms, Rest) -> - case string:tokens(Rest, [$\n,$\r,$\t]) of - [] -> - lists:reverse(Terms); - _ -> - {error,{bad_term,L}} - end. - set_default_config(NewConfig, Scope) -> call({set_default_config, {NewConfig, Scope}}). @@ -347,15 +259,15 @@ loop(Mode,TestData,StartDir) -> return(From,Result), loop(Mode,TestData,StartDir); {{set_default_config,{Config,Scope}},From} -> - set_config(Config,{true,Scope}), + ct_config:set_config(Config,{true,Scope}), return(From,ok), loop(Mode,TestData,StartDir); {{set_default_config,{Name,Config,Scope}},From} -> - set_config(Name,Config,{true,Scope}), + ct_config:set_config(Name,Config,{true,Scope}), return(From,ok), loop(Mode,TestData,StartDir); {{delete_default_config,Scope},From} -> - delete_config({true,Scope}), + ct_config:delete_config({true,Scope}), return(From,ok), loop(Mode,TestData,StartDir); {{update_config,{Name,NewConfig}},From} -> @@ -765,21 +677,7 @@ lookup_key(Key) -> [], [{{'$1','$2'}}]}]). -set_config(Config) -> - set_config('_UNDEF',Config,false). - -set_config(Config,Default) -> - set_config('_UNDEF',Config,Default). -set_config(Name,Config,Default) -> - [ets:insert(?attr_table, - #ct_conf{key=Key,value=Val,ref=ct_make_ref(), - name=Name,default=Default}) || - {Key,Val} <- Config]. - -delete_config(Default) -> - ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), - ok. %%%----------------------------------------------------------------- -- cgit v1.2.3 From fb0f87106861fcd45688a78dc587352d15f91a4a Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Tue, 11 May 2010 16:37:28 +0200 Subject: Make ct_xml_config smarter --- lib/common_test/src/ct_config_xml.erl | 39 ++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index ef991123f3..79f25cead7 100755 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -22,8 +22,9 @@ %% Created : 16 February 2010 %%---------------------------------------------------------------------- -module(ct_config_xml). --export([read_config_file/1, list_to_term/1]). +-export([read_config_file/1]). +% the only function to be called outside read_config_file(ConfigFile) -> case catch do_read_xml_config(ConfigFile) of {ok, Config}-> @@ -32,49 +33,57 @@ read_config_file(ConfigFile) -> {error, Error, ErroneousString} end. +% actual reading of the config do_read_xml_config(ConfigFile)-> {ok, EntityList, _}= xmerl_sax_parser:file(ConfigFile, [{event_fun, fun event/3}, - {event_state, initial_state()}]), + {event_state, []}]), {ok, transform_entity_list(EntityList)}. - -initial_state() -> - []. - +% event callback for xmerl_sax_parser event(Event, _LineNo, State) -> tag(Event, State). +% document start tag(startDocument, State) -> State; -tag(endDocument, {_Tags, Result}) -> - Result; - +% start of the config tag({startElement, _Uri, "config", _QName, _Attributes}, []) -> [{"config", []}]; -tag({endElement, _Uri, "config", _QName}, [{"config", Config}]) -> - Config; - +% start tag tag({startElement, _Uri, Name, _QName, _Attributes}, Tags) -> [{Name, []}|Tags]; +% value +tag({characters, String}, [{Tag, _Value}|Tags]) -> + [{Tag, String}|Tags]; + +% end tag tag({endElement, _Uri, _Name, _QName}, [Entity, {PrevEntityTag, PrevEntityValue}|Tags]) -> NewHead = {PrevEntityTag, [Entity|PrevEntityValue]}, [NewHead|Tags]; -tag({characters, String}, [{Tag, _Value}|Tags]) -> - [{Tag, String}|Tags]; +% end of the config +tag({endElement, _Uri, "config", _QName}, [{"config", Config}]) -> + Config; + +% end of document, return result +tag(endDocument, {_Tags, Result}) -> + Result; +% default tag(_El, State) -> State. +% transform of the ugly deeply nested entity list to the key-value "tree" transform_entity_list(EntityList)-> lists:map(fun transform_entity/1, EntityList). +% transform entity from {list(), list()} to {atom(), term()} transform_entity({Tag, [Value|Rest]}) when is_tuple(Value)-> {list_to_atom(Tag), transform_entity_list([Value|Rest])}; @@ -86,6 +95,8 @@ transform_entity({Tag, String})-> throw(Error) end. +% transform a string with Erlang terms to the terms +% stolen from trapexit.org :-) list_to_term(String) -> {ok, T, _} = erl_scan:string(String++"."), case erl_parse:parse_term(T) of -- cgit v1.2.3 From 8c79c17611c37217345e969aa2c344fe34e0375e Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Tue, 23 Feb 2010 15:31:12 +0100 Subject: Remove configuration handling from ct_util completely --- lib/common_test/src/ct.erl | 21 +- lib/common_test/src/ct_config.erl | 579 +++++++++++++++++++++++++++++++- lib/common_test/src/ct_config_plain.erl | 6 +- lib/common_test/src/ct_framework.erl | 10 +- lib/common_test/src/ct_run.erl | 2 +- lib/common_test/src/ct_snmp.erl | 12 +- lib/common_test/src/ct_util.erl | 534 ++--------------------------- 7 files changed, 638 insertions(+), 526 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 8ae041e5b4..3cc6154592 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -135,7 +135,8 @@ run(TestDirs) -> %%% @spec run_test(Opts) -> Result %%% Opts = [OptTuples] %%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} | -%%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} | +%%% {userconfig, Callback, CfgFiles} | +%%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} | %%% {allow_user_terms,Bool} | {logdir,LogDir} | %%% {silent_connections,Conns} | {cover,CoverSpecFile} | %%% {step,StepOpts} | {event_handler,EventHandlers} | {include,InclDirs} | @@ -269,7 +270,7 @@ stop_interactive() -> %%% @see get_config/2 %%% @see get_config/3 require(Required) -> - ct_util:require(Required). + ct_config:require(Required). %%%----------------------------------------------------------------- %%% @spec require(Name,Required) -> ok | {error,Reason} @@ -304,19 +305,19 @@ require(Required) -> %%% @see get_config/2 %%% @see get_config/3 require(Name,Required) -> - ct_util:require(Name,Required). + ct_config:require(Name,Required). %%%----------------------------------------------------------------- %%% @spec get_config(Required) -> Value %%% @equiv get_config(Required,undefined,[]) get_config(Required) -> - ct_util:get_config(Required,undefined,[]). + ct_config:get_config(Required,undefined,[]). %%%----------------------------------------------------------------- %%% @spec get_config(Required,Default) -> Value %%% @equiv get_config(Required,Default,[]) get_config(Required,Default) -> - ct_util:get_config(Required,Default,[]). + ct_config:get_config(Required,Default,[]). %%%----------------------------------------------------------------- %%% @spec get_config(Required,Default,Opts) -> ValueOrElement @@ -375,7 +376,7 @@ get_config(Required,Default) -> %%% @see require/1 %%% @see require/2 get_config(Required,Default,Opts) -> - ct_util:get_config(Required,Default,Opts). + ct_config:get_config(Required,Default,Opts). %%%----------------------------------------------------------------- %%% @spec log(Format) -> ok @@ -734,7 +735,7 @@ abort_current_testcase(Reason) -> %%%

See the crypto application for details on DES3 %%% encryption/decryption.

encrypt_config_file(SrcFileName, EncryptFileName) -> - ct_util:encrypt_config_file(SrcFileName, EncryptFileName). + ct_config:encrypt_config_file(SrcFileName, EncryptFileName). %%%----------------------------------------------------------------- %%% @spec encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> @@ -754,7 +755,7 @@ encrypt_config_file(SrcFileName, EncryptFileName) -> %%%

See the crypto application for details on DES3 %%% encryption/decryption.

encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> - ct_util:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile). + ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile). %%%----------------------------------------------------------------- %%% @spec decrypt_config_file(EncryptFileName, TargetFileName) -> @@ -770,7 +771,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> %%% .ct_config.crypt in the current directory, or the %%% home directory of the user (it is searched for in that order).

decrypt_config_file(EncryptFileName, TargetFileName) -> - ct_util:decrypt_config_file(EncryptFileName, TargetFileName). + ct_config:decrypt_config_file(EncryptFileName, TargetFileName). %%%----------------------------------------------------------------- %%% @spec decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> @@ -785,5 +786,5 @@ decrypt_config_file(EncryptFileName, TargetFileName) -> %%% file contents is saved in the target file. The key must have the %%% the same value as that used for encryption.

decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> - ct_util:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile). + ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile). diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index f344813128..9a1f23201f 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -24,16 +24,169 @@ %%---------------------------------------------------------------------- -module(ct_config). +% start of the config server +-export([start/0, start/1, start/2, stop/0]). + +% manipulating with config files -export([read_config_files/1, - set_config/1, set_config/2, set_config/3, - delete_config/1, get_config_file_list/1]). --include("ct_event.hrl"). +% require +-export([require/1, require/2]). + +% get config data +-export([get_config/1, get_config/2, get_config/3, + get_all_config/0]). + +% set config data +-export([set_config/1, set_config/2, set_config/3, + set_default_config/2, set_default_config/3]). + +% delete config data +-export([delete_config/1, delete_default_config/1]). + +% update and reload config +-export([%reload_config/1, + update_config/2]). + +% ? +-export([release_allocated/0]). + +-export([encrypt_config_file/2, encrypt_config_file/3, + decrypt_config_file/2, decrypt_config_file/3, + get_crypt_key_from_file/0, get_crypt_key_from_file/1]). + +% references +-export([get_ref_from_name/1, get_name_from_ref/1, get_key_from_name/1]). + -include("ct_util.hrl"). +-include("ct_event.hrl"). + +-define(cryptfile, ".ct_config.crypt"). +% TODO: add handler field here -record(ct_conf,{key,value,ref,name='_UNDEF',default=false}). -%% default = {true,suite} | {true,testcase} | false + +%%%----------------------------------------------------------------- +%%% @spec start(Mode) -> Pid | exit(Error) +%%% Mode = normal | interactive +%%% Pid = pid() +%%% +%%% @doc Start start the ct_config_server process +%%% (tool-internal use only). +%%% +%%%

This function is called from ct_run.erl. It starts and initiates +%%% the ct_config_server

+%%% +%%%

Returns the process identity of the +%%% ct_config_server.

+%%% +%%% @see ct +start() -> + start(normal,"."). + +start(LogDir) when is_list(LogDir) -> + start(normal,LogDir); +start(Mode) -> + start(Mode,"."). + +start(Mode,LogDir) -> + case whereis(ct_config_server) of + undefined -> + Me = self(), + Pid = spawn_link(fun() -> do_start(Me) end), + receive + {Pid,started} -> Pid; + {Pid,Error} -> exit(Error) + end; + Pid -> + case ct_util:get_mode() of + interactive when Mode==interactive -> + Pid; + interactive -> + {error,interactive_mode}; + _OtherMode -> + Pid + end + end. + +do_start(Parent) -> + process_flag(trap_exit,true), + register(ct_config_server,self()), + ct_util:create_table(?attr_table,bag,#ct_conf.key), + {ok,StartDir} = file:get_cwd(), + Opts = case ct_util:read_opts() of + {ok,Opts1} -> + Opts1; + Error -> + Parent ! {self(),Error}, + exit(Error) + end, + case read_config_files(Opts) of + ok -> + Parent ! {self(),started}, + loop(StartDir); + ReadError -> + Parent ! {self(),ReadError}, + exit(ReadError) + end. + +%%%----------------------------------------------------------------- +%%% @spec stop() -> ok +%%% +%%% @doc Stop the ct_config_server and close all existing connections +%%% (tool-internal use only). +%%% +%%% @see ct +stop() -> + case whereis(ct_config_server) of + undefined -> ok; + _ -> call({stop}) + end. + +call(Msg) -> + MRef = erlang:monitor(process, whereis(ct_config_server)), + Ref = make_ref(), + ct_config_server ! {Msg,{self(),Ref}}, + receive + {Ref, Result} -> + erlang:demonitor(MRef), + Result; + {'DOWN',MRef,process,_,Reason} -> + {error,{ct_util_server_down,Reason}} + end. + +return({To,Ref},Result) -> + To ! {Ref, Result}. + + +loop(StartDir) -> + receive + {{require,Name,Tag,SubTags},From} -> + Result = do_require(Name,Tag,SubTags), + return(From,Result), + loop(StartDir); + {{set_default_config,{Config,Scope}},From} -> + ct_config:set_config(Config,{true,Scope}), + return(From,ok), + loop(StartDir); + {{set_default_config,{Name,Config,Scope}},From} -> + ct_config:set_config(Name,Config,{true,Scope}), + return(From,ok), + loop(StartDir); + {{delete_default_config,Scope},From} -> + ct_config:delete_config({true,Scope}), + return(From,ok), + loop(StartDir); + {{update_config,{Name,NewConfig}},From} -> + update_conf(Name,NewConfig), + return(From,ok), + loop(StartDir); + {{stop},From} -> + ets:delete(?attr_table), + file:set_cwd(StartDir), + return(From,ok) + end. read_config_files(Opts) -> AddCallback = fun(CallBack, Files)-> @@ -74,6 +227,180 @@ set_config(Name,Config,Default) -> name=Name,default=Default}) || {Key,Val} <- Config]. +get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) -> + case lookup_config(KeyOrName) of + [] -> + Default; + [{_Ref,Val}|_] = Vals -> + case {lists:member(all,Opts),lists:member(element,Opts)} of + {true,true} -> + [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)]; + {true,false} -> + [V || {_R,V} <- lists:sort(Vals)]; + {false,true} -> + {KeyOrName,Val}; + {false,false} -> + Val + end + end; + +get_config({KeyOrName,SubKey},Default,Opts) -> + case lookup_config(KeyOrName) of + [] -> + Default; + Vals -> + Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of + Result=[L|_] when is_list(L) -> + case L of + [{_,_}|_] -> + Result; + _ -> + [] + end; + _ -> + [] + end, + case get_subconfig([SubKey],Vals1,[],Opts) of + {ok,[{_,SubVal}|_]=SubVals} -> + case {lists:member(all,Opts),lists:member(element,Opts)} of + {true,true} -> + [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals]; + {true,false} -> + [Val || {_SubKey,Val} <- SubVals]; + {false,true} -> + {{KeyOrName,SubKey},SubVal}; + {false,false} -> + SubVal + end; + _ -> + Default + end + end. + + +%%%----------------------------------------------------------------- +%%% @spec +%%% +%%% @doc +update_conf(Name, NewConfig) -> + Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]), + lists:foreach(fun(OldElem) -> + NewElem = OldElem#ct_conf{value=NewConfig}, + ets:delete_object(?attr_table, OldElem), + ets:insert(?attr_table, NewElem) + end, Old), + ok. + +%%%----------------------------------------------------------------- +%%% @spec release_allocated() -> ok +%%% +%%% @doc Release all allocated resources, but don't take down any +%%% connections. +release_allocated() -> + Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'}, + [{'=/=','$1','_UNDEF'}], + ['$_']}]), + release_allocated(Allocated). +release_allocated([H|T]) -> + ets:delete_object(?attr_table,H), + ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}), + release_allocated(T); +release_allocated([]) -> + ok. + +allocate(Name,Key,SubKeys) -> + case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of + [] -> + {error,{not_available,Key}}; + Available -> + case allocate_subconfig(Name,SubKeys,Available,false) of + ok -> + ok; + Error -> + Error + end + end. + +allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) -> + case do_get_config(SubKeys,Value,[]) of + {ok,_SubMapped} -> + ets:insert(?attr_table,C#ct_conf{name=Name}), + allocate_subconfig(Name,SubKeys,Rest,true); + _Error -> + allocate_subconfig(Name,SubKeys,Rest,Found) + end; +allocate_subconfig(_Name,_SubKeys,[],true) -> + ok; +allocate_subconfig(_Name,SubKeys,[],false) -> + {error,{not_available,SubKeys}}. + +%%%----------------------------------------------------------------- +%%% @hidden +%%% @equiv ct:get_config/1 +get_config(KeyOrName) -> + ct_config:get_config(KeyOrName,undefined,[]). + +%%%----------------------------------------------------------------- +%%% @hidden +%%% @equiv ct:get_config/2 +get_config(KeyOrName,Default) -> + ct_config:get_config(KeyOrName,Default,[]). + +get_subconfig(SubKeys,Values) -> + get_subconfig(SubKeys,Values,[],[]). + +get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) -> + case do_get_config(SubKeys,Value,[]) of + {ok,SubMapped} -> + case lists:member(all,Opts) of + true -> + get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts); + false -> + {ok,SubMapped} + end; + _Error -> + get_subconfig(SubKeys,Rest,Mapped,Opts) + end; +get_subconfig(SubKeys,[],[],_) -> + {error,{not_available,SubKeys}}; +get_subconfig(_SubKeys,[],Mapped,_) -> + {ok,Mapped}. + +do_get_config([Key|Required],Available,Mapped) -> + case lists:keysearch(Key,1,Available) of + {value,{Key,Value}} -> + NewAvailable = lists:keydelete(Key,1,Available), + NewMapped = [{Key,Value}|Mapped], + do_get_config(Required,NewAvailable,NewMapped); + false -> + {error,{not_available,Key}} + end; +do_get_config([],_Available,Mapped) -> + {ok,lists:reverse(Mapped)}. + +get_all_config() -> + ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3', + default='$4',_='_'}, + [], + [{{'$1','$2','$3','$4'}}]}]). + +lookup_config(KeyOrName) -> + case lookup_name(KeyOrName) of + [] -> + lookup_key(KeyOrName); + Values -> + Values + end. + +lookup_name(Name) -> + ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'}, + [], + [{{'$1','$2'}}]}]). +lookup_key(Key) -> + ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'}, + [], + [{{'$1','$2'}}]}]). + delete_config(Default) -> ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), ok. @@ -115,3 +442,247 @@ get_config_file_list(Opts)-> end ++ process_user_configs(Opts, []), CfgFiles. + +%%%----------------------------------------------------------------- +%%% @hidden +%%% @equiv ct:require/1 +require(Key) when is_atom(Key) -> + require({Key,[]}); +require({Key,SubKeys}) when is_atom(Key) -> + allocate('_UNDEF',Key,to_list(SubKeys)); +require(Key) -> + {error,{invalid,Key}}. + + +%%%----------------------------------------------------------------- +%%% @hidden +%%% @equiv ct:require/2 +require(Name,Key) when is_atom(Key) -> + require(Name,{Key,[]}); +require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) -> + call({require,Name,Key,to_list(SubKeys)}); +require(Name,Keys) -> + {error,{invalid,{Name,Keys}}}. + +to_list(X) when is_list(X) -> X; +to_list(X) -> [X]. + +do_require(Name,Key,SubKeys) when is_list(SubKeys) -> + case get_key_from_name(Name) of + {error,_} -> + allocate(Name,Key,SubKeys); + {ok,Key} -> + %% already allocated - check that it has all required subkeys + Vals = [Val || {_Ref,Val} <- lookup_name(Name)], + case get_subconfig(SubKeys,Vals) of + {ok,_SubMapped} -> + ok; + Error -> + Error + end; + {ok,OtherKey} -> + {error,{name_in_use,Name,OtherKey}} + end. + +%%%----------------------------------------------------------------- +%%% @spec +%%% +%%% @doc +encrypt_config_file(SrcFileName, EncryptFileName) -> + case get_crypt_key_from_file() of + {error,_} = E -> + E; + Key -> + encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) + end. + + +get_ref_from_name(Name) -> + case ets:select(?attr_table,[{#ct_conf{name=Name,ref='$1',_='_'}, + [], + ['$1']}]) of + [Ref] -> + {ok,Ref}; + _ -> + {error,{no_such_name,Name}} + end. + +get_name_from_ref(Ref) -> + case ets:select(?attr_table,[{#ct_conf{name='$1',ref=Ref,_='_'}, + [], + ['$1']}]) of + [Name] -> + {ok,Name}; + _ -> + {error,{no_such_ref,Ref}} + end. + +get_key_from_name(Name) -> + case ets:select(?attr_table,[{#ct_conf{name=Name,key='$1',_='_'}, + [], + ['$1']}]) of + [Key|_] -> + {ok,Key}; + _ -> + {error,{no_such_name,Name}} + end. + +%%%----------------------------------------------------------------- +%%% @spec +%%% +%%% @doc +encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) -> + case get_crypt_key_from_file(KeyFile) of + {error,_} = E -> + E; + Key -> + encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) + end; + +encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) -> + crypto:start(), + {K1,K2,K3,IVec} = make_crypto_key(Key), + case file:read_file(SrcFileName) of + {ok,Bin0} -> + Bin1 = term_to_binary({SrcFileName,Bin0}), + Bin2 = case byte_size(Bin1) rem 8 of + 0 -> Bin1; + N -> list_to_binary([Bin1,random_bytes(8-N)]) + end, + EncBin = crypto:des3_cbc_encrypt(K1, K2, K3, IVec, Bin2), + case file:write_file(EncryptFileName, EncBin) of + ok -> + io:format("~s --(encrypt)--> ~s~n", + [SrcFileName,EncryptFileName]), + ok; + {error,Reason} -> + {error,{Reason,EncryptFileName}} + end; + {error,Reason} -> + {error,{Reason,SrcFileName}} + end. + +%%%----------------------------------------------------------------- +%%% @spec +%%% +%%% @doc +decrypt_config_file(EncryptFileName, TargetFileName) -> + case get_crypt_key_from_file() of + {error,_} = E -> + E; + Key -> + decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) + end. + + +set_default_config(NewConfig, Scope) -> + call({set_default_config, {NewConfig, Scope}}). + +set_default_config(Name, NewConfig, Scope) -> + call({set_default_config, {Name, NewConfig, Scope}}). + +delete_default_config(Scope) -> + call({delete_default_config, Scope}). + +update_config(Name, Config) -> + call({update_config, {Name, Config}}). + +%%%----------------------------------------------------------------- +%%% @spec +%%% +%%% @doc +decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) -> + case get_crypt_key_from_file(KeyFile) of + {error,_} = E -> + E; + Key -> + decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) + end; + +decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) -> + crypto:start(), + {K1,K2,K3,IVec} = make_crypto_key(Key), + case file:read_file(EncryptFileName) of + {ok,Bin} -> + DecBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin), + case catch binary_to_term(DecBin) of + {'EXIT',_} -> + {error,bad_file}; + {_SrcFile,SrcBin} -> + case TargetFileName of + undefined -> + {ok,SrcBin}; + _ -> + case file:write_file(TargetFileName, SrcBin) of + ok -> + io:format("~s --(decrypt)--> ~s~n", + [EncryptFileName,TargetFileName]), + ok; + {error,Reason} -> + {error,{Reason,TargetFileName}} + end + end + end; + {error,Reason} -> + {error,{Reason,EncryptFileName}} + end. + + +get_crypt_key_from_file(File) -> + case file:read_file(File) of + {ok,Bin} -> + case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of + [Key] -> + Key; + _ -> + {error,{bad_crypt_file,File}} + end; + {error,Reason} -> + {error,{Reason,File}} + end. + +get_crypt_key_from_file() -> + CwdFile = filename:join(".",?cryptfile), + {Result,FullName} = + case file:read_file(CwdFile) of + {ok,Bin} -> + {Bin,CwdFile}; + _ -> + case init:get_argument(home) of + {ok,[[Home]]} -> + HomeFile = filename:join(Home,?cryptfile), + case file:read_file(HomeFile) of + {ok,Bin} -> + {Bin,HomeFile}; + _ -> + {{error,no_crypt_file},noent} + end; + _ -> + {{error,no_crypt_file},noent} + end + end, + case FullName of + noent -> + Result; + _ -> + case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of + [Key] -> + io:format("~nCrypt key file: ~s~n", [FullName]), + Key; + _ -> + {error,{bad_crypt_file,FullName}} + end + end. + +make_crypto_key(String) -> + <> = First = erlang:md5(String), + <> = erlang:md5([First|lists:reverse(String)]), + {K1,K2,K3,IVec}. + +random_bytes(N) -> + {A,B,C} = now(), + random:seed(A, B, C), + random_bytes_1(N, []). + +random_bytes_1(0, Acc) -> Acc; +random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl index a5947cafef..be0433aa9f 100755 --- a/lib/common_test/src/ct_config_plain.erl +++ b/lib/common_test/src/ct_config_plain.erl @@ -38,10 +38,10 @@ read_config_file(ConfigFile) -> {key,K} -> K; {file,F} -> - ct_util:get_crypt_key_from_file(F) + ct_config:get_crypt_key_from_file(F) end; _ -> - ct_util:get_crypt_key_from_file() + ct_config:get_crypt_key_from_file() end, case Key of {error,no_crypt_file} -> @@ -49,7 +49,7 @@ read_config_file(ConfigFile) -> {error,CryptError} -> {error, decrypt_file_error, CryptError}; _ when is_list(Key) -> - case ct_util:decrypt_config_file(ConfigFile, undefined, {key,Key}) of + case ct_config:decrypt_config_file(ConfigFile, undefined, {key,Key}) of {ok,CfgBin} -> case read_config_terms(CfgBin) of {error,ReadFail} -> diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index ed8b564921..89bbc2448f 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -113,9 +113,9 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> ok; true -> %% delete all default values used in previous suite - ct_util:delete_default_config(suite), + ct_config:delete_default_config(suite), %% release all name -> key bindings (once per suite) - ct_util:release_allocated() + ct_config:release_allocated() end, TestCaseInfo = case catch apply(Mod,Func,[]) of @@ -125,7 +125,7 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) -> %% clear all config data default values set by previous %% testcase info function (these should only survive the %% testcase, not the whole suite) - ct_util:delete_default_config(testcase), + ct_config:delete_default_config(testcase), case add_defaults(Mod,Func,TestCaseInfo,DoInit) of Error = {suite0_failed,_} -> ct_logs:init_tc(), @@ -381,10 +381,10 @@ try_set_default(Name,Key,Info,Where) -> {_,[]} -> no_default; {'_UNDEF',_} -> - [ct_util:set_default_config([CfgVal],Where) || CfgVal <- CfgElems], + [ct_config:set_default_config([CfgVal],Where) || CfgVal <- CfgElems], ok; _ -> - [ct_util:set_default_config(Name,[CfgVal],Where) || CfgVal <- CfgElems], + [ct_config:set_default_config(Name,[CfgVal],Where) || CfgVal <- CfgElems], ok end. diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index b86dd8fe2f..04bc047f43 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -222,7 +222,7 @@ script_start1(Parent, Args) -> end, Config = ConfigFiles ++ UserConfigs, case Config of - false -> + [] -> case install([{config,[]}, {event_handler,EvHandlers}], LogDir) of diff --git a/lib/common_test/src/ct_snmp.erl b/lib/common_test/src/ct_snmp.erl index 7ff88ad7d3..b515c578c7 100644 --- a/lib/common_test/src/ct_snmp.erl +++ b/lib/common_test/src/ct_snmp.erl @@ -332,7 +332,7 @@ set_info(Config) -> register_users(MgrAgentConfName, Users) -> {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {users, Users}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), setup_users(Users). %%% @spec register_agents(MgrAgentConfName, ManagedAgents) -> ok | {error, Reason} @@ -347,7 +347,7 @@ register_agents(MgrAgentConfName, ManagedAgents) -> {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(managed_agents, 1, SnmpVals, {managed_agents, ManagedAgents}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), setup_managed_agents(ManagedAgents). %%% @spec register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason} @@ -361,7 +361,7 @@ register_agents(MgrAgentConfName, ManagedAgents) -> register_usm_users(MgrAgentConfName, UsmUsers) -> {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {usm_users, UsmUsers}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID), setup_usm_users(UsmUsers, EngineID). @@ -376,7 +376,7 @@ unregister_users(MgrAgentConfName) -> ct:get_config({MgrAgentConfName, users})), {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {users, []}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), takedown_users(Users). %%% @spec unregister_agents(MgrAgentConfName) -> ok | {error, Reason} @@ -393,7 +393,7 @@ unregister_agents(MgrAgentConfName) -> {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(managed_agents, 1, SnmpVals, {managed_agents, []}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), takedown_managed_agents(ManagedAgents). @@ -409,7 +409,7 @@ update_usm_users(MgrAgentConfName, UsmUsers) -> {snmp, SnmpVals} = ct:get_config(MgrAgentConfName), NewSnmpVals = lists:keyreplace(usm_users, 1, SnmpVals, {usm_users, UsmUsers}), - ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), + ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}), EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID), do_update_usm_users(UsmUsers, EngineID). diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 814bd97840..df124e1ca8 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -30,10 +30,7 @@ -export([register_connection/4,unregister_connection/1, does_connection_exist/3,get_key_from_name/1]). --export([require/1, require/2, get_config/1, get_config/2, get_config/3, - set_default_config/2, set_default_config/3, delete_default_config/1, - get_all_config/0, update_config/2, - release_allocated/0, close_connections/0]). +-export([close_connections/0]). -export([save_suite_data/3, save_suite_data/2, read_suite_data/1, delete_suite_data/0, delete_suite_data/1, match_delete_suite_data/1, @@ -46,6 +43,8 @@ silence_all_connections/0, silence_connections/1, is_silenced/1, reset_silent_connections/0]). +-export([get_mode/0, create_table/3, read_opts/0]). + -export([set_cwd/1, reset_cwd/0]). -export([parse_table/1]). @@ -56,9 +55,6 @@ -export([is_test_dir/1, get_testdir/2]). --export([encrypt_config_file/2, encrypt_config_file/3, - decrypt_config_file/2, decrypt_config_file/3]). - -export([kill_attached/2, get_attached/1, ct_make_ref/0]). -export([warn_duplicates/1]). @@ -66,13 +62,8 @@ -include("ct_event.hrl"). -include("ct_util.hrl"). --record(ct_conf,{key,value,ref,name='_UNDEF',default=false}). -%% default = {true,suite} | {true,testcase} | false - -record(suite_data, {key,name,value}). --define(cryptfile, ".ct_config.crypt"). - %%%----------------------------------------------------------------- %%% @spec start(Mode) -> Pid | exit(Error) %%% Mode = normal | interactive @@ -119,7 +110,6 @@ start(Mode,LogDir) -> do_start(Parent,Mode,LogDir) -> process_flag(trap_exit,true), register(ct_util_server,self()), - create_table(?attr_table,bag,#ct_conf.key), create_table(?conn_table,#conn.handle), create_table(?board_table,2), create_table(?suite_table,#suite_data.key), @@ -148,38 +138,34 @@ do_start(Parent,Mode,LogDir) -> ct_event:add_handler([{vts,VtsPid}]) end end, - case ct_config:read_config_files(Opts) of - ok -> - %% add user handlers - case lists:keysearch(event_handler,1,Opts) of - {value,{_,Handlers}} -> - Add = fun({H,Args}) -> - case catch gen_event:add_handler(?CT_EVMGR_REF,H,Args) of - ok -> ok; - {'EXIT',Why} -> exit(Why); - Other -> exit({event_handler,Other}) - end - end, - case catch lists:foreach(Add,Handlers) of - {'EXIT',Reason} -> - Parent ! {self(),Reason}; - _ -> - ok - end; - false -> + %% start ct_config server + CTCSResult = ct_config:start(Mode, LogDir), + %% add user event handlers + case lists:keysearch(event_handler,1,Opts) of + {value,{_,Handlers}} -> + Add = fun({H,Args}) -> + case catch gen_event:add_handler(?CT_EVMGR_REF,H,Args) of + ok -> ok; + {'EXIT',Why} -> exit(Why); + Other -> exit({event_handler,Other}) + end + end, + case catch lists:foreach(Add,Handlers) of + {'EXIT',Reason} -> + Parent ! {self(),Reason}; + _ -> ok - end, - {StartTime,TestLogDir} = ct_logs:init(Mode), - ct_event:notify(#event{name=test_start, - node=node(), - data={StartTime, - lists:flatten(TestLogDir)}}), - Parent ! {self(),started}, - loop(Mode,[],StartDir); - ReadError -> - Parent ! {self(),ReadError}, - exit(ReadError) - end. + end; + false -> + ok + end, + {StartTime,TestLogDir} = ct_logs:init(Mode), + ct_event:notify(#event{name=test_start, + node=node(), + data={StartTime, + lists:flatten(TestLogDir)}}), + Parent ! {self(),started}, + loop(Mode,[],StartDir). create_table(TableName,KeyPos) -> create_table(TableName,set,KeyPos). @@ -197,18 +183,6 @@ read_opts() -> {error,{bad_installation,Error}} end. -set_default_config(NewConfig, Scope) -> - call({set_default_config, {NewConfig, Scope}}). - -set_default_config(Name, NewConfig, Scope) -> - call({set_default_config, {Name, NewConfig, Scope}}). - -delete_default_config(Scope) -> - call({delete_default_config, Scope}). - -update_config(Name, Config) -> - call({update_config, {Name, Config}}). - save_suite_data(Key, Value) -> call({save_suite_data, {Key, undefined, Value}}). @@ -254,26 +228,6 @@ loop(Mode,TestData,StartDir) -> ct_logs:make_last_run_index(), return(From,ok), loop(Mode,TestData,StartDir); - {{require,Name,Tag,SubTags},From} -> - Result = do_require(Name,Tag,SubTags), - return(From,Result), - loop(Mode,TestData,StartDir); - {{set_default_config,{Config,Scope}},From} -> - ct_config:set_config(Config,{true,Scope}), - return(From,ok), - loop(Mode,TestData,StartDir); - {{set_default_config,{Name,Config,Scope}},From} -> - ct_config:set_config(Name,Config,{true,Scope}), - return(From,ok), - loop(Mode,TestData,StartDir); - {{delete_default_config,Scope},From} -> - ct_config:delete_config({true,Scope}), - return(From,ok), - loop(Mode,TestData,StartDir); - {{update_config,{Name,NewConfig}},From} -> - update_conf(Name,NewConfig), - return(From,ok), - loop(Mode,TestData,StartDir); {{save_suite_data,{Key,Name,Value}},From} -> ets:insert(?suite_table, #suite_data{key=Key, name=Name, @@ -346,7 +300,6 @@ loop(Mode,TestData,StartDir) -> ct_event:sync_notify(#event{name=test_done, node=node(), data=Time}), - ets:delete(?attr_table), close_connections(ets:tab2list(?conn_table)), ets:delete(?conn_table), ets:delete(?board_table), @@ -354,6 +307,7 @@ loop(Mode,TestData,StartDir) -> ct_logs:close(How), file:set_cwd(StartDir), ct_event:stop(), + ct_config:stop(), return(From,ok); {get_mode,From} -> return(From,Mode), @@ -375,6 +329,8 @@ close_connections([#conn{handle=Handle,callback=CB}|Conns]) -> close_connections([]) -> ok. +get_key_from_name(Name)-> + ct_config:get_key_from_name(Name). %%%----------------------------------------------------------------- %%% @spec register_connection(TargetName,Address,Callback,Handle) -> @@ -392,7 +348,7 @@ close_connections([]) -> %%% test is finished by calling Callback:close/1.

register_connection(TargetName,Address,Callback,Handle) -> TargetRef = - case get_ref_from_name(TargetName) of + case ct_config:get_ref_from_name(TargetName) of {ok,Ref} -> Ref; _ -> @@ -430,7 +386,7 @@ unregister_connection(Handle) -> %%% %%% @doc Check if a connection already exists. does_connection_exist(TargetName,Address,Callback) -> - case get_ref_from_name(TargetName) of + case ct_config:get_ref_from_name(TargetName) of {ok,TargetRef} -> case ets:select(?conn_table,[{#conn{handle='$1', targetref=TargetRef, @@ -460,7 +416,7 @@ does_connection_exist(TargetName,Address,Callback) -> %%% @doc Return all connections for the Callback on the %%% given target (TargetName). get_connections(TargetName,Callback) -> - case get_ref_from_name(TargetName) of + case ct_config:get_ref_from_name(TargetName) of {ok,Ref} -> {ok,ets:select(?conn_table,[{#conn{handle='$1', address='$2', @@ -480,236 +436,11 @@ get_target_name(ConnPid) -> [], ['$1']}]) of [TargetRef] -> - get_name_from_ref(TargetRef); + ct_config:get_name_from_ref(TargetRef); [] -> {error,{unknown_connection,ConnPid}} end. - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:require/1 -require(Key) when is_atom(Key) -> - require({Key,[]}); -require({Key,SubKeys}) when is_atom(Key) -> - allocate('_UNDEF',Key,to_list(SubKeys)); -require(Key) -> - {error,{invalid,Key}}. - - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:require/2 -require(Name,Key) when is_atom(Key) -> - require(Name,{Key,[]}); -require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) -> - call({require,Name,Key,to_list(SubKeys)}); -require(Name,Keys) -> - {error,{invalid,{Name,Keys}}}. - -to_list(X) when is_list(X) -> X; -to_list(X) -> [X]. - -do_require(Name,Key,SubKeys) when is_list(SubKeys) -> - case get_key_from_name(Name) of - {error,_} -> - allocate(Name,Key,SubKeys); - {ok,Key} -> - %% already allocated - check that it has all required subkeys - Vals = [Val || {_Ref,Val} <- lookup_name(Name)], - case get_subconfig(SubKeys,Vals) of - {ok,_SubMapped} -> - ok; - Error -> - Error - end; - {ok,OtherKey} -> - {error,{name_in_use,Name,OtherKey}} - end. - -allocate(Name,Key,SubKeys) -> - case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of - [] -> - {error,{not_available,Key}}; - Available -> - case allocate_subconfig(Name,SubKeys,Available,false) of - ok -> - ok; - Error -> - Error - end - end. - -allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) -> - case do_get_config(SubKeys,Value,[]) of - {ok,_SubMapped} -> - ets:insert(?attr_table,C#ct_conf{name=Name}), - allocate_subconfig(Name,SubKeys,Rest,true); - _Error -> - allocate_subconfig(Name,SubKeys,Rest,Found) - end; -allocate_subconfig(_Name,_SubKeys,[],true) -> - ok; -allocate_subconfig(_Name,SubKeys,[],false) -> - {error,{not_available,SubKeys}}. - - - - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:get_config/1 -get_config(KeyOrName) -> - get_config(KeyOrName,undefined,[]). - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:get_config/2 -get_config(KeyOrName,Default) -> - get_config(KeyOrName,Default,[]). - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:get_config/3 -get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) -> - case lookup_config(KeyOrName) of - [] -> - Default; - [{_Ref,Val}|_] = Vals -> - case {lists:member(all,Opts),lists:member(element,Opts)} of - {true,true} -> - [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)]; - {true,false} -> - [V || {_R,V} <- lists:sort(Vals)]; - {false,true} -> - {KeyOrName,Val}; - {false,false} -> - Val - end - end; - -get_config({KeyOrName,SubKey},Default,Opts) -> - case lookup_config(KeyOrName) of - [] -> - Default; - Vals -> - Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of - Result=[L|_] when is_list(L) -> - case L of - [{_,_}|_] -> - Result; - _ -> - [] - end; - _ -> - [] - end, - case get_subconfig([SubKey],Vals1,[],Opts) of - {ok,[{_,SubVal}|_]=SubVals} -> - case {lists:member(all,Opts),lists:member(element,Opts)} of - {true,true} -> - [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals]; - {true,false} -> - [Val || {_SubKey,Val} <- SubVals]; - {false,true} -> - {{KeyOrName,SubKey},SubVal}; - {false,false} -> - SubVal - end; - _ -> - Default - end - end. - - -get_subconfig(SubKeys,Values) -> - get_subconfig(SubKeys,Values,[],[]). - -get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) -> - case do_get_config(SubKeys,Value,[]) of - {ok,SubMapped} -> - case lists:member(all,Opts) of - true -> - get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts); - false -> - {ok,SubMapped} - end; - _Error -> - get_subconfig(SubKeys,Rest,Mapped,Opts) - end; -get_subconfig(SubKeys,[],[],_) -> - {error,{not_available,SubKeys}}; -get_subconfig(_SubKeys,[],Mapped,_) -> - {ok,Mapped}. - -do_get_config([Key|Required],Available,Mapped) -> - case lists:keysearch(Key,1,Available) of - {value,{Key,Value}} -> - NewAvailable = lists:keydelete(Key,1,Available), - NewMapped = [{Key,Value}|Mapped], - do_get_config(Required,NewAvailable,NewMapped); - false -> - {error,{not_available,Key}} - end; -do_get_config([],_Available,Mapped) -> - {ok,lists:reverse(Mapped)}. - -get_all_config() -> - ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3', - default='$4',_='_'}, - [], - [{{'$1','$2','$3','$4'}}]}]). - -lookup_config(KeyOrName) -> - case lookup_name(KeyOrName) of - [] -> - lookup_key(KeyOrName); - Values -> - Values - end. - -lookup_name(Name) -> - ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'}, - [], - [{{'$1','$2'}}]}]). -lookup_key(Key) -> - ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'}, - [], - [{{'$1','$2'}}]}]). - - - - -%%%----------------------------------------------------------------- -%%% @spec release_allocated() -> ok -%%% -%%% @doc Release all allocated resources, but don't take down any -%%% connections. -release_allocated() -> - Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'}, - [{'=/=','$1','_UNDEF'}], - ['$_']}]), - release_allocated(Allocated). -release_allocated([H|T]) -> - ets:delete_object(?attr_table,H), - ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}), - release_allocated(T); -release_allocated([]) -> - ok. - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -update_conf(Name, NewConfig) -> - Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]), - lists:foreach(fun(OldElem) -> - NewElem = OldElem#ct_conf{value=NewConfig}, - ets:delete_object(?attr_table, OldElem), - ets:insert(?attr_table, NewElem) - end, Old), - ok. - %%%----------------------------------------------------------------- %%% @spec close_connections() -> ok %%% @@ -889,166 +620,6 @@ get_testdir(Dir, Suite) when is_list(Suite) -> get_testdir(Dir, _) -> get_testdir(Dir, all). -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -encrypt_config_file(SrcFileName, EncryptFileName) -> - case get_crypt_key_from_file() of - {error,_} = E -> - E; - Key -> - encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) - end. - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) -> - case get_crypt_key_from_file(KeyFile) of - {error,_} = E -> - E; - Key -> - encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) - end; - -encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) -> - crypto:start(), - {K1,K2,K3,IVec} = make_crypto_key(Key), - case file:read_file(SrcFileName) of - {ok,Bin0} -> - Bin1 = term_to_binary({SrcFileName,Bin0}), - Bin2 = case byte_size(Bin1) rem 8 of - 0 -> Bin1; - N -> list_to_binary([Bin1,random_bytes(8-N)]) - end, - EncBin = crypto:des3_cbc_encrypt(K1, K2, K3, IVec, Bin2), - case file:write_file(EncryptFileName, EncBin) of - ok -> - io:format("~s --(encrypt)--> ~s~n", - [SrcFileName,EncryptFileName]), - ok; - {error,Reason} -> - {error,{Reason,EncryptFileName}} - end; - {error,Reason} -> - {error,{Reason,SrcFileName}} - end. - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -decrypt_config_file(EncryptFileName, TargetFileName) -> - case get_crypt_key_from_file() of - {error,_} = E -> - E; - Key -> - decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) - end. - - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) -> - case get_crypt_key_from_file(KeyFile) of - {error,_} = E -> - E; - Key -> - decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) - end; - -decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) -> - crypto:start(), - {K1,K2,K3,IVec} = make_crypto_key(Key), - case file:read_file(EncryptFileName) of - {ok,Bin} -> - DecBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin), - case catch binary_to_term(DecBin) of - {'EXIT',_} -> - {error,bad_file}; - {_SrcFile,SrcBin} -> - case TargetFileName of - undefined -> - {ok,SrcBin}; - _ -> - case file:write_file(TargetFileName, SrcBin) of - ok -> - io:format("~s --(decrypt)--> ~s~n", - [EncryptFileName,TargetFileName]), - ok; - {error,Reason} -> - {error,{Reason,TargetFileName}} - end - end - end; - {error,Reason} -> - {error,{Reason,EncryptFileName}} - end. - - -get_crypt_key_from_file(File) -> - case file:read_file(File) of - {ok,Bin} -> - case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of - [Key] -> - Key; - _ -> - {error,{bad_crypt_file,File}} - end; - {error,Reason} -> - {error,{Reason,File}} - end. - -get_crypt_key_from_file() -> - CwdFile = filename:join(".",?cryptfile), - {Result,FullName} = - case file:read_file(CwdFile) of - {ok,Bin} -> - {Bin,CwdFile}; - _ -> - case init:get_argument(home) of - {ok,[[Home]]} -> - HomeFile = filename:join(Home,?cryptfile), - case file:read_file(HomeFile) of - {ok,Bin} -> - {Bin,HomeFile}; - _ -> - {{error,no_crypt_file},noent} - end; - _ -> - {{error,no_crypt_file},noent} - end - end, - case FullName of - noent -> - Result; - _ -> - case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of - [Key] -> - io:format("~nCrypt key file: ~s~n", [FullName]), - Key; - _ -> - {error,{bad_crypt_file,FullName}} - end - end. - -make_crypto_key(String) -> - <> = First = erlang:md5(String), - <> = erlang:md5([First|lists:reverse(String)]), - {K1,K2,K3,IVec}. - -random_bytes(N) -> - {A,B,C} = now(), - random:seed(A, B, C), - random_bytes_1(N, []). - -random_bytes_1(0, Acc) -> Acc; -random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). - %%%----------------------------------------------------------------- %%% @spec @@ -1142,37 +713,6 @@ ct_make_ref_loop(N) -> From ! {self(),N}, ct_make_ref_loop(N+1) end. - -get_ref_from_name(Name) -> - case ets:select(?attr_table,[{#ct_conf{name=Name,ref='$1',_='_'}, - [], - ['$1']}]) of - [Ref] -> - {ok,Ref}; - _ -> - {error,{no_such_name,Name}} - end. - -get_name_from_ref(Ref) -> - case ets:select(?attr_table,[{#ct_conf{name='$1',ref=Ref,_='_'}, - [], - ['$1']}]) of - [Name] -> - {ok,Name}; - _ -> - {error,{no_such_ref,Ref}} - end. - -get_key_from_name(Name) -> - case ets:select(?attr_table,[{#ct_conf{name=Name,key='$1',_='_'}, - [], - ['$1']}]) of - [Key|_] -> - {ok,Key}; - _ -> - {error,{no_such_name,Name}} - end. - abs_name(Dir0) -> Abs = filename:absname(Dir0), -- cgit v1.2.3 From f2dcd87634cdcee65f0a930731a45005b4c20d08 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Wed, 24 Feb 2010 15:15:41 +0100 Subject: Implement reloading of the config data --- lib/common_test/src/ct.erl | 17 ++ lib/common_test/src/ct_config.erl | 380 ++++++++++++++++++-------------------- lib/common_test/src/ct_run.erl | 4 +- lib/common_test/src/ct_util.erl | 2 +- 4 files changed, 200 insertions(+), 203 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 3cc6154592..bee5c920a9 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -59,6 +59,7 @@ %% Test suite API -export([require/1, require/2, get_config/1, get_config/2, get_config/3, + reload_config/1, log/1, log/2, log/3, print/1, print/2, print/3, pal/1, pal/2, pal/3, @@ -378,6 +379,22 @@ get_config(Required,Default) -> get_config(Required,Default,Opts) -> ct_config:get_config(Required,Default,Opts). +%%%----------------------------------------------------------------- +%%% @spec reload_config(Required) -> ValueOrElement +%%% Required = KeyOrName | {KeyOrName,SubKey} +%%% KeyOrName = atom() +%%% SubKey = atom() +%%% ValueOrElement = term() +%%% +%%% @doc Reload config file which contains specified configuration key. +%%% +%%%

This function performs updating of the configuration data from which the +%%% given configuration variable was read, and returns the (possibly) new +%%% value of this variable.

+%%% +reload_config(Required)-> + ct_config:reload_config(Required). + %%%----------------------------------------------------------------- %%% @spec log(Format) -> ok %%% @equiv log(default,Format,[]) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 9a1f23201f..dbb0e0b152 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -24,73 +24,37 @@ %%---------------------------------------------------------------------- -module(ct_config). -% start of the config server --export([start/0, start/1, start/2, stop/0]). +-export([start/1, stop/0]). -% manipulating with config files -export([read_config_files/1, get_config_file_list/1]). -% require -export([require/1, require/2]). -% get config data -export([get_config/1, get_config/2, get_config/3, get_all_config/0]). -% set config data --export([set_config/1, set_config/2, set_config/3, - set_default_config/2, set_default_config/3]). +-export([set_default_config/2, set_default_config/3]). -% delete config data -export([delete_config/1, delete_default_config/1]). -% update and reload config --export([%reload_config/1, - update_config/2]). +-export([reload_config/1, update_config/2]). -% ? -export([release_allocated/0]). -export([encrypt_config_file/2, encrypt_config_file/3, decrypt_config_file/2, decrypt_config_file/3, get_crypt_key_from_file/0, get_crypt_key_from_file/1]). -% references -export([get_ref_from_name/1, get_name_from_ref/1, get_key_from_name/1]). -include("ct_util.hrl"). --include("ct_event.hrl"). -define(cryptfile, ".ct_config.crypt"). -% TODO: add handler field here --record(ct_conf,{key,value,ref,name='_UNDEF',default=false}). - -%%%----------------------------------------------------------------- -%%% @spec start(Mode) -> Pid | exit(Error) -%%% Mode = normal | interactive -%%% Pid = pid() -%%% -%%% @doc Start start the ct_config_server process -%%% (tool-internal use only). -%%% -%%%

This function is called from ct_run.erl. It starts and initiates -%%% the ct_config_server

-%%% -%%%

Returns the process identity of the -%%% ct_config_server.

-%%% -%%% @see ct -start() -> - start(normal,"."). - -start(LogDir) when is_list(LogDir) -> - start(normal,LogDir); -start(Mode) -> - start(Mode,"."). +-record(ct_conf,{key,value,handler,config,ref,name='_UNDEF',default=false}). -start(Mode,LogDir) -> +start(Mode) -> case whereis(ct_config_server) of undefined -> Me = self(), @@ -131,13 +95,6 @@ do_start(Parent) -> exit(ReadError) end. -%%%----------------------------------------------------------------- -%%% @spec stop() -> ok -%%% -%%% @doc Stop the ct_config_server and close all existing connections -%%% (tool-internal use only). -%%% -%%% @see ct stop() -> case whereis(ct_config_server) of undefined -> ok; @@ -159,7 +116,6 @@ call(Msg) -> return({To,Ref},Result) -> To ! {Ref, Result}. - loop(StartDir) -> receive {{require,Name,Tag,SubTags},From} -> @@ -167,11 +123,11 @@ loop(StartDir) -> return(From,Result), loop(StartDir); {{set_default_config,{Config,Scope}},From} -> - ct_config:set_config(Config,{true,Scope}), + set_config(Config,{true,Scope}), return(From,ok), loop(StartDir); {{set_default_config,{Name,Config,Scope}},From} -> - ct_config:set_config(Name,Config,{true,Scope}), + set_config(Name,Config,{true,Scope}), return(From,ok), loop(StartDir); {{delete_default_config,Scope},From} -> @@ -182,12 +138,69 @@ loop(StartDir) -> update_conf(Name,NewConfig), return(From,ok), loop(StartDir); + {{reload_config, KeyOrName},From}-> + NewValue = reload_conf(KeyOrName), + return(From, NewValue), + loop(StartDir); {{stop},From} -> ets:delete(?attr_table), file:set_cwd(StartDir), return(From,ok) end. +set_default_config(NewConfig, Scope) -> + call({set_default_config, {NewConfig, Scope}}). + +set_default_config(Name, NewConfig, Scope) -> + call({set_default_config, {Name, NewConfig, Scope}}). + +delete_default_config(Scope) -> + call({delete_default_config, Scope}). + +update_config(Name, Config) -> + call({update_config, {Name, Config}}). + +reload_config(KeyOrName)-> + call({reload_config, KeyOrName}). + +process_default_configs(Opts)-> + case lists:keysearch(config, 1, Opts) of + {value,{_,Files=[File|_]}} when is_list(File) -> + Files; + {value,{_,File=[C|_]}} when is_integer(C) -> + [File]; + {value,{_,[]}} -> + []; + false -> + [] + end. + +process_user_configs(Opts, Acc)-> + case lists:keytake(userconfig, 1, Opts) of + false-> + Acc; + {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when + is_list(File)-> + process_user_configs(NewOpts, [{Callback, Files} | Acc]); + {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when + is_integer(C)-> + process_user_configs(NewOpts, [{Callback, [File]} | Acc]); + {value, {userconfig, {_Callback, []}}, NewOpts}-> + process_user_configs(NewOpts, Acc) + end. + +get_config_file_list(Opts)-> + DefaultConfigs = process_default_configs(Opts), + CfgFiles = + if + DefaultConfigs == []-> + []; + true-> + [{ct_config_plain, DefaultConfigs}] + end ++ + process_user_configs(Opts, []), + CfgFiles. + read_config_files(Opts) -> AddCallback = fun(CallBack, Files)-> lists:map(fun(X)-> {CallBack, X} end, Files) @@ -202,21 +215,50 @@ read_config_files(Opts) -> false-> [] end, - read_config_files_int(ConfigFiles). + read_config_files_int(ConfigFiles, fun store_config/3). -read_config_files_int([{Callback, File}|Files])-> +read_config_files_int([{Callback, File}|Files], FunToSave)-> case Callback:read_config_file(File) of {ok, Config}-> - set_config(Config), - read_config_files_int(Files); + FunToSave(Config, Callback, File), + read_config_files_int(Files, FunToSave); {error, ErrorName, ErrorDetail}-> {user_error, {ErrorName, File, ErrorDetail}} end; -read_config_files_int([])-> +read_config_files_int([], _FunToSave)-> ok. -set_config(Config) -> - set_config('_UNDEF',Config,false). +store_config(Config, Callback, File)-> + [ets:insert(?attr_table, + #ct_conf{key=Key, + value=Val, + handler=Callback, + config=File, + ref=ct_util:ct_make_ref(), + default=false}) || + {Key,Val} <- Config]. + +rewrite_config(Config, Callback, File)-> + [begin + Rows=case ets:match_object(?attr_table, + #ct_conf{key=Key, + handler=Callback, + config=File,_='_'}) of + []-> + [#ct_conf{default=false}]; + Elements-> + Elements + end, + lists:foreach(fun(Row)-> + ets:delete_object(?attr_table, Row), + ets:insert(?attr_table, + Row#ct_conf{key=Key, + value=Val, + handler=Callback, + config=File, + ref=ct_util:ct_make_ref()}) end, + Rows) + end || {Key,Val} <- Config]. set_config(Config,Default) -> set_config('_UNDEF',Config,Default). @@ -227,6 +269,12 @@ set_config(Name,Config,Default) -> name=Name,default=Default}) || {Key,Val} <- Config]. +get_config(KeyOrName) -> + get_config(KeyOrName,undefined,[]). + +get_config(KeyOrName,Default) -> + get_config(KeyOrName,Default,[]). + get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) -> case lookup_config(KeyOrName) of [] -> @@ -277,75 +325,6 @@ get_config({KeyOrName,SubKey},Default,Opts) -> end end. - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc -update_conf(Name, NewConfig) -> - Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]), - lists:foreach(fun(OldElem) -> - NewElem = OldElem#ct_conf{value=NewConfig}, - ets:delete_object(?attr_table, OldElem), - ets:insert(?attr_table, NewElem) - end, Old), - ok. - -%%%----------------------------------------------------------------- -%%% @spec release_allocated() -> ok -%%% -%%% @doc Release all allocated resources, but don't take down any -%%% connections. -release_allocated() -> - Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'}, - [{'=/=','$1','_UNDEF'}], - ['$_']}]), - release_allocated(Allocated). -release_allocated([H|T]) -> - ets:delete_object(?attr_table,H), - ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}), - release_allocated(T); -release_allocated([]) -> - ok. - -allocate(Name,Key,SubKeys) -> - case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of - [] -> - {error,{not_available,Key}}; - Available -> - case allocate_subconfig(Name,SubKeys,Available,false) of - ok -> - ok; - Error -> - Error - end - end. - -allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) -> - case do_get_config(SubKeys,Value,[]) of - {ok,_SubMapped} -> - ets:insert(?attr_table,C#ct_conf{name=Name}), - allocate_subconfig(Name,SubKeys,Rest,true); - _Error -> - allocate_subconfig(Name,SubKeys,Rest,Found) - end; -allocate_subconfig(_Name,_SubKeys,[],true) -> - ok; -allocate_subconfig(_Name,SubKeys,[],false) -> - {error,{not_available,SubKeys}}. - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:get_config/1 -get_config(KeyOrName) -> - ct_config:get_config(KeyOrName,undefined,[]). - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:get_config/2 -get_config(KeyOrName,Default) -> - ct_config:get_config(KeyOrName,Default,[]). - get_subconfig(SubKeys,Values) -> get_subconfig(SubKeys,Values,[],[]). @@ -401,51 +380,87 @@ lookup_key(Key) -> [], [{{'$1','$2'}}]}]). -delete_config(Default) -> - ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), +lookup_handler_for_config({Key, _Subkey})-> + lookup_handler_for_config(Key); +lookup_handler_for_config(KeyOrName)-> + case lookup_handler_for_name(KeyOrName) of + [] -> + lookup_handler_for_key(KeyOrName); + Values -> + Values + end. + +lookup_handler_for_name(Name)-> + ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',name=Name,_='_'}, + [], + [{{'$1','$2'}}]}]). + +lookup_handler_for_key(Key)-> + ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',key=Key,_='_'}, + [], + [{{'$1','$2'}}]}]). + + +update_conf(Name, NewConfig) -> + Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]), + lists:foreach(fun(OldElem) -> + NewElem = OldElem#ct_conf{value=NewConfig}, + ets:delete_object(?attr_table, OldElem), + ets:insert(?attr_table, NewElem) + end, Old), ok. -process_user_configs(Opts, Acc)-> - case lists:keytake(userconfig, 1, Opts) of - false-> - Acc; - {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when - is_list(File)-> - process_user_configs(NewOpts, [{Callback, Files} | Acc]); - {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when - is_integer(C)-> - process_user_configs(NewOpts, [{Callback, [File]} | Acc]); - {value, {userconfig, {_Callback, []}}, NewOpts}-> - process_user_configs(NewOpts, Acc) +reload_conf(KeyOrName) -> + case lookup_handler_for_config(KeyOrName) of + []-> + undefined; + HandlerList-> + read_config_files_int(HandlerList, fun rewrite_config/3), + get_config(KeyOrName) end. -process_default_configs(Opts)-> - case lists:keysearch(config, 1, Opts) of - {value,{_,Files=[File|_]}} when is_list(File) -> - Files; - {value,{_,File=[C|_]}} when is_integer(C) -> - [File]; - {value,{_,[]}} -> - []; - false -> - [] +release_allocated() -> + Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'}, + [{'=/=','$1','_UNDEF'}], + ['$_']}]), + release_allocated(Allocated). +release_allocated([H|T]) -> + ets:delete_object(?attr_table,H), + ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}), + release_allocated(T); +release_allocated([]) -> + ok. + +allocate(Name,Key,SubKeys) -> + case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of + [] -> + {error,{not_available,Key}}; + Available -> + case allocate_subconfig(Name,SubKeys,Available,false) of + ok -> + ok; + Error -> + Error + end end. -get_config_file_list(Opts)-> - DefaultConfigs = process_default_configs(Opts), - CfgFiles = - if - DefaultConfigs == []-> - []; - true-> - [{ct_config_plain, DefaultConfigs}] - end ++ - process_user_configs(Opts, []), - CfgFiles. +allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) -> + case do_get_config(SubKeys,Value,[]) of + {ok,_SubMapped} -> + ets:insert(?attr_table,C#ct_conf{name=Name}), + allocate_subconfig(Name,SubKeys,Rest,true); + _Error -> + allocate_subconfig(Name,SubKeys,Rest,Found) + end; +allocate_subconfig(_Name,_SubKeys,[],true) -> + ok; +allocate_subconfig(_Name,SubKeys,[],false) -> + {error,{not_available,SubKeys}}. + +delete_config(Default) -> + ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}), + ok. -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:require/1 require(Key) when is_atom(Key) -> require({Key,[]}); require({Key,SubKeys}) when is_atom(Key) -> @@ -453,10 +468,6 @@ require({Key,SubKeys}) when is_atom(Key) -> require(Key) -> {error,{invalid,Key}}. - -%%%----------------------------------------------------------------- -%%% @hidden -%%% @equiv ct:require/2 require(Name,Key) when is_atom(Key) -> require(Name,{Key,[]}); require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) -> @@ -484,10 +495,6 @@ do_require(Name,Key,SubKeys) when is_list(SubKeys) -> {error,{name_in_use,Name,OtherKey}} end. -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc encrypt_config_file(SrcFileName, EncryptFileName) -> case get_crypt_key_from_file() of {error,_} = E -> @@ -496,7 +503,6 @@ encrypt_config_file(SrcFileName, EncryptFileName) -> encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) end. - get_ref_from_name(Name) -> case ets:select(?attr_table,[{#ct_conf{name=Name,ref='$1',_='_'}, [], @@ -527,10 +533,6 @@ get_key_from_name(Name) -> {error,{no_such_name,Name}} end. -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) -> case get_crypt_key_from_file(KeyFile) of {error,_} = E -> @@ -562,10 +564,6 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) -> {error,{Reason,SrcFileName}} end. -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc decrypt_config_file(EncryptFileName, TargetFileName) -> case get_crypt_key_from_file() of {error,_} = E -> @@ -574,23 +572,6 @@ decrypt_config_file(EncryptFileName, TargetFileName) -> decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) end. - -set_default_config(NewConfig, Scope) -> - call({set_default_config, {NewConfig, Scope}}). - -set_default_config(Name, NewConfig, Scope) -> - call({set_default_config, {Name, NewConfig, Scope}}). - -delete_default_config(Scope) -> - call({delete_default_config, Scope}). - -update_config(Name, Config) -> - call({update_config, {Name, Config}}). - -%%%----------------------------------------------------------------- -%%% @spec -%%% -%%% @doc decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) -> case get_crypt_key_from_file(KeyFile) of {error,_} = E -> @@ -627,7 +608,6 @@ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) -> {error,{Reason,EncryptFileName}} end. - get_crypt_key_from_file(File) -> case file:read_file(File) of {ok,Bin} -> diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 04bc047f43..d3239b7ce3 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1576,8 +1576,8 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> Failure; MakeInfo -> FileTest = fun(F, suites) -> is_suite(F); - (F, helpmods) -> not is_suite(F); - (_, _) -> true end, + (F, helpmods) -> not is_suite(F) + end, Files = lists:flatmap(fun({F,out_of_date}) -> case FileTest(F, Targets) of true -> [F]; diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index df124e1ca8..2b06338479 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -139,7 +139,7 @@ do_start(Parent,Mode,LogDir) -> end end, %% start ct_config server - CTCSResult = ct_config:start(Mode, LogDir), + ct_config:start(Mode), %% add user event handlers case lists:keysearch(event_handler,1,Opts) of {value,{_,Handlers}} -> -- cgit v1.2.3 From c5275f3dc1d78ecbe1bf311b7c04c3c815a293d9 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Wed, 3 Mar 2010 10:27:38 +0100 Subject: Added config test suite for CT --- lib/common_test/test/Makefile | 3 +- lib/common_test/test/ct_config_SUITE.erl | 167 +++++++++++++++++++++ .../test/ct_config_SUITE_data/config/cfg.cfg | 11 ++ .../config/test/config_1_SUITE.erl | 135 +++++++++++++++++ .../config/test/config_2_SUITE.erl | 59 ++++++++ 5 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 lib/common_test/test/ct_config_SUITE.erl create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 35ba22aa59..2b0a06871e 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -33,7 +33,8 @@ MODULES= \ ct_groups_test_2_SUITE \ ct_skip_SUITE \ ct_error_SUITE \ - ct_test_server_if_1_SUITE + ct_test_server_if_1_SUITE \ + ct_config_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl new file mode 100644 index 0000000000..38c8cbe6eb --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -0,0 +1,167 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_config_SUITE +%%% +%%% Description: +%%% Test configuration handling in Common Test suites. +%%% +%%% The suites used for the test are located in the data directory. +%%%------------------------------------------------------------------- +-module(ct_config_SUITE). + +-compile(export_all). + +-include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +all(doc) -> + [""]; + +all(suite) -> + [ + require, + nested_keys + ]. + + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +require(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "config/test/"++S) end, + Suites = [Join(DataDir, "config_1_SUITE")], + CTConfig = {config, filename:join(DataDir, "config/cfg.cfg")}, + {Opts,ERPid} = setup({suite,Suites}, Config, CTConfig), + ok = ct_test_support:run(ct, run_test, [Opts], Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(require, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = test_events(require), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +nested_keys(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "config/test/"++S) end, + Suites = [Join(DataDir, "config_2_SUITE")], + CTConfig = {config, filename:join(DataDir, "config/cfg.cfg")}, + {Opts,ERPid} = setup({suite,Suites}, Config, CTConfig), + ok = ct_test_support:run(ct, run_test, [Opts], Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(nested_keys, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = test_events(nested_keys), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +setup(Test, Config, CTConfig) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}, CTConfig], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). +%reformat(Events, _EH) -> +% Events. + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +test_events(require) -> +[ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,8}}, + {?eh,tc_start,{config_1_SUITE,init_per_suite}}, + {?eh,tc_done,{config_1_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{config_1_SUITE,test1}}, + {?eh,tc_done,{config_1_SUITE,test1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{config_1_SUITE,test2}}, + {?eh,tc_done, + {config_1_SUITE,test2,{skipped,{config_name_already_in_use,[x1]}}}}, + {?eh,test_stats,{1,0,{1,0}}}, + {?eh,tc_start,{config_1_SUITE,test3}}, + {?eh,tc_done,{config_1_SUITE,test3,ok}}, + {?eh,test_stats,{2,0,{1,0}}}, + {?eh,tc_start,{config_1_SUITE,test4}}, + {?eh,tc_done, + {config_1_SUITE,test4,{skipped,{config_name_already_in_use,[x1,alias]}}}}, + {?eh,test_stats,{2,0,{2,0}}}, + {?eh,tc_start,{config_1_SUITE,test5}}, + {?eh,tc_done,{config_1_SUITE,test5,ok}}, + {?eh,test_stats,{3,0,{2,0}}}, + {?eh,tc_start,{config_1_SUITE,test6}}, + {?eh,tc_done,{config_1_SUITE,test6,ok}}, + {?eh,test_stats,{4,0,{2,0}}}, + {?eh,tc_start,{config_1_SUITE,test7}}, + {?eh,tc_done,{config_1_SUITE,test7,ok}}, + {?eh,test_stats,{5,0,{2,0}}}, + {?eh,tc_start,{config_1_SUITE,test8}}, + {?eh,tc_done,{config_1_SUITE,test8,ok}}, + {?eh,test_stats,{6,0,{2,0}}}, + {?eh,tc_start,{config_1_SUITE,end_per_suite}}, + {?eh,tc_done,{config_1_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} +]; + +test_events(nested_keys)-> +[]. diff --git a/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg b/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg new file mode 100644 index 0000000000..f0c1783189 --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg @@ -0,0 +1,11 @@ +{x, suite}. +{gen_cfg2, + [ + {a,x}, + {b,y} + ]}. +{gen_cfg3, + [ + {v,w}, + {m,n} + ]}. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl new file mode 100644 index 0000000000..696014ee9f --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl @@ -0,0 +1,135 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(config_1_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +suite() -> + [ + {timetrap, {seconds,10}}, + %% x1 doesn't exist in cfg-file! + {require, x1, x}, + {require, gen_cfg2}, + {require, alias, gen_cfg3}, + %% x1 default value + {x1, {x,suite}} + ]. + +is_exported(Module, Function, Arity)-> + Exports = Module:module_info(exports), + case lists:keyfind(Function, 1, Exports) of + false-> + false; + {Function, Arity}-> + true; + {Function, _OtherArity}-> + false + end. + +get_all_config()-> + case is_exported(ct_util, get_all_config, 0) of + true-> + {ct_util, ct_util:get_all_config()}; + false-> + {ct_config, ct_config:get_all_config()} + end. + +init_per_suite(Config) -> + {Module, Cfg} = get_all_config(), + ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + Config. + +end_per_suite(_) -> + ok. + +all() -> [test1,test2,test3,test4,test5,test6,test7,test8]. + +init_per_testcase(_, Config) -> + {Module, Cfg} = get_all_config(), + ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + Config. + +end_per_testcase(_, _) -> + ok. + +test1(_) -> + suite = ct:get_config(x1), + [{a,x},{b,y}] = ct:get_config(gen_cfg2), + [{v,w},{m,n}] = ct:get_config(alias), + ok. + +%% should get skipped +test2() -> + [{timetrap, {seconds,2}}, + {require, x1, x}, + {x1, {x,test2}}]. +test2(_) -> + test2 = ct:get_config(x1), + [{a,x},{b,y}] = ct:get_config(gen_cfg2), + [{v,w},{m,n}] = ct:get_config(alias), + ct:fail("Test should've been skipped, you shouldn't see this!"), + ok. + +test3() -> + [{timetrap, {seconds,3}}, + {require, y1, y}, + {y1, {y,test3}}]. +test3(_) -> + suite = ct:get_config(x1), + test3 = ct:get_config(y1), + [{a,x},{b,y}] = ct:get_config(gen_cfg2), + [{v,w},{m,n}] = ct:get_config(alias), + ok. + +%% should get skipped +test4() -> + [{require,alias,something}, + {alias,{something,else}}, + {require, x1, x}, + {x1, {x,test4}}]. +test4(_) -> + ct:fail("Test should've been skipped, you shouldn't see this!"), + ok. + +test5() -> + [{require,newalias,gen_cfg2}]. +test5(_) -> + A = [{a,x},{b,y}] = ct:get_config(newalias), + A = ct:get_config(gen_cfg2), + ok. + +test6(_) -> + undefined = ct:get_config(y1), + ok. + +test7() -> + [{require, y1, y}, + {y1, {y,test6}}]. +test7(_) -> + suite = ct:get_config(x1), + test6 = ct:get_config(y1), + ok. + +%% should get skipped +test8() -> + [{require, x}]. +test8(_) -> + ok. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl new file mode 100644 index 0000000000..25e050984a --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl @@ -0,0 +1,59 @@ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(config_2_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +suite() -> + [ + {timetrap, {seconds,10}} + ]. + +is_exported(Module, Function, Arity)-> + Exports = Module:module_info(exports), + case lists:keyfind(Function, 1, Exports) of + false-> + false; + {Function, Arity}-> + true; + {Function, _OtherArity}-> + false + end. + +get_all_config()-> + case is_exported(ct_util, get_all_config, 0) of + true-> + {ct_util, ct_util:get_all_config()}; + false-> + {ct_config, ct_config:get_all_config()} + end. + +init_per_suite(Config) -> + {Module, Cfg} = get_all_config(), + ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + Config. + +end_per_suite(_) -> + ok. + +all() -> [test1]. + +init_per_testcase(_, Config) -> + {Module, Cfg} = get_all_config(), + ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + Config. + +end_per_testcase(_, _) -> + ok. + +test1(_)-> + x = ct:get_config({gen_cfg2, a}), + ok. -- cgit v1.2.3 From 3237b512933560ea43173847c44d8d8dab93fd3a Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 4 Mar 2010 14:38:41 +0100 Subject: Allow callbacks to take any list as parameter --- lib/common_test/src/ct_config.erl | 60 +++++++++++++++++++++++++-- lib/common_test/src/ct_config_plain.erl | 13 +++++- lib/common_test/src/ct_config_xml.erl | 15 +++++-- lib/common_test/src/ct_run.erl | 72 ++++++++------------------------- 4 files changed, 95 insertions(+), 65 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index dbb0e0b152..63e44b6301 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -48,6 +48,8 @@ -export([get_ref_from_name/1, get_name_from_ref/1, get_key_from_name/1]). +-export([check_config_files/1, prepare_config_list/1]). + -include("ct_util.hrl"). -define(cryptfile, ".ct_config.crypt"). @@ -179,6 +181,8 @@ process_user_configs(Opts, Acc)-> case lists:keytake(userconfig, 1, Opts) of false-> Acc; + {value, {userconfig, {Callback, []}}, NewOpts}-> + process_user_configs(NewOpts, [{Callback, []} | Acc]); {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when is_list(File)-> process_user_configs(NewOpts, [{Callback, Files} | Acc]); @@ -202,9 +206,12 @@ get_config_file_list(Opts)-> CfgFiles. read_config_files(Opts) -> - AddCallback = fun(CallBack, Files)-> - lists:map(fun(X)-> {CallBack, X} end, Files) - end, + ct:pal("ct_config:read_config_files/1:~nOpts:~n~p", [Opts]), + AddCallback = fun(CallBack, [])-> + [{CallBack, []}]; + (CallBack, Files)-> + lists:map(fun(X)-> {CallBack, X} end, Files) + end, ConfigFiles = case lists:keyfind(config, 1, Opts) of {config, ConfigLists}-> lists:foldr(fun({Callback,Files}, Acc)-> @@ -215,10 +222,11 @@ read_config_files(Opts) -> false-> [] end, + ct:pal("ct_config:read_config_files/1:~nConfigFiles:~n~p", [ConfigFiles]), read_config_files_int(ConfigFiles, fun store_config/3). read_config_files_int([{Callback, File}|Files], FunToSave)-> - case Callback:read_config_file(File) of + case Callback:read_config(File) of {ok, Config}-> FunToSave(Config, Callback, File), read_config_files_int(Files, FunToSave); @@ -666,3 +674,47 @@ random_bytes(N) -> random_bytes_1(0, Acc) -> Acc; random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). + +check_config_files(Configs)-> + lists:keysearch(nok, 1, + lists:flatten( + lists:map(fun({Callback, Files})-> + case code:load_file(Callback) of + {module, Callback}-> + lists:map(fun(File)-> + Callback:check_parameter(File) + end, + Files); + {error, _}-> + {nok, {callback, Callback}} + end + end, + Configs))). + +prepare_user_configs([ConfigString|UserConfigs], Acc, new)-> + prepare_user_configs(UserConfigs, + [{list_to_atom(ConfigString), []}|Acc], + cur); +prepare_user_configs(["and"|UserConfigs], Acc, _)-> + prepare_user_configs(UserConfigs, Acc, new); +prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur)-> + prepare_user_configs(UserConfigs, + [{LastMod, [ConfigString|LastList]}|Acc], + cur); +prepare_user_configs([], Acc, _)-> + Acc. + +prepare_config_list(Args)-> + ConfigFiles = case lists:keysearch(ct_config, 1, Args) of + {value,{ct_config,Files}}-> + [{ct_config_plain, Files}]; + false-> + [] + end, + UserConfigs = case lists:keysearch(userconfig, 1, Args) of + {value,{userconfig,UserConfigFiles}}-> + prepare_user_configs(UserConfigFiles, [], new); + false-> + [] + end, + ConfigFiles ++ UserConfigs. diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl index be0433aa9f..327fc66b40 100755 --- a/lib/common_test/src/ct_config_plain.erl +++ b/lib/common_test/src/ct_config_plain.erl @@ -22,9 +22,9 @@ %% Created : 15 February 2010 %%---------------------------------------------------------------------- -module(ct_config_plain). --export([read_config_file/1]). +-export([read_config/1, check_parameter/1]). -read_config_file(ConfigFile) -> +read_config(ConfigFile) -> case file:consult(ConfigFile) of {ok,Config} -> {ok, Config}; @@ -65,6 +65,15 @@ read_config_file(ConfigFile) -> end end. +% check against existence of config file +check_parameter(File)-> + case filelib:is_file(File) of + true-> + {ok, {file, File}}; + false-> + {nok, {nofile, File}} + end. + read_config_terms(Bin) when is_binary(Bin) -> case catch binary_to_list(Bin) of {'EXIT',_} -> diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index 79f25cead7..4b16c7c675 100755 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -22,10 +22,10 @@ %% Created : 16 February 2010 %%---------------------------------------------------------------------- -module(ct_config_xml). --export([read_config_file/1]). +-export([read_config/1, check_parameter/1]). -% the only function to be called outside -read_config_file(ConfigFile) -> +% read config file +read_config(ConfigFile) -> case catch do_read_xml_config(ConfigFile) of {ok, Config}-> {ok, Config}; @@ -33,6 +33,15 @@ read_config_file(ConfigFile) -> {error, Error, ErroneousString} end. +% check against existence of the file +check_parameter(File)-> + case filelib:is_file(File) of + true-> + {ok, {file, File}}; + false-> + {nok, {nofile, File}} + end. + % actual reading of the config do_read_xml_config(ConfigFile)-> {ok, EntityList, _}= diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index d3239b7ce3..cf5c99ee19 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -208,19 +208,7 @@ script_start1(Parent, Args) -> end end; false -> - ConfigFiles = case lists:keysearch(ct_config, 1, Args) of - {value,{ct_config,Files}}-> - [{ct_config_plain, Files}]; - false-> - [] - end, - UserConfigs = case lists:keysearch(userconfig, 1, Args) of - {value,{userconfig,UserConfigFiles}}-> - prepare_user_configs(UserConfigFiles, [], new); - false-> - [] - end, - Config = ConfigFiles ++ UserConfigs, + Config = ct_config:prepare_config_list(Args), case Config of [] -> case install([{config,[]}, @@ -252,48 +240,15 @@ script_start1(Parent, Args) -> end, Parent ! {self(), Result}. -prepare_user_configs([ConfigString|UserConfigs], Acc, new)-> - prepare_user_configs(UserConfigs, - [{list_to_atom(ConfigString), []}|Acc], - cur); -prepare_user_configs(["and"|UserConfigs], Acc, _)-> - prepare_user_configs(UserConfigs, Acc, new); -prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur)-> - prepare_user_configs(UserConfigs, - [{LastMod, [ConfigString|LastList]}|Acc], - cur); -prepare_user_configs([], Acc, _)-> - Acc. - check_and_install_configfiles(Configs, LogDir, EvHandlers) -> - % Configs is list of tuples such as {Callback=atom(), Files=list()} - % The ugly code below checks: - % 1. that all config files are present - % 2. thar all callback modules are loadable - case lists:keysearch(nok, 1, - lists:flatten( - lists:map(fun({Callback, Files})-> - case code:load_file(Callback) of - {module, Callback}-> - lists:map(fun(File)-> - case filelib:is_file(File) of - true-> - {ok, File}; - false-> - {nok, {config, File}} - end - end, - Files); - {error, _}-> - {nok, {callback, Callback}} - end - end, - Configs))) of + case ct_config:check_config_files(Configs) of false-> install([{config,Configs}, {event_handler,EvHandlers}], LogDir); - {value, {nok, {config, File}}} -> + {value, {nok, {nofile, File}}} -> {error,{cant_read_config_file,File}}; + {value, {nok, {wrong_config, Message}}}-> + {error,{wrong_config, Message}}; {value, {nok, {callback, File}}} -> {error,{cant_load_callback_module,File}} end. @@ -740,11 +695,16 @@ run_prepared(LogDir, CfgFiles, EvHandlers, Run, Skip, Cover, Opts) -> exit(Reason) end. -check_config_file(File)-> - AbsName = ?abs(File), - case filelib:is_file(AbsName) of - true -> AbsName; - false -> exit({no_such_file,AbsName}) +check_config_file(Callback, File)-> + case Callback:check_parameter(File) of + {ok, {file, File}}-> + ?abs(File); + {ok, {config, _}}-> + File; + {nok, {wrong_config, Message}}-> + exit({wrong_config, {Callback, Message}}); + {nok, {nofile, File}}-> + exit({no_such_file, ?abs(File)}) end. run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> @@ -763,7 +723,7 @@ run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> end, {Callback, lists:map(fun(File)-> - check_config_file(File) + check_config_file(Callback, File) end, FileList)} end, CfgFiles), -- cgit v1.2.3 From 2dc962eead182ca5c79c5ac8d9d31a08f22c97af Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Fri, 5 Mar 2010 08:48:06 +0100 Subject: Fix problems with test specifications Work in progress... --- lib/common_test/src/ct_config.erl | 22 +++++++++++++++++----- lib/common_test/src/ct_testspec.erl | 23 ++++++++++++++++++++--- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 63e44b6301..8e2f71647f 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -188,9 +188,7 @@ process_user_configs(Opts, Acc)-> process_user_configs(NewOpts, [{Callback, Files} | Acc]); {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when is_integer(C)-> - process_user_configs(NewOpts, [{Callback, [File]} | Acc]); - {value, {userconfig, {_Callback, []}}, NewOpts}-> - process_user_configs(NewOpts, Acc) + process_user_configs(NewOpts, [{Callback, [File]} | Acc]) end. get_config_file_list(Opts)-> @@ -675,12 +673,26 @@ random_bytes(N) -> random_bytes_1(0, Acc) -> Acc; random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). +check_callback_load(Callback)-> + case code:is_loaded(Callback) of + {file, _Filename}-> + {ok, Callback}; + false-> + case code:load_file(Callback) of + {module, Callback}-> + {ok, Callback}; + {error, Error}-> + {error, Error} + end + end. + check_config_files(Configs)-> + ct:pal("ct_config:check_config_files(~p)", [Configs]), lists:keysearch(nok, 1, lists:flatten( lists:map(fun({Callback, Files})-> - case code:load_file(Callback) of - {module, Callback}-> + case check_callback_load(Callback) of + {ok, Callback}-> lists:map(fun(File)-> Callback:check_parameter(File) end, diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 4378ec5a52..169c614955 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -417,23 +417,40 @@ add_tests([{cover,File}|Ts],Spec) -> %% --- config --- add_tests([{config,all_nodes,Files}|Ts],Spec) -> - Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)), + io:format("1: add_tests([{config,all_nodes,~p}|~p],~p~n", [Files, Ts, Spec]), + Tests = lists:map(fun(N) -> {config,N,{ct_config_plain,Files}} end, list_nodes(Spec)), add_tests(Tests++Ts,Spec); add_tests([{config,Nodes,Files}|Ts],Spec) when is_list(Nodes) -> + io:format("2: add_tests([{config,~p,~p}|~p],~p) when is_list(Nodes)~n", [Nodes,Files,Spec,Nodes]), Ts1 = separate(Nodes,config,[Files],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); -add_tests([{config,Node,[F|Fs]}|Ts],Spec) when is_list(F) -> +add_tests([{config,Node,[{Callback,F}|Fs]}|Ts],Spec) when is_list(F) -> + io:format("3: add_tests([{config,~p,[~p|~p]}|~p],~p) when is_list(~p)~n", [Node, F, Fs, Ts, Spec, F]), Cfgs = Spec#testspec.config, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{config,Node,Fs}|Ts], - Spec#testspec{config=[{Node1,get_absfile(F,Spec)}|Cfgs]}); +% TODO FIX IT SOMEHOW! There SHOULD be absolute paths, +% but it can't be applied to the config parameters +% probably, that's a good idea to call Callback:check_parameter/1 +% and proceed according to the results +% Spec#testspec{config=[{Node1,{Callback, get_absfile(F,Spec)}}|Cfgs]}); + Spec#testspec{config=[{Node1,{Callback,[F]}}|Cfgs]}); add_tests([{config,_Node,[]}|Ts],Spec) -> + io:format("4: add_tests([{config,_,[]}|~p],~p)~n", [Ts, Spec]), add_tests(Ts,Spec); add_tests([{config,Node,F}|Ts],Spec) -> + io:format("5: add_tests([{config,~p,~p}|~p],~p)~n", [Node, F, Ts, Spec]), add_tests([{config,Node,[F]}|Ts],Spec); add_tests([{config,Files}|Ts],Spec) -> + io:format("6: add_tests([{config,~p}|~p],~p)~n", [Files, Ts, Spec]), add_tests([{config,all_nodes,Files}|Ts],Spec); +%% --- userconfig --- +add_tests([{userconfig, {Callback, Files}}|Ts], Spec)-> + io:format("add_tests([{userconfig, {~p, ~p}}|~p], ~p)~n", [Callback, Files, Ts, Spec]), + Tests = lists:map(fun(N) -> {config,N,{Callback,Files}} end, list_nodes(Spec)), + add_tests(Tests++Ts,Spec); + %% --- event_handler --- add_tests([{event_handler,all_nodes,Hs}|Ts],Spec) -> Tests = lists:map(fun(N) -> {event_handler,N,Hs,[]} end, list_nodes(Spec)), -- cgit v1.2.3 From 3c3360947692d8cf55d6dc2290934ec111675cde Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Tue, 9 Mar 2010 10:36:19 +0100 Subject: Bug fix: ct_config_xml now returns items in right order --- lib/common_test/src/ct_config_xml.erl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index 4b16c7c675..e14bb0f9f9 100755 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -24,6 +24,9 @@ -module(ct_config_xml). -export([read_config/1, check_parameter/1]). +% DEBUG ONLY +-export([list_to_term/1]). + % read config file read_config(ConfigFile) -> case catch do_read_xml_config(ConfigFile) of @@ -44,11 +47,14 @@ check_parameter(File)-> % actual reading of the config do_read_xml_config(ConfigFile)-> - {ok, EntityList, _}= - xmerl_sax_parser:file(ConfigFile, + case catch xmerl_sax_parser:file(ConfigFile, [{event_fun, fun event/3}, - {event_state, []}]), - {ok, transform_entity_list(EntityList)}. + {event_state, []}]) of + {ok, EntityList, _}-> + {ok, lists:reverse(transform_entity_list(EntityList))}; + Oops-> + {error, parsing_failed, Oops} + end. % event callback for xmerl_sax_parser event(Event, _LineNo, State) -> @@ -95,7 +101,7 @@ transform_entity_list(EntityList)-> % transform entity from {list(), list()} to {atom(), term()} transform_entity({Tag, [Value|Rest]}) when is_tuple(Value)-> - {list_to_atom(Tag), transform_entity_list([Value|Rest])}; + {list_to_atom(Tag), transform_entity_list(lists:reverse([Value|Rest]))}; transform_entity({Tag, String})-> case list_to_term(String) of {ok, Value}-> @@ -108,7 +114,7 @@ transform_entity({Tag, String})-> % stolen from trapexit.org :-) list_to_term(String) -> {ok, T, _} = erl_scan:string(String++"."), - case erl_parse:parse_term(T) of + case catch erl_parse:parse_term(T) of {ok, Term} -> {ok, Term}; Error -> -- cgit v1.2.3 From 5b66bed40bd374dfcaa5e5669adf338734d812a3 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Tue, 9 Mar 2010 15:10:22 +0100 Subject: Add test suites for configuration --- lib/common_test/src/ct_config.erl | 10 +- lib/common_test/src/ct_run.erl | 1 + lib/common_test/src/ct_testspec.erl | 18 +-- lib/common_test/src/ct_util.hrl | 1 + lib/common_test/src/vts.erl | 8 +- lib/common_test/test/ct_config_SUITE.erl | 127 ++++++++++++--------- .../test/ct_config_SUITE_data/config/cfg.cfg | 11 -- .../test/ct_config_SUITE_data/config/config.txt | 31 +++++ .../test/ct_config_SUITE_data/config/config.xml | 27 +++++ .../config/test/config_1_SUITE.erl | 100 +++++++++------- .../config/test/config_2_SUITE.erl | 88 ++++++++++++-- .../config/test/config_driver.erl | 41 +++++++ .../config/test/config_server.erl | 87 ++++++++++++++ 13 files changed, 420 insertions(+), 130 deletions(-) delete mode 100644 lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/config.txt create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/config.xml create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 8e2f71647f..bc57930381 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -198,13 +198,13 @@ get_config_file_list(Opts)-> DefaultConfigs == []-> []; true-> - [{ct_config_plain, DefaultConfigs}] + [{?ct_config_txt, DefaultConfigs}] end ++ process_user_configs(Opts, []), CfgFiles. read_config_files(Opts) -> - ct:pal("ct_config:read_config_files/1:~nOpts:~n~p", [Opts]), + %ct:pal("ct_config:read_config_files/1:~nOpts:~n~p", [Opts]), AddCallback = fun(CallBack, [])-> [{CallBack, []}]; (CallBack, Files)-> @@ -220,7 +220,7 @@ read_config_files(Opts) -> false-> [] end, - ct:pal("ct_config:read_config_files/1:~nConfigFiles:~n~p", [ConfigFiles]), + %ct:pal("ct_config:read_config_files/1:~nConfigFiles:~n~p", [ConfigFiles]), read_config_files_int(ConfigFiles, fun store_config/3). read_config_files_int([{Callback, File}|Files], FunToSave)-> @@ -687,7 +687,7 @@ check_callback_load(Callback)-> end. check_config_files(Configs)-> - ct:pal("ct_config:check_config_files(~p)", [Configs]), + ct:pal("ct_config:check_config_files/1~nConfigs:~n~p", [Configs]), lists:keysearch(nok, 1, lists:flatten( lists:map(fun({Callback, Files})-> @@ -719,7 +719,7 @@ prepare_user_configs([], Acc, _)-> prepare_config_list(Args)-> ConfigFiles = case lists:keysearch(ct_config, 1, Args) of {value,{ct_config,Files}}-> - [{ct_config_plain, Files}]; + [{?ct_config_txt, Files}]; false-> [] end, diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index cf5c99ee19..0e7da60821 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -467,6 +467,7 @@ install(Opts, LogDir) -> case whereis(ct_util_server) of undefined -> VarFile = variables_file_name(LogDir), + io:format("Varfile=~p~n", [VarFile]), case file:open(VarFile, [write]) of {ok,Fd} -> [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts], diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 169c614955..8a384a1b3a 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -416,16 +416,17 @@ add_tests([{cover,File}|Ts],Spec) -> add_tests([{cover,all_nodes,File}|Ts],Spec); %% --- config --- +% TODO finish that! add_tests([{config,all_nodes,Files}|Ts],Spec) -> - io:format("1: add_tests([{config,all_nodes,~p}|~p],~p~n", [Files, Ts, Spec]), - Tests = lists:map(fun(N) -> {config,N,{ct_config_plain,Files}} end, list_nodes(Spec)), + %io:format("1: add_tests([{config,all_nodes,~p}|~p],~p~n", [Files, Ts, Spec]), + Tests = lists:map(fun(N) -> {config,N,{?ct_config_txt,Files}} end, list_nodes(Spec)), add_tests(Tests++Ts,Spec); add_tests([{config,Nodes,Files}|Ts],Spec) when is_list(Nodes) -> - io:format("2: add_tests([{config,~p,~p}|~p],~p) when is_list(Nodes)~n", [Nodes,Files,Spec,Nodes]), + %io:format("2: add_tests([{config,~p,~p}|~p],~p) when is_list(Nodes)~n", [Nodes,Files,Spec,Nodes]), Ts1 = separate(Nodes,config,[Files],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); add_tests([{config,Node,[{Callback,F}|Fs]}|Ts],Spec) when is_list(F) -> - io:format("3: add_tests([{config,~p,[~p|~p]}|~p],~p) when is_list(~p)~n", [Node, F, Fs, Ts, Spec, F]), + %io:format("3: add_tests([{config,~p,[~p|~p]}|~p],~p) when is_list(~p)~n", [Node, F, Fs, Ts, Spec, F]), Cfgs = Spec#testspec.config, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{config,Node,Fs}|Ts], @@ -436,18 +437,19 @@ add_tests([{config,Node,[{Callback,F}|Fs]}|Ts],Spec) when is_list(F) -> % Spec#testspec{config=[{Node1,{Callback, get_absfile(F,Spec)}}|Cfgs]}); Spec#testspec{config=[{Node1,{Callback,[F]}}|Cfgs]}); add_tests([{config,_Node,[]}|Ts],Spec) -> - io:format("4: add_tests([{config,_,[]}|~p],~p)~n", [Ts, Spec]), + %io:format("4: add_tests([{config,_,[]}|~p],~p)~n", [Ts, Spec]), add_tests(Ts,Spec); add_tests([{config,Node,F}|Ts],Spec) -> - io:format("5: add_tests([{config,~p,~p}|~p],~p)~n", [Node, F, Ts, Spec]), + %io:format("5: add_tests([{config,~p,~p}|~p],~p)~n", [Node, F, Ts, Spec]), add_tests([{config,Node,[F]}|Ts],Spec); add_tests([{config,Files}|Ts],Spec) -> - io:format("6: add_tests([{config,~p}|~p],~p)~n", [Files, Ts, Spec]), + %io:format("6: add_tests([{config,~p}|~p],~p)~n", [Files, Ts, Spec]), add_tests([{config,all_nodes,Files}|Ts],Spec); +% TODO add support for {userconfig, Nodes, {Callback, Files}} %% --- userconfig --- add_tests([{userconfig, {Callback, Files}}|Ts], Spec)-> - io:format("add_tests([{userconfig, {~p, ~p}}|~p], ~p)~n", [Callback, Files, Ts, Spec]), + %io:format("add_tests([{userconfig, {~p, ~p}}|~p], ~p)~n", [Callback, Files, Ts, Spec]), Tests = lists:map(fun(N) -> {config,N,{Callback,Files}} end, list_nodes(Spec)), add_tests(Tests++Ts,Spec); diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index c1dc14f943..fff59f3fde 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -50,3 +50,4 @@ -define(CT_MEVMGR_REF, ct_master_event). -define(missing_suites_info, "missing_suites.info"). +-define(ct_config_txt, ct_config_plain). \ No newline at end of file diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index ad4845a7c3..c94a796ab8 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -161,10 +161,12 @@ init(Parent) -> loop(State) -> receive {{init_data,ConfigFiles,EvHandlers,LogDir,Tests},From} -> - ct_install(State), + ct:pal("State#state.current_log_dir=~p", [State#state.current_log_dir]), + NewState = State#state{config=ConfigFiles,event_handler=EvHandlers, + current_log_dir=LogDir,tests=Tests}, + ct_install(NewState), return(From,ok), - loop(#state{config=ConfigFiles,event_handler=EvHandlers, - current_log_dir=LogDir,tests=Tests}); + loop(NewState); {start_page,From} -> return(From,start_page1()), loop(State); diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 38c8cbe6eb..4ae68fbfa8 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -62,7 +62,8 @@ all(doc) -> all(suite) -> [ require, - nested_keys + userconfig_static, + userconfig_dynamic ]. @@ -74,41 +75,41 @@ all(suite) -> %%% require(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), - Join = fun(D, S) -> filename:join(D, "config/test/"++S) end, - Suites = [Join(DataDir, "config_1_SUITE")], - CTConfig = {config, filename:join(DataDir, "config/cfg.cfg")}, - {Opts,ERPid} = setup({suite,Suites}, Config, CTConfig), - ok = ct_test_support:run(ct, run_test, [Opts], Config), - Events = ct_test_support:get_events(ERPid, Config), - - ct_test_support:log_events(require, - reformat(Events, ?eh), - ?config(priv_dir, Config)), + run_test(require, + Config, + {config, filename:join(DataDir, "config/config.txt")}, + ["config_1_SUITE"]). - TestEvents = test_events(require), - ok = ct_test_support:verify_events(TestEvents, Events, Config). - -nested_keys(Config) when is_list(Config) -> +userconfig_static(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), - Join = fun(D, S) -> filename:join(D, "config/test/"++S) end, - Suites = [Join(DataDir, "config_2_SUITE")], - CTConfig = {config, filename:join(DataDir, "config/cfg.cfg")}, - {Opts,ERPid} = setup({suite,Suites}, Config, CTConfig), - ok = ct_test_support:run(ct, run_test, [Opts], Config), - Events = ct_test_support:get_events(ERPid, Config), + run_test(userconfig_static, + Config, + {userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}, + ["config_1_SUITE"]). - ct_test_support:log_events(nested_keys, - reformat(Events, ?eh), - ?config(priv_dir, Config)), - - TestEvents = test_events(nested_keys), - ok = ct_test_support:verify_events(TestEvents, Events, Config). +userconfig_dynamic(Config) when is_list(Config) -> + run_test(userconfig_dynamic, + Config, + {userconfig, {config_driver, "config_server"}}, + ["config_2_SUITE"]). %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- +run_test(Name, Config, CTConfig, SuiteNames)-> + DataDir = ?config(data_dir, Config), + Joiner = fun(Suite) -> filename:join(DataDir, "config/test/"++Suite) end, + Suites = lists:map(Joiner, SuiteNames), + {Opts,ERPid} = setup_env({suite,Suites}, Config, CTConfig), + ok = ct_test_support:run(ct, run_test, [Opts], Config), + TestEvents = ct_test_support:get_events(ERPid, Config), + ct_test_support:log_events(Name, + reformat_events(TestEvents, ?eh), + ?config(priv_dir, Config)), + ExpEvents = expected_events(Name), + ok = ct_test_support:verify_events(ExpEvents, TestEvents, Config). -setup(Test, Config, CTConfig) -> +setup_env(Test, Config, CTConfig) -> Opts0 = ct_test_support:get_opts(Config), Level = ?config(trace_level, Config), EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], @@ -116,46 +117,45 @@ setup(Test, Config, CTConfig) -> ERPid = ct_test_support:start_event_receiver(Config), {Opts,ERPid}. -reformat(Events, EH) -> +reformat_events(Events, EH) -> ct_test_support:reformat(Events, EH). -%reformat(Events, _EH) -> -% Events. %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- -test_events(require) -> +expected_events(ReqOrUCS) when ReqOrUCS==require; ReqOrUCS==userconfig_static-> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, {?eh,start_info,{1,1,8}}, {?eh,tc_start,{config_1_SUITE,init_per_suite}}, {?eh,tc_done,{config_1_SUITE,init_per_suite,ok}}, - {?eh,tc_start,{config_1_SUITE,test1}}, - {?eh,tc_done,{config_1_SUITE,test1,ok}}, + {?eh,tc_start,{config_1_SUITE,test_get_config_simple}}, + {?eh,tc_done,{config_1_SUITE,test_get_config_simple,ok}}, {?eh,test_stats,{1,0,{0,0}}}, - {?eh,tc_start,{config_1_SUITE,test2}}, + {?eh,tc_start,{config_1_SUITE,test_get_config_nested}}, + {?eh,tc_done,{config_1_SUITE,test_get_config_nested,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + {?eh,tc_start,{config_1_SUITE,test_default_suitewide}}, + {?eh,tc_done,{config_1_SUITE,test_default_suitewide,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{config_1_SUITE,test_config_name_already_in_use1}}, {?eh,tc_done, - {config_1_SUITE,test2,{skipped,{config_name_already_in_use,[x1]}}}}, - {?eh,test_stats,{1,0,{1,0}}}, - {?eh,tc_start,{config_1_SUITE,test3}}, - {?eh,tc_done,{config_1_SUITE,test3,ok}}, - {?eh,test_stats,{2,0,{1,0}}}, - {?eh,tc_start,{config_1_SUITE,test4}}, + {config_1_SUITE,test_config_name_already_in_use1,{skipped,{config_name_already_in_use,[x1]}}}}, + {?eh,test_stats,{3,0,{1,0}}}, + {?eh,tc_start,{config_1_SUITE,test_default_tclocal}}, + {?eh,tc_done,{config_1_SUITE,test_default_tclocal,ok}}, + {?eh,test_stats,{4,0,{1,0}}}, + {?eh,tc_start,{config_1_SUITE,test_config_name_already_in_use2}}, {?eh,tc_done, - {config_1_SUITE,test4,{skipped,{config_name_already_in_use,[x1,alias]}}}}, - {?eh,test_stats,{2,0,{2,0}}}, - {?eh,tc_start,{config_1_SUITE,test5}}, - {?eh,tc_done,{config_1_SUITE,test5,ok}}, - {?eh,test_stats,{3,0,{2,0}}}, - {?eh,tc_start,{config_1_SUITE,test6}}, - {?eh,tc_done,{config_1_SUITE,test6,ok}}, + {config_1_SUITE,test_config_name_already_in_use2, + {skipped,{config_name_already_in_use,[x1,alias]}}}}, {?eh,test_stats,{4,0,{2,0}}}, - {?eh,tc_start,{config_1_SUITE,test7}}, - {?eh,tc_done,{config_1_SUITE,test7,ok}}, + {?eh,tc_start,{config_1_SUITE,test_alias_tclocal}}, + {?eh,tc_done,{config_1_SUITE,test_alias_tclocal,ok}}, {?eh,test_stats,{5,0,{2,0}}}, - {?eh,tc_start,{config_1_SUITE,test8}}, - {?eh,tc_done,{config_1_SUITE,test8,ok}}, + {?eh,tc_start,{config_1_SUITE,test_get_config_undefined}}, + {?eh,tc_done,{config_1_SUITE,test_get_config_undefined,ok}}, {?eh,test_stats,{6,0,{2,0}}}, {?eh,tc_start,{config_1_SUITE,end_per_suite}}, {?eh,tc_done,{config_1_SUITE,end_per_suite,ok}}, @@ -163,5 +163,24 @@ test_events(require) -> {?eh,stop_logging,[]} ]; -test_events(nested_keys)-> -[]. +expected_events(userconfig_dynamic)-> +[ + {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}}, + {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {ct_test_support_eh,start_info,{1,1,3}}, + {ct_test_support_eh,tc_start,{config_2_SUITE,init_per_suite}}, + {ct_test_support_eh,tc_done,{config_2_SUITE,init_per_suite,ok}}, + {ct_test_support_eh,tc_start,{config_2_SUITE,test_get_known_variable}}, + {ct_test_support_eh,tc_done,{config_2_SUITE,test_get_known_variable,ok}}, + {ct_test_support_eh,test_stats,{1,0,{0,0}}}, + {ct_test_support_eh,tc_start,{config_2_SUITE,test_localtime_update}}, + {ct_test_support_eh,tc_done,{config_2_SUITE,test_localtime_update,ok}}, + {ct_test_support_eh,test_stats,{2,0,{0,0}}}, + {ct_test_support_eh,tc_start,{config_2_SUITE,test_server_pid}}, + {ct_test_support_eh,tc_done,{config_2_SUITE,test_server_pid,ok}}, + {ct_test_support_eh,test_stats,{3,0,{0,0}}}, + {ct_test_support_eh,tc_start,{config_2_SUITE,end_per_suite}}, + {ct_test_support_eh,tc_done,{config_2_SUITE,end_per_suite,ok}}, + {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}}, + {ct_test_support_eh,stop_logging,[]} +]. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg b/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg deleted file mode 100644 index f0c1783189..0000000000 --- a/lib/common_test/test/ct_config_SUITE_data/config/cfg.cfg +++ /dev/null @@ -1,11 +0,0 @@ -{x, suite}. -{gen_cfg2, - [ - {a,x}, - {b,y} - ]}. -{gen_cfg3, - [ - {v,w}, - {m,n} - ]}. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.txt b/lib/common_test/test/ct_config_SUITE_data/config/config.txt new file mode 100644 index 0000000000..0424dbf92e --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/config.txt @@ -0,0 +1,31 @@ +{x, suite}. +{gen_cfg, + [ + {a,a_value}, + {b,b_value} + ]}. +{gen_cfg2, + [ + {c, "Hello, world!"}, + {d, atom_value}, + {e, {tuple,1,"third value",[]}}, + {f, []}, + {g, [1,atom,"string",13.6,{1,2,3}]} + ]}. +{gen_cfg3, + [ + {h, + [ + {i, third1}, + {j, "Third2"}, + {k, 'THIRD3'} + ]}, + {l, + [ + {m, + [ + {n, "N"}, + {o, 'O'} + ]} + ]} + ]}. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.xml b/lib/common_test/test/ct_config_SUITE_data/config/config.xml new file mode 100644 index 0000000000..0a3e5f2e31 --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/config.xml @@ -0,0 +1,27 @@ + + suite + + a_value + b_value + + + "Hello, world!" + atom_value + {tuple,1,"third value",[]} + [] + [1,atom,"string",13.6,{1,2,3}] + + + + third1 + "Third2" + 'THIRD3' + + + + "N" + 'O' + + + + diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl index 696014ee9f..e102c69d3d 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl @@ -16,23 +16,44 @@ %% %% %CopyrightEnd% %% + +%%%------------------------------------------------------------------- +%%% File: config_1_SUITE +%%% +%%% Description: +%%% Test suite for common_test which tests the get_config and require +%%% functionality +%%%------------------------------------------------------------------- -module(config_1_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). +% The config contains variables: +% x - atom +% gen_cfg - list with two key-values tagged with a and b +% gen_cfg2 - list of five key-values tagged with c, d, e, f and g +% gen_cfg3 - list of two complex key-values taggen with: +% h: three elements inside - i, j and k +% l: m inside, contains n and o + suite() -> [ {timetrap, {seconds,10}}, %% x1 doesn't exist in cfg-file! {require, x1, x}, - {require, gen_cfg2}, - {require, alias, gen_cfg3}, + {require, gen_cfg3}, + {require, alias, gen_cfg}, %% x1 default value {x1, {x,suite}} ]. +% to get it running on development branch (without userconfig features) +% function to print full config is in the ct_util, for me it's moved to ct_config +% two following functions are only for the design period +% after merging of userconfigs to the main branch ct_config:get_all_config/0 +% should be called instead is_exported(Module, Function, Arity)-> Exports = Module:module_info(exports), case lists:keyfind(Function, 1, Exports) of @@ -53,83 +74,78 @@ get_all_config()-> end. init_per_suite(Config) -> - {Module, Cfg} = get_all_config(), - ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + %{Module, Cfg} = get_all_config(), + %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_suite(_) -> ok. -all() -> [test1,test2,test3,test4,test5,test6,test7,test8]. +all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide, + test_config_name_already_in_use1, test_default_tclocal, + test_config_name_already_in_use2, test_alias_tclocal, + test_get_config_undefined]. init_per_testcase(_, Config) -> - {Module, Cfg} = get_all_config(), - ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + %{Module, Cfg} = get_all_config(), + %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_testcase(_, _) -> ok. -test1(_) -> +%% test getting a simple value +test_get_config_simple(_)-> + suite = ct:get_config(x), + ok. + +%% test getting a nested value +test_get_config_nested(_)-> + a_value = ct:get_config({gen_cfg, a}), + ok. + +%% test suite-wide default value +test_default_suitewide(_)-> suite = ct:get_config(x1), - [{a,x},{b,y}] = ct:get_config(gen_cfg2), - [{v,w},{m,n}] = ct:get_config(alias), ok. %% should get skipped -test2() -> +test_config_name_already_in_use1() -> [{timetrap, {seconds,2}}, {require, x1, x}, {x1, {x,test2}}]. -test2(_) -> - test2 = ct:get_config(x1), - [{a,x},{b,y}] = ct:get_config(gen_cfg2), - [{v,w},{m,n}] = ct:get_config(alias), +test_config_name_already_in_use1(_) -> ct:fail("Test should've been skipped, you shouldn't see this!"), ok. -test3() -> +%% test defaults in a testcase +test_default_tclocal() -> [{timetrap, {seconds,3}}, {require, y1, y}, {y1, {y,test3}}]. -test3(_) -> - suite = ct:get_config(x1), +test_default_tclocal(_) -> test3 = ct:get_config(y1), - [{a,x},{b,y}] = ct:get_config(gen_cfg2), - [{v,w},{m,n}] = ct:get_config(alias), ok. %% should get skipped -test4() -> +test_config_name_already_in_use2() -> [{require,alias,something}, {alias,{something,else}}, {require, x1, x}, {x1, {x,test4}}]. -test4(_) -> +test_config_name_already_in_use2(_) -> ct:fail("Test should've been skipped, you shouldn't see this!"), ok. -test5() -> - [{require,newalias,gen_cfg2}]. -test5(_) -> - A = [{a,x},{b,y}] = ct:get_config(newalias), - A = ct:get_config(gen_cfg2), +%% test aliases +test_alias_tclocal() -> + [{require,newalias,gen_cfg}]. +test_alias_tclocal(_) -> + A = [{a,a_value},{b,b_value}] = ct:get_config(newalias), + A = ct:get_config(gen_cfg), ok. -test6(_) -> +%% test for getting undefined variables +test_get_config_undefined(_) -> undefined = ct:get_config(y1), ok. - -test7() -> - [{require, y1, y}, - {y1, {y,test6}}]. -test7(_) -> - suite = ct:get_config(x1), - test6 = ct:get_config(y1), - ok. - -%% should get skipped -test8() -> - [{require, x}]. -test8(_) -> - ok. \ No newline at end of file diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl index 25e050984a..86fd300a77 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl @@ -1,4 +1,14 @@ %% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations @@ -6,17 +16,39 @@ %% %% %CopyrightEnd% %% + +%%%------------------------------------------------------------------- +%%% File: ct_config_SUITE +%%% +%%% Description: +%%% Test suite for common_test which tests the userconfig functionality +%%%------------------------------------------------------------------- -module(config_2_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). +%% This suite will be run with dynamic userconfig +%% test_driver.erl is compliant to ct_config_* callback +%% and test_server is simple server for getting runtime-changing data +%% which will return the list with the following variables: +%% localtime = the erlang:localtime() result in list [{date, Date}, {time, Time}] +%% node = erlang:node() - can be compared in the testcase +%% now = erlang:now() - easier to compare than localtime() +%% config_server_pid - pid of the config server, should NOT change! +%% config_server_vsn - .19 + suite() -> [ {timetrap, {seconds,10}} ]. +% to get it running on development branch (without userconfig features) +% function to print full config is in the ct_util, for me it's moved to ct_config +% two following functions are only for the design period +% after merging of userconfigs to the main branch ct_config:get_all_config/0 +% should be called instead is_exported(Module, Function, Arity)-> Exports = Module:module_info(exports), case lists:keyfind(Function, 1, Exports) of @@ -37,23 +69,65 @@ get_all_config()-> end. init_per_suite(Config) -> - {Module, Cfg} = get_all_config(), - ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + %{Module, Cfg} = get_all_config(), + %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_suite(_) -> ok. -all() -> [test1]. +all() -> [test_get_known_variable, test_localtime_update, + test_server_pid]. init_per_testcase(_, Config) -> - {Module, Cfg} = get_all_config(), - ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), + %{Module, Cfg} = get_all_config(), + %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_testcase(_, _) -> ok. -test1(_)-> - x = ct:get_config({gen_cfg2, a}), +% test that usual config works +test_get_known_variable(_)-> + Node = erlang:node(), + 0.19 = ct:get_config(config_server_vsn), + Node = ct:get_config(node), + ok. + +% localtime will be updated in 5 seconds, check that +test_localtime_update(_)-> + Seconds = 5, + LT1 = ct:get_config(localtime), + timer:sleep(Seconds*1000), + LT2 = ct:reload_config(localtime), + case is_diff_ok(LT1, LT2, Seconds) of + {false, Actual, Exp}-> + ct:fail(io_lib:format("Time difference ~p is not ok, expected ~p", [Actual, Exp])); + true-> + ok + end. + +% server pid should not change +test_server_pid()-> + [{require, cfvsn, config_server_vsn}]. +test_server_pid(_)-> + Pid = ct:get_config(config_server_pid), + Pid = ct:reload_config(config_server_pid), + Vsn = ct:get_config(config_server_vsn), + % aliases remain after config reloading + Vsn = ct:get_config(cfvsn), ok. + +my_dt_to_datetime([{date, D},{time, T}])-> + {D, T}. + +is_diff_ok(DT1, DT2, Seconds)-> + GS1 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT1)), + GS2 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT2)), + if + GS2-GS1 > Seconds+Seconds/2; + GS2-GS1 < Seconds-Seconds/2-> + {false, GS2-GS1, Seconds}; + true-> + true + end. diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl new file mode 100644 index 0000000000..37572500c7 --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl @@ -0,0 +1,41 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_config_SUITE +%%% +%%% Description: +%%% Config driver used in the CT's tests (config_2_SUITE) +%%%------------------------------------------------------------------- +-module(config_driver). +-export([read_config/1, check_parameter/1]). + +read_config(ServerName)-> + ServerModule = list_to_atom(ServerName), + ServerModule:start(), + ServerModule:get_config(). + +check_parameter(ServerName)-> + ServerModule = list_to_atom(ServerName), + case code:load_file(ServerModule) of + {module, ServerModule}-> + {ok, {config, ServerName}}; + {error, nofile}-> + {nok, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} + end. diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl new file mode 100644 index 0000000000..0ee0bcfc22 --- /dev/null +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl @@ -0,0 +1,87 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_config_SUITE +%%% +%%% Description: +%%% Config server used in the CT's tests (config_2_SUITE) +%%%------------------------------------------------------------------- +-module(config_server). +-export([start/0, stop/0, init/1, get_config/0, loop/0]). + +-define(REGISTERED_NAME, ct_test_config_server). +-define(vsn, 0.19). + +start()-> + case whereis(?REGISTERED_NAME) of + undefined-> + spawn(?MODULE, init, [?REGISTERED_NAME]), + wait(); + _Pid-> + ok + end, + ?REGISTERED_NAME. + +init(Name)-> + register(Name, self()), + loop(). + +get_config()-> + call(self(), get_config). + +stop()-> + call(self(), stop). + +call(Client, Request)-> + case whereis(?REGISTERED_NAME) of + undefined-> + {error, not_started, Request}; + Pid-> + Pid ! {Client, Request}, + receive + Reply-> + {ok, Reply} + after 4000-> + {error, timeout, Request} + end + end. + +loop()-> + receive + {Pid, stop}-> + Pid ! ok; + {Pid, get_config}-> + {D,T} = erlang:localtime(), + Pid ! + [{localtime, [{date, D}, {time, T}]}, + {node, erlang:node()}, + {now, erlang:now()}, + {config_server_pid, self()}, + {config_server_vsn, ?vsn}], + ?MODULE:loop() + end. + +wait()-> + case whereis(?REGISTERED_NAME) of + undefined-> + wait(); + _Pid-> + ok + end. -- cgit v1.2.3 From e04b9be7167841c7eaefcfb7eee5b4bc2eb3a943 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 11 Mar 2010 14:21:51 +0100 Subject: Add tests for test specifications --- lib/common_test/src/ct_config.erl | 3 -- lib/common_test/src/ct_testspec.erl | 29 +++++++++--- lib/common_test/test/ct_config_SUITE.erl | 54 ++++++++++++++++++++-- .../config/test/config_driver.erl | 13 ++++-- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index bc57930381..4b99d8db88 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -204,7 +204,6 @@ get_config_file_list(Opts)-> CfgFiles. read_config_files(Opts) -> - %ct:pal("ct_config:read_config_files/1:~nOpts:~n~p", [Opts]), AddCallback = fun(CallBack, [])-> [{CallBack, []}]; (CallBack, Files)-> @@ -220,7 +219,6 @@ read_config_files(Opts) -> false-> [] end, - %ct:pal("ct_config:read_config_files/1:~nConfigFiles:~n~p", [ConfigFiles]), read_config_files_int(ConfigFiles, fun store_config/3). read_config_files_int([{Callback, File}|Files], FunToSave)-> @@ -687,7 +685,6 @@ check_callback_load(Callback)-> end. check_config_files(Configs)-> - ct:pal("ct_config:check_config_files/1~nConfigs:~n~p", [Configs]), lists:keysearch(nok, 1, lists:flatten( lists:map(fun({Callback, Files})-> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 8a384a1b3a..ea30ccc13b 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -305,6 +305,28 @@ get_global([{node,Ref,Node}|Ts],Spec=#testspec{nodes=Refs}) -> get_global([_|Ts],Spec) -> get_global(Ts,Spec); get_global([],Spec) -> Spec. +% TODO probably we can terminate here, if any problem with the filename +% anyway, later ct_run will do it for us :-) +get_absfile(Callback, FullName,#testspec{spec_dir=SpecDir}) -> + % we need to temporary switch to new cwd here, because + % otherwise config files cannot be found + {ok, OldWd} = file:get_cwd(), + ok = file:set_cwd(SpecDir), + R = Callback:check_parameter(FullName), + ok = file:set_cwd(OldWd), + case R of + {ok, {file, FullName}}-> + File = filename:basename(FullName), + Dir = get_absname(filename:dirname(FullName),SpecDir), + filename:join(Dir,File); + {ok, {config, FullName}}-> + FullName; + {nok, {nofile, FullName}}-> + FullName; + {nok, {wrong_config, FullName}}-> + FullName + end. + get_absfile(FullName,#testspec{spec_dir=SpecDir}) -> File = filename:basename(FullName), Dir = get_absname(filename:dirname(FullName),SpecDir), @@ -430,12 +452,7 @@ add_tests([{config,Node,[{Callback,F}|Fs]}|Ts],Spec) when is_list(F) -> Cfgs = Spec#testspec.config, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{config,Node,Fs}|Ts], -% TODO FIX IT SOMEHOW! There SHOULD be absolute paths, -% but it can't be applied to the config parameters -% probably, that's a good idea to call Callback:check_parameter/1 -% and proceed according to the results -% Spec#testspec{config=[{Node1,{Callback, get_absfile(F,Spec)}}|Cfgs]}); - Spec#testspec{config=[{Node1,{Callback,[F]}}|Cfgs]}); + Spec#testspec{config=[{Node1,{Callback, [get_absfile(Callback, F,Spec)]}}|Cfgs]}); add_tests([{config,_Node,[]}|Ts],Spec) -> %io:format("4: add_tests([{config,_,[]}|~p],~p)~n", [Ts, Spec]), add_tests(Ts,Spec); diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 4ae68fbfa8..ad7c521946 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -63,7 +63,10 @@ all(suite) -> [ require, userconfig_static, - userconfig_dynamic + userconfig_dynamic, + testspec_legacy, + testspec_static, + testspec_dynamic ]. @@ -93,9 +96,52 @@ userconfig_dynamic(Config) when is_list(Config) -> {userconfig, {config_driver, "config_server"}}, ["config_2_SUITE"]). +testspec_legacy(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + make_spec(DataDir, + "config/spec_legacy.spec", + [config_1_SUITE], + [{config, filename:join(DataDir, "config/config.txt")}]), + run_test(testspec_legacy, + Config, + {spec, filename:join(DataDir, "config/spec_legacy.spec")}, + []), + file:delete(filename:join(DataDir, "config/spec_legacy.spec")). + +testspec_static(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + make_spec(DataDir, + "config/spec_static.spec", + [config_1_SUITE], + [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]), + run_test(testspec_static, + Config, + {spec, filename:join(DataDir, "config/spec_static.spec")}, + []), + file:delete(filename:join(DataDir, "config/spec_static.spec")). + +testspec_dynamic(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + make_spec(DataDir, "config/spec_dynamic.spec", + [config_2_SUITE], + [{userconfig, {config_driver, "config_server"}}]), + run_test(testspec_dynamic, + Config, + {spec, filename:join(DataDir, "config/spec_dynamic.spec")}, + []), + file:delete(filename:join(DataDir, "config/spec_dynamic.spec")). + %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- +% {suites, "ct_config_SUITE_data/config/test", config_2_SUITE}. +make_spec(DataDir, Filename, Suites, Config)-> + {ok, Fd} = file:open(filename:join(DataDir, Filename), [write]), + ok = file:write(Fd, + io_lib:format("{suites, \"~sconfig/test/\", ~p}.~n", [DataDir, Suites])), + lists:foreach(fun(C)-> ok=file:write(Fd, io_lib:format("~p.~n", [C])) end, Config), + ok = file:close(Fd). + run_test(Name, Config, CTConfig, SuiteNames)-> DataDir = ?config(data_dir, Config), Joiner = fun(Suite) -> filename:join(DataDir, "config/test/"++Suite) end, @@ -123,7 +169,9 @@ reformat_events(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- -expected_events(ReqOrUCS) when ReqOrUCS==require; ReqOrUCS==userconfig_static-> +expected_events(Static) when + Static == require; Static == testspec_legacy; + Static == userconfig_static; Static == testspec_static-> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, @@ -163,7 +211,7 @@ expected_events(ReqOrUCS) when ReqOrUCS==require; ReqOrUCS==userconfig_static-> {?eh,stop_logging,[]} ]; -expected_events(userconfig_dynamic)-> +expected_events(Dynamic) when Dynamic == testspec_dynamic; Dynamic == userconfig_dynamic-> [ {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}}, {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl index 37572500c7..670639f7c7 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl @@ -33,9 +33,14 @@ read_config(ServerName)-> check_parameter(ServerName)-> ServerModule = list_to_atom(ServerName), - case code:load_file(ServerModule) of - {module, ServerModule}-> + case code:is_loaded(ServerModule) of + {file, _}-> {ok, {config, ServerName}}; - {error, nofile}-> - {nok, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} + false-> + case code:load_file(ServerModule) of + {module, ServerModule}-> + {ok, {config, ServerName}}; + {error, nofile}-> + {nok, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} + end end. -- cgit v1.2.3 From 66aac8ed29c5cb6ecf06491938d90b27100e6919 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Fri, 12 Mar 2010 11:55:20 +0100 Subject: Remove non-breaking space Some editors get confused by illegal break character. --- lib/test_server/src/test_server_ctrl.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 4cb5863955..5c8ef0b703 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -1741,7 +1741,7 @@ start_log_file() -> ok. make_html_link(LinkName, Target, Explanation) -> - %% if possible use a relative reference to Target. + %% if possible use a relative reference to Target. TargetL = filename:split(Target), PwdL = filename:split(filename:dirname(LinkName)), Href = case lists:prefix(PwdL, TargetL) of -- cgit v1.2.3 From 6d2e8af337b046dcb4d569c18af3bfb597d7c0bf Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Fri, 12 Mar 2010 11:55:36 +0100 Subject: Remove dialyzer warning --- lib/test_server/src/ts_erl_config.erl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/test_server/src/ts_erl_config.erl b/lib/test_server/src/ts_erl_config.erl index 5cdbf0fbb8..14c36a2c35 100644 --- a/lib/test_server/src/ts_erl_config.erl +++ b/lib/test_server/src/ts_erl_config.erl @@ -70,18 +70,18 @@ dl_vars(Vars, _) -> ShlibRules = ts_lib:subst(ShlibRules0, Vars), [{'SHLIB_RULES', ShlibRules}|Vars]. -erts_lib_name(multi_threaded, win32) -> +erts_lib_name(multi_threaded, {win32, V}) -> link_library("erts_MD" ++ case is_debug_build() of true -> "d"; false -> "" end, - win32); -erts_lib_name(single_threaded, win32) -> + {win32, V}); +erts_lib_name(single_threaded, {win32, V}) -> link_library("erts_ML" ++ case is_debug_build() of true -> "d"; false -> "" end, - win32); + {win32, V}); erts_lib_name(multi_threaded, OsType) -> link_library("erts_r", OsType); erts_lib_name(single_threaded, OsType) -> @@ -349,10 +349,7 @@ sock_libraries({unix, _}) -> sock_libraries(vxworks) -> ""; sock_libraries(ose) -> - ""; -sock_libraries(_Other) -> - exit({sock_libraries, not_supported}). - + "". link_library(LibName,{win32, _}) -> LibName ++ ".lib"; -- cgit v1.2.3 From 9ad66edc389b04141443bc5339c9f34dfeac59f0 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Fri, 12 Mar 2010 11:55:55 +0100 Subject: Support running cover on common_test test suites --- lib/common_test/test/ct_test_support.erl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 6148e3280e..4aa750c37f 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -55,6 +55,12 @@ init_per_suite(Config, Level) -> test_server:fail(Reason); {ok,CTNode} -> test_server:format(0, "Node ~p started~n", [CTNode]), + IsCover = test_server:is_cover(), + if IsCover -> + cover:start(CTNode); + true-> + ok + end, DataDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -87,6 +93,7 @@ end_per_suite(Config) -> CTNode = ?config(ct_node, Config), PrivDir = ?config(priv_dir, Config), true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]), + cover:stop(CTNode), slave:stop(CTNode), ok. -- cgit v1.2.3 From a9c315929d96c7d0d0e5e79f8fc0ba0c7f17e94d Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 15 Mar 2010 15:26:02 +0100 Subject: Changed return value tags for config file handling Return value tags modified and various documentation updates made (work in progress). --- lib/common_test/doc/src/config_file_chapter.xml | 241 ++++++++++++++++++++- lib/common_test/doc/src/run_test_chapter.xml | 3 + lib/common_test/src/ct_config.erl | 4 +- lib/common_test/src/ct_config_plain.erl | 2 +- lib/common_test/src/ct_config_xml.erl | 2 +- lib/common_test/src/ct_run.erl | 10 +- lib/common_test/src/ct_testspec.erl | 4 +- .../config/test/config_driver.erl | 2 +- lib/common_test/vsn.mk | 2 +- 9 files changed, 246 insertions(+), 24 deletions(-) diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml index a22a5270c1..ef07ef6154 100644 --- a/lib/common_test/doc/src/config_file_chapter.xml +++ b/lib/common_test/doc/src/config_file_chapter.xml @@ -4,7 +4,7 @@
- 20042009 + 20042010 Ericsson AB. All Rights Reserved. @@ -180,7 +180,123 @@
- Examples + Using own configuration data formats + +

The nature of the configuration variables can be not only plain text + files with the key-value tuples, but they can be loaded from the files in + various formats, fetched via http from the Web, or can be loaded with help + of some driver process. For this purpose, mechanism of plugging in user + configuration handling callback modules is implemented in the Common Test.

+ +
+ Standard callback modules for loading configuration variables +

Two callback modules for handling of configuration files are provided + with the Common Test application:

+ + + ct_config_plain - for reading configuration files with + key-value tuples (traditional format). This handler will be tried to + parse configuration file, if no user callback is specified. + + + ct_config_xml - for reading configuration data from the XML + files. + + +
+ +
+ Using XML configuration files + +

This is an example of the XML configuration file:

+ +
+      
+    
+        "targethost"
+        "tester"
+        "letmein"
+    
+    "/test/loadmodules"
+]]>
+      
+ +

This configuration file, once read, will produce the same configuration + variables as the following "traditional" file:

+
+{ftp_host, [{ftp,"targethost"},
+            {username,"tester"},
+            {password,"letmein"}]}.
+
+{lm_directory, "/test/loadmodules"}.
+      
+
+ +
+ Implementing of the own handler + +

Own handler can be written to handle special configuration file formats. + The parameter can be either file name or configuration string (empty list + is valid).

+

The callback module is completely responsible for the + configuration string correctness checks during runtime.

+ +

Callback module should have the following functions exported:

+

One for checking configuration parameter

+ +

Callback:check_parameter/1

+

Input value will be passed from the Common Test, as defined in the test + specification or given as an option to the run_test.

+

Return value should be any of the following value indicating if given + configuration parameter is valid:

+ + + {ok, {file, FileName}} - parameter is a file name and + file exists; + + + {ok, {config, ConfigString}} - parameter is a config string + and it is correct; + + + {error, {nofile, FileName}} - there is no file with the given + name in the current directory; + + + {error, {wrong_config, ConfigString}} - configuration string + is wrong. + + + +

And second function performs reading of the configuration variables:

+

Callback:read_config/1

+

Input value is the same as for check_parameter/1 function

+

Return value should be either:

+ + + + {ok, Config} - is configuration variables read successfully; + + + {error, Error, ErrorDetails} - if callback module failed to + proceed. + + +

Above, the Config variable is the key-value list, possibly nested, + e.g. for the configuration files above it will be the following

+ +
+        [{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
+        {lm_directory, "/test/loadmodules"}]
+      
+ +
+ +
+ +
+ Examples of the configuration files

A config file for using the FTP client to access files on a remote host could look like this:

@@ -188,28 +304,33 @@
     {ftp_host, [{ftp,"targethost"},
                 {username,"tester"},
-	        {password,"letmein"}]}.
+                {password,"letmein"}]}.
 
     {lm_directory, "/test/loadmodules"}.
+ +

XML version shown in chapter above can also be used, but it should be + explicitly specified that ct_config_xml callback module is to be + used.

+

Example of how to assert that the configuration data is available and use it for an FTP session:

     init_per_testcase(ftptest, Config) ->
         {ok,_} = ct_ftp:open(ftp),
-	Config.
+        Config.
 
     end_per_testcase(ftptest, _Config) ->
         ct_ftp:close(ftp).
 
     ftptest() ->
         [{require,ftp,ftp_host},
-	 {require,lm_directory}].
+         {require,lm_directory}].
 
     ftptest(Config) ->
-	Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
+        Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
         Local = filename:join(?config(priv_dir,Config), "loadmodule"),
         ok = ct_ftp:recv(ftp, Remote, Local),
-	...
+ ...

An example of how the above functions could be rewritten if necessary to open multiple connections to the FTP server:

@@ -217,7 +338,7 @@ init_per_testcase(ftptest, Config) -> {ok,Handle1} = ct_ftp:open(ftp_host), {ok,Handle2} = ct_ftp:open(ftp_host), - [{ftp_handles,[Handle1,Handle2]} | Config]. + [{ftp_handles,[Handle1,Handle2]} | Config]. end_per_testcase(ftptest, Config) -> lists:foreach(fun(Handle) -> ct_ftp:close(Handle) end, @@ -225,17 +346,115 @@ ftptest() -> [{require,ftp_host}, - {require,lm_directory}]. + {require,lm_directory}]. ftptest(Config) -> - Remote = filename:join(ct:get_config(lm_directory), "loadmodX"), + Remote = filename:join(ct:get_config(lm_directory), "loadmodX"), Local = filename:join(?config(priv_dir,Config), "loadmodule"), [Handle | MoreHandles] = ?config(ftp_handles,Config), ok = ct_ftp:recv(Handle, Remote, Local), - ... + ...
+
+ Example of own configuration handler +

Simple configuration hanling "driver" can be implemented this way:

+
+-module(config_driver).
+-export([read_config/1, check_parameter/1]).
+
+read_config(ServerName)->
+    ServerModule = list_to_atom(ServerName),
+    ServerModule:start(),
+    ServerModule:get_config().
+
+check_parameter(ServerName)->
+    ServerModule = list_to_atom(ServerName),
+    case code:is_loaded(ServerModule) of
+        {file, _}->
+            {ok, {config, ServerName}};
+        false->
+            case code:load_file(ServerModule) of
+                {module, ServerModule}->
+                    {ok, {config, ServerName}};
+                {error, nofile}->
+                    {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}}
+            end
+    end.
+    
+

Configuration string for this driver may be "config_server", if the + config_server.erl module below is built and is exist in the code path:

+
+-module(config_server).
+-export([start/0, stop/0, init/1, get_config/0, loop/0]).
+
+-define(REGISTERED_NAME, ct_test_config_server).
+-define(vsn, 0.1).
+
+start()->
+    case whereis(?REGISTERED_NAME) of
+        undefined->
+            spawn(?MODULE, init, [?REGISTERED_NAME]),
+            wait();
+        _Pid->
+        ok
+    end,
+    ?REGISTERED_NAME.
+
+init(Name)->
+    register(Name, self()),
+    loop().
+
+get_config()->
+    call(self(), get_config).
+
+stop()->
+    call(self(), stop).
+
+call(Client, Request)->
+    case whereis(?REGISTERED_NAME) of
+        undefined->
+            {error, not_started, Request};
+        Pid->
+            Pid ! {Client, Request},
+            receive
+                Reply->
+                    {ok, Reply}
+            after 4000->
+                {error, timeout, Request}
+            end
+    end.
+
+loop()->
+    receive
+        {Pid, stop}->
+            Pid ! ok;
+        {Pid, get_config}->
+            {D,T} = erlang:localtime(),
+            Pid !
+                [{localtime, [{date, D}, {time, T}]},
+                 {node, erlang:node()},
+                 {now, erlang:now()},
+                 {config_server_pid, self()},
+                 {config_server_vsn, ?vsn}],
+            ?MODULE:loop()
+    end.
+
+wait()->
+    case whereis(?REGISTERED_NAME) of
+        undefined->
+            wait();
+        _Pid->
+            ok
+    end.
+    
+

There two modules provide the ability to dynamically reload configuration + variables. If the ct:reload_config(localtime) is called from the test + case, all variables which were loaded with the config_driver above, will be + updated with the new values.

+
+ diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index d731d18783..b7e9ab88e3 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -111,11 +111,14 @@ -dir ]]> -suite ]]> + -suite ]]> + -suite -group -case ]]>

Examples:

$ run_test -config $CFGS/sys1.cfg $CFGS/sys2.cfg -dir $SYS1_TEST $SYS2_TEST

+

$ run_test -userconfig ct_config_xml $CFGS/sys1.xml $CFGS/sys2.xml -dir $SYS1_TEST $SYS2_TEST

$ run_test -suite $SYS1_TEST/setup_SUITE $SYS2_TEST/config_SUITE

$ run_test -suite $SYS1_TEST/setup_SUITE -case start stop

$ run_test -suite $SYS1_TEST/setup_SUITE -group installation -case start stop

diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 4b99d8db88..5623b80592 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -685,7 +685,7 @@ check_callback_load(Callback)-> end. check_config_files(Configs)-> - lists:keysearch(nok, 1, + lists:keysearch(error, 1, lists:flatten( lists:map(fun({Callback, Files})-> case check_callback_load(Callback) of @@ -695,7 +695,7 @@ check_config_files(Configs)-> end, Files); {error, _}-> - {nok, {callback, Callback}} + {error, {callback, Callback}} end end, Configs))). diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl index 327fc66b40..0fed58e45a 100755 --- a/lib/common_test/src/ct_config_plain.erl +++ b/lib/common_test/src/ct_config_plain.erl @@ -71,7 +71,7 @@ check_parameter(File)-> true-> {ok, {file, File}}; false-> - {nok, {nofile, File}} + {error, {nofile, File}} end. read_config_terms(Bin) when is_binary(Bin) -> diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index e14bb0f9f9..111d1426c9 100755 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -42,7 +42,7 @@ check_parameter(File)-> true-> {ok, {file, File}}; false-> - {nok, {nofile, File}} + {error, {nofile, File}} end. % actual reading of the config diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 0e7da60821..92c2334a80 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -245,11 +245,11 @@ check_and_install_configfiles(Configs, LogDir, EvHandlers) -> false-> install([{config,Configs}, {event_handler,EvHandlers}], LogDir); - {value, {nok, {nofile, File}}} -> + {value, {error, {nofile, File}}} -> {error,{cant_read_config_file,File}}; - {value, {nok, {wrong_config, Message}}}-> + {value, {error, {wrong_config, Message}}}-> {error,{wrong_config, Message}}; - {value, {nok, {callback, File}}} -> + {value, {error, {callback, File}}} -> {error,{cant_load_callback_module,File}} end. @@ -702,9 +702,9 @@ check_config_file(Callback, File)-> ?abs(File); {ok, {config, _}}-> File; - {nok, {wrong_config, Message}}-> + {error, {wrong_config, Message}}-> exit({wrong_config, {Callback, Message}}); - {nok, {nofile, File}}-> + {error, {nofile, File}}-> exit({no_such_file, ?abs(File)}) end. diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index ea30ccc13b..5248a6e319 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -321,9 +321,9 @@ get_absfile(Callback, FullName,#testspec{spec_dir=SpecDir}) -> filename:join(Dir,File); {ok, {config, FullName}}-> FullName; - {nok, {nofile, FullName}}-> + {error, {nofile, FullName}}-> FullName; - {nok, {wrong_config, FullName}}-> + {error, {wrong_config, FullName}}-> FullName end. diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl index 670639f7c7..073cb66ac2 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl @@ -41,6 +41,6 @@ check_parameter(ServerName)-> {module, ServerModule}-> {ok, {config, ServerName}}; {error, nofile}-> - {nok, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} + {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}} end end. diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk index cdb8e1f71c..2947c6a152 100644 --- a/lib/common_test/vsn.mk +++ b/lib/common_test/vsn.mk @@ -1,3 +1,3 @@ -COMMON_TEST_VSN = 1.4.8 +COMMON_TEST_VSN = 1.5 -- cgit v1.2.3 From 542a73f9c405b424f743fb0ab679a23f60db2a17 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 15 Mar 2010 18:12:29 +0100 Subject: Eliminate compilation warning Modified debug function never called. --- lib/common_test/src/ct_telnet_client.erl | 61 ++++++++++++++++---------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl index 1a12c5e343..2e0536ec2d 100644 --- a/lib/common_test/src/ct_telnet_client.erl +++ b/lib/common_test/src/ct_telnet_client.erl @@ -35,8 +35,6 @@ -export([open/1, open/2, open/3, open/4, close/1]). -export([send_data/2, get_data/1]). --define(DBG, false). - -define(TELNET_PORT, 23). -define(OPEN_TIMEOUT,10000). -define(IDLE_TIMEOUT,10000). @@ -287,35 +285,38 @@ get_subcmd([?SE | Rest], Acc) -> get_subcmd([Opt | Rest], Acc) -> get_subcmd(Rest, [Opt | Acc]). - +-ifdef(debug). dbg(_Str,_Args) -> - if ?DBG -> io:format(_Str,_Args); - true -> ok + io:format(_Str,_Args). + +cmd_dbg(_Cmd) -> + case _Cmd of + [?IAC|Cmd1] -> + cmd_dbg(Cmd1); + [Ctrl|Opts] -> + CtrlStr = + case Ctrl of + ?DO -> "DO"; + ?DONT -> "DONT"; + ?WILL -> "WILL"; + ?WONT -> "WONT"; + ?NOP -> "NOP"; + _ -> "CMD" + end, + Opts1 = + case Opts of + [Opt] -> Opt; + _ -> Opts + end, + io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]); + Any -> + io:format("Unexpected in cmd_dbg:~n~w~n",[Any]) end. +-else. +dbg(_Str,_Args) -> + ok. + cmd_dbg(_Cmd) -> - if ?DBG -> - case _Cmd of - [?IAC|Cmd1] -> - cmd_dbg(Cmd1); - [Ctrl|Opts] -> - CtrlStr = - case Ctrl of - ?DO -> "DO"; - ?DONT -> "DONT"; - ?WILL -> "WILL"; - ?WONT -> "WONT"; - ?NOP -> "NOP"; - _ -> "CMD" - end, - Opts1 = - case Opts of - [Opt] -> Opt; - _ -> Opts - end, - io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]); - Any -> - io:format("Unexpected in cmd_dbg:~n~w~n",[Any]) - end; - true -> ok - end. + ok. +-endif. \ No newline at end of file -- cgit v1.2.3 From eda9f3c82d3241549352969c59488b232e902d69 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 18 Mar 2010 14:44:48 +0100 Subject: Change spelling, phrasing and structure in documentation --- lib/common_test/doc/src/config_file_chapter.xml | 33 ++++++++++++++----------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml index ef07ef6154..5199807f48 100644 --- a/lib/common_test/doc/src/config_file_chapter.xml +++ b/lib/common_test/doc/src/config_file_chapter.xml @@ -183,7 +183,7 @@ Using own configuration data formats

The nature of the configuration variables can be not only plain text - files with the key-value tuples, but they can be loaded from the files in + files with the key-value tuples, they also can be loaded from the files in various formats, fetched via http from the Web, or can be loaded with help of some driver process. For this purpose, mechanism of plugging in user configuration handling callback modules is implemented in the Common Test.

@@ -223,7 +223,7 @@

This configuration file, once read, will produce the same configuration - variables as the following "traditional" file:

+ variables as the following text file:

 {ftp_host, [{ftp,"targethost"},
             {username,"tester"},
@@ -237,13 +237,13 @@
       Implementing of the own handler
 
       

Own handler can be written to handle special configuration file formats. - The parameter can be either file name or configuration string (empty list + The parameter can be either file name(s) or configuration string (empty list is valid).

The callback module is completely responsible for the configuration string correctness checks during runtime.

-

Callback module should have the following functions exported:

-

One for checking configuration parameter

+

To perform validation of the configuration string, callback module + should have the following function exported:

Callback:check_parameter/1

Input value will be passed from the Common Test, as defined in the test @@ -269,22 +269,25 @@ -

And second function performs reading of the configuration variables:

+

To perform actual reading, in cases of initial loading of the + configuration variables and runtime re-loading, function

Callback:read_config/1

+

should be exported from the callback module

Input value is the same as for check_parameter/1 function

Return value should be either:

- {ok, Config} - is configuration variables read successfully; + {ok, Config} - if configuration variables read successfully; {error, Error, ErrorDetails} - if callback module failed to - proceed. + proceed with the given configuration parameters. -

Above, the Config variable is the key-value list, possibly nested, - e.g. for the configuration files above it will be the following

+

Above, the Config variable is the proper Erlang key-value list, + with possible key-value sublists as values, + e.g. for the configuration files above it will be the following:

         [{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
@@ -310,9 +313,9 @@
 
     

XML version shown in chapter above can also be used, but it should be explicitly specified that ct_config_xml callback module is to be - used.

+ used by the Common Test.

-

Example of how to assert that the configuration data is available and +

Example of how to assert that the configuration data is available and use it for an FTP session:

     init_per_testcase(ftptest, Config) ->
@@ -359,7 +362,8 @@
 
   
Example of own configuration handler -

Simple configuration hanling "driver" can be implemented this way:

+

Simple configuration hanling driver which will ask external server for + configuration data can be implemented this way:

 -module(config_driver).
 -export([read_config/1, check_parameter/1]).
@@ -384,7 +388,8 @@ check_parameter(ServerName)->
     end.
     

Configuration string for this driver may be "config_server", if the - config_server.erl module below is built and is exist in the code path:

+ config_server.erl module below is built and is exist in the code path + during test execution:

 -module(config_server).
 -export([start/0, stop/0, init/1, get_config/0, loop/0]).
-- 
cgit v1.2.3


From d7c10478a2287ce3f0c9ac077d835919e2745f91 Mon Sep 17 00:00:00 2001
From: Andrey Pampukha 
Date: Thu, 18 Mar 2010 16:48:27 +0100
Subject: Rename suites for ct_config and add tests for disappearing variables

Tests for disappearing variables added to ct_dynamic_SUITE.
---
 lib/common_test/test/ct_config_SUITE.erl           |  92 ++++++-----
 .../config/test/config_1_SUITE.erl                 | 151 ------------------
 .../config/test/config_2_SUITE.erl                 | 133 ----------------
 .../config/test/config_dynamic_SUITE.erl           | 172 +++++++++++++++++++++
 .../config/test/config_server.erl                  |  16 +-
 .../config/test/config_static_SUITE.erl            | 151 ++++++++++++++++++
 6 files changed, 379 insertions(+), 336 deletions(-)
 delete mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl
 delete mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl
 create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
 create mode 100644 lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl

diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl
index ad7c521946..a7fa6fe52e 100644
--- a/lib/common_test/test/ct_config_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE.erl
@@ -78,31 +78,31 @@ all(suite) ->
 %%%
 require(Config) when is_list(Config) ->
     DataDir = ?config(data_dir, Config),
-    run_test(require,
+    run_test(config_static_SUITE,
 	     Config,
 	     {config, filename:join(DataDir, "config/config.txt")},
-             ["config_1_SUITE"]).
+             ["config_static_SUITE"]).
 
 userconfig_static(Config) when is_list(Config) ->
     DataDir = ?config(data_dir, Config),
-    run_test(userconfig_static,
+    run_test(config_static_SUITE,
 	     Config,
 	     {userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}},
-             ["config_1_SUITE"]).
+             ["config_static_SUITE"]).
 
 userconfig_dynamic(Config) when is_list(Config) ->
-    run_test(userconfig_dynamic,
+    run_test(config_dynamic_SUITE,
 	     Config,
 	     {userconfig, {config_driver, "config_server"}},
-             ["config_2_SUITE"]).
+             ["config_dynamic_SUITE"]).
 
 testspec_legacy(Config) when is_list(Config) ->
     DataDir = ?config(data_dir, Config),
     make_spec(DataDir,
 		"config/spec_legacy.spec",
-		[config_1_SUITE],
+		[config_static_SUITE],
 		[{config, filename:join(DataDir, "config/config.txt")}]),
-    run_test(testspec_legacy,
+    run_test(config_static_SUITE,
 	     Config,
 	     {spec, filename:join(DataDir, "config/spec_legacy.spec")},
              []),
@@ -112,9 +112,9 @@ testspec_static(Config) when is_list(Config) ->
     DataDir = ?config(data_dir, Config),
     make_spec(DataDir,
 		"config/spec_static.spec",
-		[config_1_SUITE],
+		[config_static_SUITE],
 		[{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]),
-    run_test(testspec_static,
+    run_test(config_static_SUITE,
 	     Config,
 	     {spec, filename:join(DataDir, "config/spec_static.spec")},
              []),
@@ -123,9 +123,9 @@ testspec_static(Config) when is_list(Config) ->
 testspec_dynamic(Config) when is_list(Config) ->
     DataDir = ?config(data_dir, Config),
     make_spec(DataDir, "config/spec_dynamic.spec",
-		[config_2_SUITE],
+		[config_dynamic_SUITE],
 		[{userconfig, {config_driver, "config_server"}}]),
-    run_test(testspec_dynamic,
+    run_test(config_dynamic_SUITE,
 	     Config,
 	     {spec, filename:join(DataDir, "config/spec_dynamic.spec")},
              []),
@@ -134,7 +134,7 @@ testspec_dynamic(Config) when is_list(Config) ->
 %%%-----------------------------------------------------------------
 %%% HELP FUNCTIONS
 %%%-----------------------------------------------------------------
-% {suites, "ct_config_SUITE_data/config/test", config_2_SUITE}.
+% {suites, "ct_config_SUITE_data/config/test", config_dynamic_SUITE}.
 make_spec(DataDir, Filename, Suites, Config)->
     {ok, Fd} = file:open(filename:join(DataDir, Filename), [write]),
     ok = file:write(Fd,
@@ -169,66 +169,64 @@ reformat_events(Events, EH) ->
 %%%-----------------------------------------------------------------
 %%% TEST EVENTS
 %%%-----------------------------------------------------------------
-expected_events(Static) when
-	Static == require; Static == testspec_legacy;
-	Static == userconfig_static; Static == testspec_static->
+expected_events(config_static_SUITE)->
 [
  {?eh,start_logging,{'DEF','RUNDIR'}},
  {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
  {?eh,start_info,{1,1,8}},
- {?eh,tc_start,{config_1_SUITE,init_per_suite}},
- {?eh,tc_done,{config_1_SUITE,init_per_suite,ok}},
- {?eh,tc_start,{config_1_SUITE,test_get_config_simple}},
- {?eh,tc_done,{config_1_SUITE,test_get_config_simple,ok}},
+ {?eh,tc_start,{config_static_SUITE,init_per_suite}},
+ {?eh,tc_done,{config_static_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_simple}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_simple,ok}},
  {?eh,test_stats,{1,0,{0,0}}},
- {?eh,tc_start,{config_1_SUITE,test_get_config_nested}},
- {?eh,tc_done,{config_1_SUITE,test_get_config_nested,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_nested}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_nested,ok}},
  {?eh,test_stats,{2,0,{0,0}}},
- {?eh,tc_start,{config_1_SUITE,test_default_suitewide}},
- {?eh,tc_done,{config_1_SUITE,test_default_suitewide,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_default_suitewide}},
+ {?eh,tc_done,{config_static_SUITE,test_default_suitewide,ok}},
  {?eh,test_stats,{3,0,{0,0}}},
- {?eh,tc_start,{config_1_SUITE,test_config_name_already_in_use1}},
+ {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use1}},
  {?eh,tc_done,
-     {config_1_SUITE,test_config_name_already_in_use1,{skipped,{config_name_already_in_use,[x1]}}}},
+     {config_static_SUITE,test_config_name_already_in_use1,{skipped,{config_name_already_in_use,[x1]}}}},
  {?eh,test_stats,{3,0,{1,0}}},
- {?eh,tc_start,{config_1_SUITE,test_default_tclocal}},
- {?eh,tc_done,{config_1_SUITE,test_default_tclocal,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_default_tclocal}},
+ {?eh,tc_done,{config_static_SUITE,test_default_tclocal,ok}},
  {?eh,test_stats,{4,0,{1,0}}},
- {?eh,tc_start,{config_1_SUITE,test_config_name_already_in_use2}},
+ {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use2}},
  {?eh,tc_done,
-     {config_1_SUITE,test_config_name_already_in_use2,
+     {config_static_SUITE,test_config_name_already_in_use2,
          {skipped,{config_name_already_in_use,[x1,alias]}}}},
  {?eh,test_stats,{4,0,{2,0}}},
- {?eh,tc_start,{config_1_SUITE,test_alias_tclocal}},
- {?eh,tc_done,{config_1_SUITE,test_alias_tclocal,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_alias_tclocal}},
+ {?eh,tc_done,{config_static_SUITE,test_alias_tclocal,ok}},
  {?eh,test_stats,{5,0,{2,0}}},
- {?eh,tc_start,{config_1_SUITE,test_get_config_undefined}},
- {?eh,tc_done,{config_1_SUITE,test_get_config_undefined,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_undefined}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_undefined,ok}},
  {?eh,test_stats,{6,0,{2,0}}},
- {?eh,tc_start,{config_1_SUITE,end_per_suite}},
- {?eh,tc_done,{config_1_SUITE,end_per_suite,ok}},
+ {?eh,tc_start,{config_static_SUITE,end_per_suite}},
+ {?eh,tc_done,{config_static_SUITE,end_per_suite,ok}},
  {?eh,test_done,{'DEF','STOP_TIME'}},
  {?eh,stop_logging,[]}
 ];
 
-expected_events(Dynamic) when Dynamic == testspec_dynamic; Dynamic == userconfig_dynamic->
+expected_events(config_dynamic_SUITE)->
 [
  {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
  {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
  {ct_test_support_eh,start_info,{1,1,3}},
- {ct_test_support_eh,tc_start,{config_2_SUITE,init_per_suite}},
- {ct_test_support_eh,tc_done,{config_2_SUITE,init_per_suite,ok}},
- {ct_test_support_eh,tc_start,{config_2_SUITE,test_get_known_variable}},
- {ct_test_support_eh,tc_done,{config_2_SUITE,test_get_known_variable,ok}},
+ {ct_test_support_eh,tc_start,{config_dynamic_SUITE,init_per_suite}},
+ {ct_test_support_eh,tc_done,{config_dynamic_SUITE,init_per_suite,ok}},
+ {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_get_known_variable}},
+ {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_get_known_variable,ok}},
  {ct_test_support_eh,test_stats,{1,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_2_SUITE,test_localtime_update}},
- {ct_test_support_eh,tc_done,{config_2_SUITE,test_localtime_update,ok}},
+ {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_localtime_update}},
+ {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_localtime_update,ok}},
  {ct_test_support_eh,test_stats,{2,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_2_SUITE,test_server_pid}},
- {ct_test_support_eh,tc_done,{config_2_SUITE,test_server_pid,ok}},
+ {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_server_pid}},
+ {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_server_pid,ok}},
  {ct_test_support_eh,test_stats,{3,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_2_SUITE,end_per_suite}},
- {ct_test_support_eh,tc_done,{config_2_SUITE,end_per_suite,ok}},
+ {ct_test_support_eh,tc_start,{config_dynamic_SUITE,end_per_suite}},
+ {ct_test_support_eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}},
  {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
  {ct_test_support_eh,stop_logging,[]}
 ].
\ No newline at end of file
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl
deleted file mode 100644
index e102c69d3d..0000000000
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_1_SUITE.erl
+++ /dev/null
@@ -1,151 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File: config_1_SUITE
-%%%
-%%% Description:
-%%% Test suite for common_test which tests the get_config and require
-%%% functionality
-%%%-------------------------------------------------------------------
--module(config_1_SUITE).
-
--compile(export_all).
-
--include_lib("common_test/include/ct.hrl").
-
-% The config contains variables:
-% x - atom
-% gen_cfg - list with two key-values tagged with a and b
-% gen_cfg2 - list of five key-values tagged with c, d, e, f and g
-% gen_cfg3 - list of two complex key-values taggen with:
-%	h: three elements inside - i, j and k
-%	l: m inside, contains n and o
-
-suite() ->
-    [
-     {timetrap, {seconds,10}},
-     %% x1 doesn't exist in cfg-file!
-     {require, x1, x},
-     {require, gen_cfg3},
-     {require, alias, gen_cfg},
-     %% x1 default value
-     {x1, {x,suite}}
-    ].
-
-% to get it running on development branch (without userconfig features)
-% function to print full config is in the ct_util, for me it's moved to ct_config
-% two following functions are only for the design period
-% after merging of userconfigs to the main branch ct_config:get_all_config/0
-% should be called instead
-is_exported(Module, Function, Arity)->
-    Exports = Module:module_info(exports),
-    case lists:keyfind(Function, 1, Exports) of
-	false->
-	    false;
-	{Function, Arity}->
-	    true;
-	{Function, _OtherArity}->
-	    false
-    end.
-
-get_all_config()->
-    case is_exported(ct_util, get_all_config, 0) of
-	true->
-	    {ct_util, ct_util:get_all_config()};
-	false->
-	    {ct_config, ct_config:get_all_config()}
-    end.
-
-init_per_suite(Config) ->
-    %{Module, Cfg} = get_all_config(),
-    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
-    Config.
-
-end_per_suite(_) ->
-    ok.
-
-all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide,
-	  test_config_name_already_in_use1, test_default_tclocal,
-	  test_config_name_already_in_use2, test_alias_tclocal,
-	  test_get_config_undefined].
-
-init_per_testcase(_, Config) ->
-    %{Module, Cfg} = get_all_config(),
-    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
-    Config.
-
-end_per_testcase(_, _) ->
-    ok.
-
-%% test getting a simple value
-test_get_config_simple(_)->
-    suite = ct:get_config(x),
-    ok.
-
-%% test getting a nested value
-test_get_config_nested(_)->
-    a_value = ct:get_config({gen_cfg, a}),
-    ok.
-
-%% test suite-wide default value
-test_default_suitewide(_)->
-    suite = ct:get_config(x1),
-    ok.
-
-%% should get skipped
-test_config_name_already_in_use1() ->
-    [{timetrap, {seconds,2}},
-     {require, x1, x},
-     {x1, {x,test2}}].
-test_config_name_already_in_use1(_) ->
-    ct:fail("Test should've been skipped, you shouldn't see this!"),
-    ok.
-
-%% test defaults in a testcase
-test_default_tclocal() ->
-    [{timetrap, {seconds,3}},
-     {require, y1, y},
-     {y1, {y,test3}}].
-test_default_tclocal(_) ->
-    test3 = ct:get_config(y1),
-    ok.
-
-%% should get skipped
-test_config_name_already_in_use2() ->
-    [{require,alias,something},
-     {alias,{something,else}},
-     {require, x1, x},
-     {x1, {x,test4}}].
-test_config_name_already_in_use2(_) ->
-    ct:fail("Test should've been skipped, you shouldn't see this!"),
-    ok.
-
-%% test aliases
-test_alias_tclocal() ->
-    [{require,newalias,gen_cfg}].
-test_alias_tclocal(_) ->
-    A = [{a,a_value},{b,b_value}] = ct:get_config(newalias),
-    A = ct:get_config(gen_cfg),
-    ok.
-
-%% test for getting undefined variables
-test_get_config_undefined(_) ->
-    undefined = ct:get_config(y1),
-    ok.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl
deleted file mode 100644
index 86fd300a77..0000000000
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_2_SUITE.erl
+++ /dev/null
@@ -1,133 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File: ct_config_SUITE
-%%%
-%%% Description:
-%%% Test suite for common_test which tests the userconfig functionality
-%%%-------------------------------------------------------------------
--module(config_2_SUITE).
-
--compile(export_all).
-
--include_lib("common_test/include/ct.hrl").
-
-%% This suite will be run with dynamic userconfig
-%% test_driver.erl is compliant to ct_config_* callback
-%% and test_server is simple server for getting runtime-changing data
-%% which will return the list with the following variables:
-%% localtime = the erlang:localtime() result in list [{date, Date}, {time, Time}]
-%% node = erlang:node() - can be compared in the testcase
-%% now = erlang:now() - easier to compare than localtime()
-%% config_server_pid - pid of the config server, should NOT change!
-%% config_server_vsn - .19
-
-suite() ->
-    [
-     {timetrap, {seconds,10}}
-    ].
-
-% to get it running on development branch (without userconfig features)
-% function to print full config is in the ct_util, for me it's moved to ct_config
-% two following functions are only for the design period
-% after merging of userconfigs to the main branch ct_config:get_all_config/0
-% should be called instead
-is_exported(Module, Function, Arity)->
-    Exports = Module:module_info(exports),
-    case lists:keyfind(Function, 1, Exports) of
-	false->
-	    false;
-	{Function, Arity}->
-	    true;
-	{Function, _OtherArity}->
-	    false
-    end.
-
-get_all_config()->
-    case is_exported(ct_util, get_all_config, 0) of
-	true->
-	    {ct_util, ct_util:get_all_config()};
-	false->
-	    {ct_config, ct_config:get_all_config()}
-    end.
-
-init_per_suite(Config) ->
-    %{Module, Cfg} = get_all_config(),
-    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
-    Config.
-
-end_per_suite(_) ->
-    ok.
-
-all() -> [test_get_known_variable, test_localtime_update,
-	  test_server_pid].
-
-init_per_testcase(_, Config) ->
-    %{Module, Cfg} = get_all_config(),
-    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
-    Config.
-
-end_per_testcase(_, _) ->
-    ok.
-
-% test that usual config works
-test_get_known_variable(_)->
-    Node = erlang:node(),
-    0.19 = ct:get_config(config_server_vsn),
-    Node = ct:get_config(node),
-    ok.
-
-% localtime will be updated in 5 seconds, check that
-test_localtime_update(_)->
-    Seconds = 5,
-    LT1 = ct:get_config(localtime),
-    timer:sleep(Seconds*1000),
-    LT2 = ct:reload_config(localtime),
-    case is_diff_ok(LT1, LT2, Seconds) of
-	{false, Actual, Exp}->
-	    ct:fail(io_lib:format("Time difference ~p is not ok, expected ~p", [Actual, Exp]));
-	true->
-	    ok
-    end.
-
-% server pid should not change
-test_server_pid()->
-    [{require, cfvsn, config_server_vsn}].
-test_server_pid(_)->
-    Pid = ct:get_config(config_server_pid),
-    Pid = ct:reload_config(config_server_pid),
-    Vsn = ct:get_config(config_server_vsn),
-    % aliases remain after config reloading
-    Vsn = ct:get_config(cfvsn),
-    ok.
-
-my_dt_to_datetime([{date, D},{time, T}])->
-    {D, T}.
-
-is_diff_ok(DT1, DT2, Seconds)->
-    GS1 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT1)),
-    GS2 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT2)),
-    if
-	GS2-GS1 > Seconds+Seconds/2;
-	GS2-GS1 < Seconds-Seconds/2->
-	    {false, GS2-GS1, Seconds};
-	true->
-	    true
-    end.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
new file mode 100644
index 0000000000..1013c46c7d
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
@@ -0,0 +1,172 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_config_SUITE
+%%%
+%%% Description:
+%%% Test suite for common_test which tests the userconfig functionality
+%%%-------------------------------------------------------------------
+-module(config_2_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% This suite will be run with dynamic userconfig
+%% test_driver.erl is compliant to ct_config_* callback
+%% and test_server is simple server for getting runtime-changing data
+%% which will return the list with the following variables:
+%% localtime = the erlang:localtime() result in list [{date, Date}, {time, Time}]
+%% node = erlang:node() - can be compared in the testcase
+%% now = erlang:now() - easier to compare than localtime()
+%% config_server_pid - pid of the config server, should NOT change!
+%% config_server_vsn - .19
+%% config_server_iteration - a number of iteration config_server's loop done
+%% disappearable_variable - hereAmI - will be absent on even iterations
+
+suite() ->
+    [
+     {timetrap, {seconds,10}}
+    ].
+
+% to get it running on development branch (without userconfig features)
+% function to print full config is in the ct_util, for me it's moved to ct_config
+% two following functions are only for the design period
+% after merging of userconfigs to the main branch ct_config:get_all_config/0
+% should be called instead
+is_exported(Module, Function, Arity)->
+    Exports = Module:module_info(exports),
+    case lists:keyfind(Function, 1, Exports) of
+	false->
+	    false;
+	{Function, Arity}->
+	    true;
+	{Function, _OtherArity}->
+	    false
+    end.
+
+get_all_config()->
+    case is_exported(ct_util, get_all_config, 0) of
+	true->
+	    {ct_util, ct_util:get_all_config()};
+	false->
+	    {ct_config, ct_config:get_all_config()}
+    end.
+
+init_per_suite(Config) ->
+    %{Module, Cfg} = get_all_config(),
+    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
+    Config.
+
+end_per_suite(_) ->
+    ok.
+
+all() -> [test_get_known_variable, test_localtime_update,
+	  test_server_pid, test_disappearable_variable,
+	  test_disappearable_variable_alias].
+
+init_per_testcase(_, Config) ->
+    %{Module, Cfg} = get_all_config(),
+    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
+    Config.
+
+end_per_testcase(_, _) ->
+    ok.
+
+% test that usual config works
+test_get_known_variable(_)->
+    Node = erlang:node(),
+    0.19 = ct:get_config(config_server_vsn),
+    Node = ct:get_config(node),
+    ok.
+
+% localtime will be updated in 5 seconds, check that
+test_localtime_update(_)->
+    Seconds = 5,
+    LT1 = ct:get_config(localtime),
+    timer:sleep(Seconds*1000),
+    LT2 = ct:reload_config(localtime),
+    case is_diff_ok(LT1, LT2, Seconds) of
+	{false, Actual, Exp}->
+	    ct:fail(io_lib:format("Time difference ~p is not ok, expected ~p", [Actual, Exp]));
+	true->
+	    ok
+    end.
+
+% server pid should not change
+test_server_pid()->
+    [{require, cfvsn, config_server_vsn}].
+test_server_pid(_)->
+    Pid = ct:get_config(config_server_pid),
+    Pid = ct:reload_config(config_server_pid),
+    Vsn = ct:get_config(config_server_vsn),
+    % aliases remain after config reloading
+    Vsn = ct:get_config(cfvsn),
+    ok.
+
+% test that variables may disappear from the config_2_SUITE
+test_disappearable_variable(_)->
+    % ask CT for config_server_iteration variable
+    Iter = ct:reload_config(config_server_iteration),
+    % here we should reload this variable in case it's odd
+    if Iter rem 2 == 1->
+	Iter2 = ct:reload_config(config_server_iteration),
+	Iter2 = Iter+1;
+	true->ok
+    end,
+    % now disappearable_variable should be in place
+    hereAmI = ct:get_config(disappearable_variable),
+    % and now it should disappear
+    undefined = ct:reload_config(disappearable_variable).
+
+% alias of disappearable_variable should disappear too
+test_disappearable_variable_alias(_)->
+    % the same rules apply for this testcase as for previous one
+    Iter = ct:reload_config(config_server_iteration),
+    Iter2 = if
+	Iter rem 2 == 1 ->
+	    NewIter = ct:reload_config(config_server_iteration),
+	    NewIter = Iter+1;
+	true->
+	    Iter
+    end,
+    ct:require(diav, disappearable_variable),
+    hereAmI = ct:get_config(disappearable_variable),
+    hereAmI = ct:get_config(diav),
+    undefined = ct:reload_config(disappearable_variable),
+    % after reloading, it's even again
+    Iter3=ct:get_config(config_server_iteration),
+    Iter3 = Iter2+1,
+    % and alias does not exist
+    undefined = ct:get_config(diav).
+
+my_dt_to_datetime([{date, D},{time, T}])->
+    {D, T}.
+
+is_diff_ok(DT1, DT2, Seconds)->
+    GS1 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT1)),
+    GS2 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT2)),
+    if
+	GS2-GS1 > Seconds+Seconds/2;
+	GS2-GS1 < Seconds-Seconds/2->
+	    {false, GS2-GS1, Seconds};
+	true->
+	    true
+    end.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl
index 0ee0bcfc22..f56bdf15ad 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl
@@ -24,7 +24,7 @@
 %%% Config server used in the CT's tests (config_2_SUITE)
 %%%-------------------------------------------------------------------
 -module(config_server).
--export([start/0, stop/0, init/1, get_config/0, loop/0]).
+-export([start/0, stop/0, loop/1, init/1, get_config/0]).
 
 -define(REGISTERED_NAME, ct_test_config_server).
 -define(vsn, 0.19).
@@ -41,7 +41,7 @@ start()->
 
 init(Name)->
     register(Name, self()),
-    loop().
+    loop(0).
 
 get_config()->
     call(self(), get_config).
@@ -63,19 +63,25 @@ call(Client, Request)->
 	    end
     end.
 
-loop()->
+loop(Iteration)->
     receive
 	{Pid, stop}->
 	    Pid ! ok;
 	{Pid, get_config}->
 	    {D,T} = erlang:localtime(),
-	    Pid !
+	    Config =
 		[{localtime, [{date, D}, {time, T}]},
 		 {node, erlang:node()},
+		 {config_server_iteration, Iteration},
 		 {now, erlang:now()},
 		 {config_server_pid, self()},
 		 {config_server_vsn, ?vsn}],
-	    ?MODULE:loop()
+	    Config2 = if Iteration rem 2 == 0->
+		Config ++ [{disappearable_variable, hereAmI}];
+		true-> Config
+	    end,
+	    Pid ! Config2,
+	    ?MODULE:loop(Iteration+1)
     end.
 
 wait()->
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
new file mode 100644
index 0000000000..e102c69d3d
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
@@ -0,0 +1,151 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: config_1_SUITE
+%%%
+%%% Description:
+%%% Test suite for common_test which tests the get_config and require
+%%% functionality
+%%%-------------------------------------------------------------------
+-module(config_1_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+% The config contains variables:
+% x - atom
+% gen_cfg - list with two key-values tagged with a and b
+% gen_cfg2 - list of five key-values tagged with c, d, e, f and g
+% gen_cfg3 - list of two complex key-values taggen with:
+%	h: three elements inside - i, j and k
+%	l: m inside, contains n and o
+
+suite() ->
+    [
+     {timetrap, {seconds,10}},
+     %% x1 doesn't exist in cfg-file!
+     {require, x1, x},
+     {require, gen_cfg3},
+     {require, alias, gen_cfg},
+     %% x1 default value
+     {x1, {x,suite}}
+    ].
+
+% to get it running on development branch (without userconfig features)
+% function to print full config is in the ct_util, for me it's moved to ct_config
+% two following functions are only for the design period
+% after merging of userconfigs to the main branch ct_config:get_all_config/0
+% should be called instead
+is_exported(Module, Function, Arity)->
+    Exports = Module:module_info(exports),
+    case lists:keyfind(Function, 1, Exports) of
+	false->
+	    false;
+	{Function, Arity}->
+	    true;
+	{Function, _OtherArity}->
+	    false
+    end.
+
+get_all_config()->
+    case is_exported(ct_util, get_all_config, 0) of
+	true->
+	    {ct_util, ct_util:get_all_config()};
+	false->
+	    {ct_config, ct_config:get_all_config()}
+    end.
+
+init_per_suite(Config) ->
+    %{Module, Cfg} = get_all_config(),
+    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
+    Config.
+
+end_per_suite(_) ->
+    ok.
+
+all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide,
+	  test_config_name_already_in_use1, test_default_tclocal,
+	  test_config_name_already_in_use2, test_alias_tclocal,
+	  test_get_config_undefined].
+
+init_per_testcase(_, Config) ->
+    %{Module, Cfg} = get_all_config(),
+    %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]),
+    Config.
+
+end_per_testcase(_, _) ->
+    ok.
+
+%% test getting a simple value
+test_get_config_simple(_)->
+    suite = ct:get_config(x),
+    ok.
+
+%% test getting a nested value
+test_get_config_nested(_)->
+    a_value = ct:get_config({gen_cfg, a}),
+    ok.
+
+%% test suite-wide default value
+test_default_suitewide(_)->
+    suite = ct:get_config(x1),
+    ok.
+
+%% should get skipped
+test_config_name_already_in_use1() ->
+    [{timetrap, {seconds,2}},
+     {require, x1, x},
+     {x1, {x,test2}}].
+test_config_name_already_in_use1(_) ->
+    ct:fail("Test should've been skipped, you shouldn't see this!"),
+    ok.
+
+%% test defaults in a testcase
+test_default_tclocal() ->
+    [{timetrap, {seconds,3}},
+     {require, y1, y},
+     {y1, {y,test3}}].
+test_default_tclocal(_) ->
+    test3 = ct:get_config(y1),
+    ok.
+
+%% should get skipped
+test_config_name_already_in_use2() ->
+    [{require,alias,something},
+     {alias,{something,else}},
+     {require, x1, x},
+     {x1, {x,test4}}].
+test_config_name_already_in_use2(_) ->
+    ct:fail("Test should've been skipped, you shouldn't see this!"),
+    ok.
+
+%% test aliases
+test_alias_tclocal() ->
+    [{require,newalias,gen_cfg}].
+test_alias_tclocal(_) ->
+    A = [{a,a_value},{b,b_value}] = ct:get_config(newalias),
+    A = ct:get_config(gen_cfg),
+    ok.
+
+%% test for getting undefined variables
+test_get_config_undefined(_) ->
+    undefined = ct:get_config(y1),
+    ok.
-- 
cgit v1.2.3


From dd67e73980ac0c8b601079c2e3f27701c367936c Mon Sep 17 00:00:00 2001
From: Andrey Pampukha 
Date: Thu, 18 Mar 2010 18:02:37 +0100
Subject: Fix problem with disappearing variables and aliases

---
 lib/common_test/src/ct_config.erl                  | 81 +++++++++++++++++-----
 lib/common_test/test/ct_config_SUITE.erl           | 47 ++++++++-----
 .../config/test/config_dynamic_SUITE.erl           |  5 +-
 .../config/test/config_static_SUITE.erl            |  2 +-
 4 files changed, 95 insertions(+), 40 deletions(-)

diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 5623b80592..728dcde6af 100755
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -242,27 +242,51 @@ store_config(Config, Callback, File)->
 			 default=false}) ||
 	{Key,Val} <- Config].
 
+keyfindall(Key, N, List)->
+    keyfindall(Key, N, List, []).
+
+keyfindall(Key, N, List, Acc)->
+    case lists:keytake(Key, N, List) of
+	false->
+	    Acc;
+	{value, Row, Rest}->
+	    keyfindall(Key, N, Rest, [Row|Acc])
+    end.
+
 rewrite_config(Config, Callback, File)->
-    [begin
-     Rows=case ets:match_object(?attr_table,
-			       #ct_conf{key=Key,
-					handler=Callback,
-					config=File,_='_'}) of
-	[]->
-	    [#ct_conf{default=false}];
-	Elements->
-	    Elements
-     end,
-     lists:foreach(fun(Row)->
-			ets:delete_object(?attr_table, Row),
-			ets:insert(?attr_table,
-				   Row#ct_conf{key=Key,
-				   value=Val,
+    % 1) read the old config for this callback/file from the table
+    OldRows = ets:match_object(?attr_table,
+				#ct_conf{handler=Callback,
+					 config=File,_='_'}),
+    % 2) remove all config data loaded from this callback/file
+    ets:match_delete(?attr_table,
+		     #ct_conf{handler=Callback,
+			      config=File,_='_'}),
+    % prepare function that will
+    %   1. find records with this key in the old config, including aliases
+    %   2. update values
+    %   3. put them back to the table
+    Updater = fun({Key, Value})->
+	case keyfindall(Key, #ct_conf.key, OldRows) of
+	    []-> % if variable is new, just insert it
+		ets:insert(?attr_table,
+			   #ct_conf{key=Key,
+				   value=Value,
 				   handler=Callback,
 				   config=File,
-				   ref=ct_util:ct_make_ref()}) end,
-		  Rows)
-     end || {Key,Val} <- Config].
+				   ref=ct_util:ct_make_ref()});
+	    RowsToUpdate -> % else update all occurrencies of the key
+		Inserter = fun(Row)->
+		    ets:insert(?attr_table,
+				Row#ct_conf{value=Value,
+					    ref=ct_util:ct_make_ref()})
+		end,
+		lists:foreach(Inserter, RowsToUpdate)
+	end
+    end,
+
+    % run it key-by-key from the new config
+    [Updater({Key, Value})||{Key, Value}<-Config].
 
 set_config(Config,Default) ->
     set_config('_UNDEF',Config,Default).
@@ -414,12 +438,31 @@ update_conf(Name, NewConfig) ->
 		  end, Old),
     ok.
 
+has_element(_, [])->
+    false;
+has_element(Element, [Element|_Rest])->
+    true;
+has_element(Element, [_|Rest])->
+    has_element(Element, Rest).
+
+remove_duplicates([], Acc)->
+    Acc;
+remove_duplicates([{Handler, File}|Rest], Acc)->
+    case has_element({Handler, File}, Acc) of
+	false->
+	    remove_duplicates(Rest, [{Handler, File}|Acc]);
+	true->
+	    remove_duplicates(Rest, Acc)
+    end.
+
 reload_conf(KeyOrName) ->
     case lookup_handler_for_config(KeyOrName) of
 	[]->
 	    undefined;
 	HandlerList->
-	    read_config_files_int(HandlerList, fun rewrite_config/3),
+	    % if aliases set, config will be reloaded several times
+	    HandlerList2 = remove_duplicates(HandlerList, []),
+	    read_config_files_int(HandlerList2, fun rewrite_config/3),
 	    get_config(KeyOrName)
     end.
 
diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl
index a7fa6fe52e..9a0177c2ec 100644
--- a/lib/common_test/test/ct_config_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE.erl
@@ -211,22 +211,33 @@ expected_events(config_static_SUITE)->
 
 expected_events(config_dynamic_SUITE)->
 [
- {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}},
- {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {ct_test_support_eh,start_info,{1,1,3}},
- {ct_test_support_eh,tc_start,{config_dynamic_SUITE,init_per_suite}},
- {ct_test_support_eh,tc_done,{config_dynamic_SUITE,init_per_suite,ok}},
- {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_get_known_variable}},
- {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_get_known_variable,ok}},
- {ct_test_support_eh,test_stats,{1,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_localtime_update}},
- {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_localtime_update,ok}},
- {ct_test_support_eh,test_stats,{2,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_dynamic_SUITE,test_server_pid}},
- {ct_test_support_eh,tc_done,{config_dynamic_SUITE,test_server_pid,ok}},
- {ct_test_support_eh,test_stats,{3,0,{0,0}}},
- {ct_test_support_eh,tc_start,{config_dynamic_SUITE,end_per_suite}},
- {ct_test_support_eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}},
- {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}},
- {ct_test_support_eh,stop_logging,[]}
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,5}},
+ {?eh,tc_start,{config_dynamic_SUITE,init_per_suite}},
+ {?eh,tc_done,{config_dynamic_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_get_known_variable}},
+ {?eh,tc_done,
+                     {config_dynamic_SUITE,test_get_known_variable,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_localtime_update}},
+ {?eh,tc_done,{config_dynamic_SUITE,test_localtime_update,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_server_pid}},
+ {?eh,tc_done,{config_dynamic_SUITE,test_server_pid,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,
+                     {config_dynamic_SUITE,test_disappearable_variable}},
+ {?eh,tc_done,
+                     {config_dynamic_SUITE,test_disappearable_variable,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,
+                     {config_dynamic_SUITE,test_disappearable_variable_alias}},
+ {?eh,tc_done,
+     {config_dynamic_SUITE,test_disappearable_variable_alias,ok}},
+ {?eh,test_stats,{5,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,end_per_suite}},
+ {?eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
 ].
\ No newline at end of file
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
index 1013c46c7d..ae66d4e9bf 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
@@ -23,7 +23,7 @@
 %%% Description:
 %%% Test suite for common_test which tests the userconfig functionality
 %%%-------------------------------------------------------------------
--module(config_2_SUITE).
+-module(config_dynamic_SUITE).
 
 -compile(export_all).
 
@@ -150,7 +150,8 @@ test_disappearable_variable_alias(_)->
     ct:require(diav, disappearable_variable),
     hereAmI = ct:get_config(disappearable_variable),
     hereAmI = ct:get_config(diav),
-    undefined = ct:reload_config(disappearable_variable),
+    ct:reload_config(disappearable_variable),
+    undefined = ct:get_config(disappearable_variable),
     % after reloading, it's even again
     Iter3=ct:get_config(config_server_iteration),
     Iter3 = Iter2+1,
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
index e102c69d3d..d3f07980bc 100644
--- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
@@ -24,7 +24,7 @@
 %%% Test suite for common_test which tests the get_config and require
 %%% functionality
 %%%-------------------------------------------------------------------
--module(config_1_SUITE).
+-module(config_static_SUITE).
 
 -compile(export_all).
 
-- 
cgit v1.2.3


From 485a982ef7e7d2ffcdfb2606b6e2f4b66d000bac Mon Sep 17 00:00:00 2001
From: Andrey Pampukha 
Date: Fri, 19 Mar 2010 09:09:46 +0100
Subject: Replace own has_element/2 with lists:config/2

---
 lib/common_test/src/ct_config.erl | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
index 728dcde6af..0ae178ef02 100755
--- a/lib/common_test/src/ct_config.erl
+++ b/lib/common_test/src/ct_config.erl
@@ -438,17 +438,10 @@ update_conf(Name, NewConfig) ->
 		  end, Old),
     ok.
 
-has_element(_, [])->
-    false;
-has_element(Element, [Element|_Rest])->
-    true;
-has_element(Element, [_|Rest])->
-    has_element(Element, Rest).
-
 remove_duplicates([], Acc)->
     Acc;
 remove_duplicates([{Handler, File}|Rest], Acc)->
-    case has_element({Handler, File}, Acc) of
+    case lists:member({Handler, File}, Acc) of
 	false->
 	    remove_duplicates(Rest, [{Handler, File}|Acc]);
 	true->
-- 
cgit v1.2.3


From b8b5b0d60b90f22db3bcdbb15012636a67ed470a Mon Sep 17 00:00:00 2001
From: Andrey Pampukha 
Date: Mon, 22 Mar 2010 15:28:11 +0100
Subject: Update test specifications

---
 lib/common_test/src/ct_testspec.erl | 36 ++++++++++++++++++++++--------------
 1 file changed, 22 insertions(+), 14 deletions(-)

diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index 5248a6e319..c07de370b0 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -22,6 +22,7 @@
 %%% 

This module exports functions that are used within CT to %%% scan and parse test specifikations.

-module(ct_testspec). +-compile(export_all). -export([prepare_tests/1, prepare_tests/2, collect_tests_from_list/2, collect_tests_from_list/3, @@ -438,37 +439,44 @@ add_tests([{cover,File}|Ts],Spec) -> add_tests([{cover,all_nodes,File}|Ts],Spec); %% --- config --- -% TODO finish that! add_tests([{config,all_nodes,Files}|Ts],Spec) -> - %io:format("1: add_tests([{config,all_nodes,~p}|~p],~p~n", [Files, Ts, Spec]), - Tests = lists:map(fun(N) -> {config,N,{?ct_config_txt,Files}} end, list_nodes(Spec)), + Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)), add_tests(Tests++Ts,Spec); add_tests([{config,Nodes,Files}|Ts],Spec) when is_list(Nodes) -> - %io:format("2: add_tests([{config,~p,~p}|~p],~p) when is_list(Nodes)~n", [Nodes,Files,Spec,Nodes]), Ts1 = separate(Nodes,config,[Files],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); -add_tests([{config,Node,[{Callback,F}|Fs]}|Ts],Spec) when is_list(F) -> - %io:format("3: add_tests([{config,~p,[~p|~p]}|~p],~p) when is_list(~p)~n", [Node, F, Fs, Ts, Spec, F]), +add_tests([{config,Node,[F|Fs]}|Ts],Spec) when is_list(F) -> Cfgs = Spec#testspec.config, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{config,Node,Fs}|Ts], - Spec#testspec{config=[{Node1,{Callback, [get_absfile(Callback, F,Spec)]}}|Cfgs]}); + Spec#testspec{config=[{Node1,{?ct_config_txt, + get_absfile(?ct_config_txt, F,Spec)}}|Cfgs]}); add_tests([{config,_Node,[]}|Ts],Spec) -> - %io:format("4: add_tests([{config,_,[]}|~p],~p)~n", [Ts, Spec]), add_tests(Ts,Spec); add_tests([{config,Node,F}|Ts],Spec) -> - %io:format("5: add_tests([{config,~p,~p}|~p],~p)~n", [Node, F, Ts, Spec]), add_tests([{config,Node,[F]}|Ts],Spec); add_tests([{config,Files}|Ts],Spec) -> - %io:format("6: add_tests([{config,~p}|~p],~p)~n", [Files, Ts, Spec]), add_tests([{config,all_nodes,Files}|Ts],Spec); -% TODO add support for {userconfig, Nodes, {Callback, Files}} %% --- userconfig --- -add_tests([{userconfig, {Callback, Files}}|Ts], Spec)-> - %io:format("add_tests([{userconfig, {~p, ~p}}|~p], ~p)~n", [Callback, Files, Ts, Spec]), - Tests = lists:map(fun(N) -> {config,N,{Callback,Files}} end, list_nodes(Spec)), +add_tests([{userconfig,all_nodes,CBF}|Ts],Spec) -> + Tests = lists:map(fun(N) -> {userconfig,N,CBF} end, list_nodes(Spec)), add_tests(Tests++Ts,Spec); +add_tests([{userconfig,Nodes,CBF}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,userconfig,[CBF],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{userconfig,Node,[{Callback, Config}|CBF]}|Ts],Spec) -> + Cfgs = Spec#testspec.config, + Node1 = ref2node(Node,Spec#testspec.nodes), + add_tests([{userconfig,Node,CBF}|Ts], + Spec#testspec{config=[{Node1,{Callback, + get_absfile(Callback, Config ,Spec)}}|Cfgs]}); +add_tests([{userconfig,_Node,[]}|Ts],Spec) -> + add_tests(Ts,Spec); +add_tests([{userconfig,Node,CBF}|Ts],Spec) -> + add_tests([{userconfig,Node,[CBF]}|Ts],Spec); +add_tests([{userconfig,CBF}|Ts],Spec) -> + add_tests([{userconfig,all_nodes,CBF}|Ts],Spec); %% --- event_handler --- add_tests([{event_handler,all_nodes,Hs}|Ts],Spec) -> -- cgit v1.2.3 From 2fa838d02446ad54588c2fe6995e3c065e99ec9c Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 22 Mar 2010 15:29:28 +0100 Subject: Add functions for adding and removing config handlers This is a draft version. --- lib/common_test/src/ct.erl | 9 ++++- lib/common_test/src/ct_config.erl | 58 ++++++++++++++------------------ lib/common_test/test/ct_config_SUITE.erl | 3 +- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index bee5c920a9..caae2326b6 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -66,12 +66,14 @@ fail/1, comment/1, testcases/2, userdata/2, userdata/3]). +%% New API for manipulating with config handlers +-export([add_config/2, remove_config/2]). + %% Other interface functions -export([get_status/0, abort_current_testcase/1, encrypt_config_file/2, encrypt_config_file/3, decrypt_config_file/2, decrypt_config_file/3]). - -export([get_target_name/1]). -export([parse_table/1, listenv/1]). @@ -805,3 +807,8 @@ decrypt_config_file(EncryptFileName, TargetFileName) -> decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile). +add_config(Callback, Config)-> + ct_config:add_config(Callback, Config). + +remove_config(Callback, Config)-> + ct_config:remove_config(Callback, Config). diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 0ae178ef02..6fd89c5617 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -36,7 +36,7 @@ -export([set_default_config/2, set_default_config/3]). --export([delete_config/1, delete_default_config/1]). +-export([delete_default_config/1]). -export([reload_config/1, update_config/2]). @@ -50,6 +50,8 @@ -export([check_config_files/1, prepare_config_list/1]). +-export([add_config/2, remove_config/2]). + -include("ct_util.hrl"). -define(cryptfile, ".ct_config.crypt"). @@ -133,7 +135,7 @@ loop(StartDir) -> return(From,ok), loop(StartDir); {{delete_default_config,Scope},From} -> - ct_config:delete_config({true,Scope}), + delete_config({true,Scope}), return(From,ok), loop(StartDir); {{update_config,{Name,NewConfig}},From} -> @@ -242,40 +244,26 @@ store_config(Config, Callback, File)-> default=false}) || {Key,Val} <- Config]. -keyfindall(Key, N, List)-> - keyfindall(Key, N, List, []). - -keyfindall(Key, N, List, Acc)-> - case lists:keytake(Key, N, List) of - false-> - Acc; - {value, Row, Rest}-> - keyfindall(Key, N, Rest, [Row|Acc]) - end. +keyfindall(Key, Pos, List)-> + [E || E <- List, element(Pos, E) =:= Key]. rewrite_config(Config, Callback, File)-> - % 1) read the old config for this callback/file from the table OldRows = ets:match_object(?attr_table, #ct_conf{handler=Callback, config=File,_='_'}), - % 2) remove all config data loaded from this callback/file ets:match_delete(?attr_table, #ct_conf{handler=Callback, config=File,_='_'}), - % prepare function that will - % 1. find records with this key in the old config, including aliases - % 2. update values - % 3. put them back to the table Updater = fun({Key, Value})-> case keyfindall(Key, #ct_conf.key, OldRows) of - []-> % if variable is new, just insert it + []-> ets:insert(?attr_table, #ct_conf{key=Key, value=Value, handler=Callback, config=File, ref=ct_util:ct_make_ref()}); - RowsToUpdate -> % else update all occurrencies of the key + RowsToUpdate -> Inserter = fun(Row)-> ets:insert(?attr_table, Row#ct_conf{value=Value, @@ -285,7 +273,6 @@ rewrite_config(Config, Callback, File)-> end end, - % run it key-by-key from the new config [Updater({Key, Value})||{Key, Value}<-Config]. set_config(Config,Default) -> @@ -438,23 +425,12 @@ update_conf(Name, NewConfig) -> end, Old), ok. -remove_duplicates([], Acc)-> - Acc; -remove_duplicates([{Handler, File}|Rest], Acc)-> - case lists:member({Handler, File}, Acc) of - false-> - remove_duplicates(Rest, [{Handler, File}|Acc]); - true-> - remove_duplicates(Rest, Acc) - end. - reload_conf(KeyOrName) -> case lookup_handler_for_config(KeyOrName) of []-> undefined; HandlerList-> - % if aliases set, config will be reloaded several times - HandlerList2 = remove_duplicates(HandlerList, []), + HandlerList2 = lists:usort(HandlerList), read_config_files_int(HandlerList2, fun rewrite_config/3), get_config(KeyOrName) end. @@ -763,3 +739,19 @@ prepare_config_list(Args)-> [] end, ConfigFiles ++ UserConfigs. + +add_config(Callback, [])-> + read_config_files_int([{Callback, []}], fun store_config/3); +add_config(Callback, [File|_Files]=Config) when is_list(File)-> + lists:foreach(fun(CfgStr)-> + read_config_files_int([{Callback, CfgStr}], fun store_config/3) end, + Config); +add_config(Callback, [C|_]=Config) when is_integer(C)-> + read_config_files_int([{Callback, Config}], fun store_config/3), + ok. + +remove_config(Callback, Config)-> + ets:match_delete(?attr_table, + #ct_conf{handler=Callback, + config=Config,_='_'}), + ok. diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 9a0177c2ec..168225feff 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -134,7 +134,6 @@ testspec_dynamic(Config) when is_list(Config) -> %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- -% {suites, "ct_config_SUITE_data/config/test", config_dynamic_SUITE}. make_spec(DataDir, Filename, Suites, Config)-> {ok, Fd} = file:open(filename:join(DataDir, Filename), [write]), ok = file:write(Fd, @@ -240,4 +239,4 @@ expected_events(config_dynamic_SUITE)-> {?eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} -]. \ No newline at end of file +]. -- cgit v1.2.3 From afff4fa809f4e5dbb5a8dca556f30df825d9c781 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 22 Mar 2010 16:23:42 +0100 Subject: Bug fix: ["config.txt"] /= "config.txt" --- lib/common_test/src/ct_config.erl | 43 ++++++++++++++++++++++++------------- lib/common_test/src/ct_testspec.erl | 2 -- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 6fd89c5617..312dc8782f 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -208,7 +208,9 @@ get_config_file_list(Opts)-> read_config_files(Opts) -> AddCallback = fun(CallBack, [])-> [{CallBack, []}]; - (CallBack, Files)-> + (CallBack, [F|_]=Files) when is_integer(F)-> + [{CallBack, Files}]; + (CallBack, [F|_]=Files) when is_list(F)-> lists:map(fun(X)-> {CallBack, X} end, Files) end, ConfigFiles = case lists:keyfind(config, 1, Opts) of @@ -697,20 +699,31 @@ check_callback_load(Callback)-> end. check_config_files(Configs)-> - lists:keysearch(error, 1, - lists:flatten( - lists:map(fun({Callback, Files})-> - case check_callback_load(Callback) of - {ok, Callback}-> - lists:map(fun(File)-> - Callback:check_parameter(File) - end, - Files); - {error, _}-> - {error, {callback, Callback}} - end - end, - Configs))). + ConfigChecker = fun + ({Callback, [F|_R]=Files})-> + case check_callback_load(Callback) of + {ok, Callback}-> + if + is_integer(F)-> + Callback:check_parameter(Files); + is_list(F)-> + lists:map(fun(File)-> + Callback:check_parameter(File) + end, + Files) + end; + {error, _}-> + {error, {callback, Callback}} + end; + ({Callback, []})-> + case check_callback_load(Callback) of + {ok, Callback}-> + Callback:check_parameter([]); + {error, _}-> + {error, {callback, Callback}} + end + end, + lists:keysearch(error, 1, lists:flatten(lists:map(ConfigChecker, Configs))). prepare_user_configs([ConfigString|UserConfigs], Acc, new)-> prepare_user_configs(UserConfigs, diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index c07de370b0..028c614991 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -306,8 +306,6 @@ get_global([{node,Ref,Node}|Ts],Spec=#testspec{nodes=Refs}) -> get_global([_|Ts],Spec) -> get_global(Ts,Spec); get_global([],Spec) -> Spec. -% TODO probably we can terminate here, if any problem with the filename -% anyway, later ct_run will do it for us :-) get_absfile(Callback, FullName,#testspec{spec_dir=SpecDir}) -> % we need to temporary switch to new cwd here, because % otherwise config files cannot be found -- cgit v1.2.3 From 743d8c90b1f7420fb29077daacf959b018cb4086 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 22 Mar 2010 16:26:54 +0100 Subject: Remove ugly -compile(export_all) --- lib/common_test/src/ct_testspec.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 028c614991..cd7fe04738 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -22,7 +22,6 @@ %%%

This module exports functions that are used within CT to %%% scan and parse test specifikations.

-module(ct_testspec). --compile(export_all). -export([prepare_tests/1, prepare_tests/2, collect_tests_from_list/2, collect_tests_from_list/3, -- cgit v1.2.3 From 497c107bcdb7095f402d4b9884b0bfc8f9bbe97f Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Wed, 24 Mar 2010 11:35:59 +0100 Subject: Add support for user_config in ct_master --- lib/common_test/src/ct.erl | 23 +++++++++++++++++ lib/common_test/src/ct_config.erl | 1 + lib/common_test/src/ct_master.erl | 29 ++++++++++++++++------ lib/common_test/src/ct_testspec.erl | 8 +++--- lib/common_test/src/ct_util.hrl | 3 ++- .../test/ct_config_SUITE_data/config/config.txt | 2 +- 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index caae2326b6..6cc876b42e 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -807,8 +807,31 @@ decrypt_config_file(EncryptFileName, TargetFileName) -> decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile). + +%%%----------------------------------------------------------------- +%%% @spec add_config(Callback, Config) -> +%%% ok | {error, Reason} +%%% Callback = atom() +%%% Config = string() +%%% Reason = term() +%%% +%%% @doc

This function loads configuration variables using the +%%% given callback module and configuration string. Callback module +%%% should be either loaded or present in the code part. Loaded +%%% configuration variables can later be removed using +%%% remove_config/2 function.

add_config(Callback, Config)-> ct_config:add_config(Callback, Config). +%%%----------------------------------------------------------------- +%%% @spec remove_config(Callback, Config) -> +%%% ok +%%% Callback = atom() +%%% Config = string() +%%% Reason = term() +%%% +%%% @doc

This function removes configuration variables (together with +%%% their aliases) which were loaded with specified callback module and +%%% configuration string.

remove_config(Callback, Config)-> ct_config:remove_config(Callback, Config). diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 312dc8782f..6314361b35 100755 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -753,6 +753,7 @@ prepare_config_list(Args)-> end, ConfigFiles ++ UserConfigs. +% TODO: add logging of the loaded configuration file to the CT FW log!!! add_config(Callback, [])-> read_config_files_int([{Callback, []}], fun store_config/3); add_config(Callback, [File|_Files]=Config) when is_list(File)-> diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 7eb2c3cfef..94afccde79 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -49,7 +49,8 @@ %%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} | %%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} | %%% {logdir,LogDir} | {event_handler,EventHandlers} | -%%% {silent_connections,Conns} | {cover,CoverSpecFile} +%%% {silent_connections,Conns} | {cover,CoverSpecFile} | +%%% {userconfig, UserCfgFiles} %%% CfgFiles = string() | [string()] %%% TestDirs = string() | [string()] %%% Suites = atom() | [atom()] @@ -98,8 +99,10 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS), {error,Reason} -> {error,Reason}; TSRec=#testspec{logdir=AllLogDirs, - config=AllCfgFiles, + config=StdCfgFiles, + userconfig=UserCfgFiles, event_handler=AllEvHs} -> + AllCfgFiles = {StdCfgFiles, UserCfgFiles}, RunSkipPerNode = ct_testspec:prepare_tests(TSRec), RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode), run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1) @@ -157,8 +160,10 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) - {error,Reason} -> {error,Reason}; TSRec=#testspec{logdir=AllLogDirs, - config=AllCfgFiles, + config=StdCfgFiles, + userconfig=UserCfgFiles, event_handler=AllEvHs} -> + AllCfgFiles = {StdCfgFiles, UserCfgFiles}, {Run,Skip} = ct_testspec:prepare_tests(TSRec,Node), run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1) end, @@ -180,7 +185,9 @@ run_on_node(TestSpecs,Node) -> -run_all([{Node,Run,Skip}|Rest],AllLogDirs,AllCfgFiles,AllEvHs,NodeOpts,LogDirs,Specs) -> +run_all([{Node,Run,Skip}|Rest],AllLogDirs, + {AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles, + AllEvHs,NodeOpts,LogDirs,Specs) -> LogDir = lists:foldl(fun({N,Dir},_Found) when N == Node -> Dir; @@ -191,11 +198,17 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs,AllCfgFiles,AllEvHs,NodeOpts,LogDirs,S (_Dir,Found) -> Found end,".",AllLogDirs), - CfgFiles = + + StdCfgFiles = lists:foldr(fun({N,F},Fs) when N == Node -> [F|Fs]; ({_N,_F},Fs) -> Fs; (F,Fs) -> [F|Fs] - end,[],AllCfgFiles), + end,[],AllStdCfgFiles), + UserCfgFiles = + lists:foldr(fun({N,F},Fs) when N == Node -> [{userconfig, F}|Fs]; + ({_N,_F},Fs) -> Fs; + (F,Fs) -> [{userconfig, F}|Fs] + end,[],AllUserCfgFiles), EvHs = lists:foldr(fun({N,H,A},Hs) when N == Node -> [{H,A}|Hs]; ({_N,_H,_A},Hs) -> Hs; @@ -203,8 +216,8 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs,AllCfgFiles,AllEvHs,NodeOpts,LogDirs,S end,[],AllEvHs), NO = {Node,[{prepared_tests,{Run,Skip},Specs}, {logdir,LogDir}, - {config,CfgFiles}, - {event_handler,EvHs}]}, + {config,StdCfgFiles}, + {event_handler,EvHs}] ++ UserCfgFiles}, run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],Specs); run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,Specs) -> Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master], diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index cd7fe04738..a0da079c54 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -446,8 +446,7 @@ add_tests([{config,Node,[F|Fs]}|Ts],Spec) when is_list(F) -> Cfgs = Spec#testspec.config, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{config,Node,Fs}|Ts], - Spec#testspec{config=[{Node1,{?ct_config_txt, - get_absfile(?ct_config_txt, F,Spec)}}|Cfgs]}); + Spec#testspec{config=[{Node1,get_absfile(F,Spec)}|Cfgs]}); add_tests([{config,_Node,[]}|Ts],Spec) -> add_tests(Ts,Spec); add_tests([{config,Node,F}|Ts],Spec) -> @@ -455,6 +454,7 @@ add_tests([{config,Node,F}|Ts],Spec) -> add_tests([{config,Files}|Ts],Spec) -> add_tests([{config,all_nodes,Files}|Ts],Spec); + %% --- userconfig --- add_tests([{userconfig,all_nodes,CBF}|Ts],Spec) -> Tests = lists:map(fun(N) -> {userconfig,N,CBF} end, list_nodes(Spec)), @@ -463,10 +463,10 @@ add_tests([{userconfig,Nodes,CBF}|Ts],Spec) when is_list(Nodes) -> Ts1 = separate(Nodes,userconfig,[CBF],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); add_tests([{userconfig,Node,[{Callback, Config}|CBF]}|Ts],Spec) -> - Cfgs = Spec#testspec.config, + Cfgs = Spec#testspec.userconfig, Node1 = ref2node(Node,Spec#testspec.nodes), add_tests([{userconfig,Node,CBF}|Ts], - Spec#testspec{config=[{Node1,{Callback, + Spec#testspec{userconfig=[{Node1,{Callback, get_absfile(Callback, Config ,Spec)}}|Cfgs]}); add_tests([{userconfig,_Node,[]}|Ts],Spec) -> add_tests(Ts,Spec); diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index fff59f3fde..c2f51dfba5 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -32,6 +32,7 @@ logdir=["."], cover=[], config=[], + userconfig=[], event_handler=[], include=[], alias=[], @@ -50,4 +51,4 @@ -define(CT_MEVMGR_REF, ct_master_event). -define(missing_suites_info, "missing_suites.info"). --define(ct_config_txt, ct_config_plain). \ No newline at end of file +-define(ct_config_txt, ct_config_plain). diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.txt b/lib/common_test/test/ct_config_SUITE_data/config/config.txt index 0424dbf92e..fcbffcd7f3 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/config.txt +++ b/lib/common_test/test/ct_config_SUITE_data/config/config.txt @@ -28,4 +28,4 @@ {o, 'O'} ]} ]} - ]}. \ No newline at end of file + ]}. -- cgit v1.2.3 From 63b4900da90cd0920b03a9a3dc5f8d8fdbf61ddf Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Wed, 24 Mar 2010 12:42:39 +0100 Subject: Separate config and user_config in test specifications This is now properly supported in the ct_run. --- lib/common_test/src/ct_run.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 92c2334a80..5fd89bc499 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -829,6 +829,7 @@ run_testspec1(TestSpec) -> get_data_for_node(#testspec{logdir=LogDirs, cover=CoverFs, config=Cfgs, + userconfig=UsrCfgs, event_handler=EvHs, include=Incl}, Node) -> LogDir = case lists:keysearch(Node,1,LogDirs) of @@ -839,7 +840,8 @@ get_data_for_node(#testspec{logdir=LogDirs, {value,{Node,CovFile}} -> CovFile; false -> undef end, - ConfigFiles = [F || {N,F} <- Cfgs, N==Node], + 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], Include = [I || {N,I} <- Incl, N==Node], {LogDir,Cover,ConfigFiles,EvHandlers,Include}. -- cgit v1.2.3 From 2c711e660df6683eee9cdfeb204e02f093153c36 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 25 Mar 2010 11:42:26 +0100 Subject: Add test suites for ct_master Also includes minor documentation updates. --- lib/common_test/doc/src/run_test.xml | 14 +- lib/common_test/doc/src/run_test_chapter.xml | 3 + lib/common_test/src/ct.erl | 3 + lib/common_test/test/Makefile | 3 +- lib/common_test/test/ct_config_SUITE.erl | 4 - .../config/test/config_dynamic_SUITE.erl | 30 +--- .../config/test/config_static_SUITE.erl | 30 +--- lib/common_test/test/ct_master_SUITE.erl | 191 +++++++++++++++++++++ .../test/ct_master_SUITE_data/master/config.txt | 2 + .../test/ct_master_SUITE_data/master/config.xml | 4 + .../ct_master_SUITE_data/master/master_SUITE.erl | 57 ++++++ lib/common_test/test/ct_test_support.erl | 11 +- 12 files changed, 284 insertions(+), 68 deletions(-) create mode 100644 lib/common_test/test/ct_master_SUITE.erl create mode 100644 lib/common_test/test/ct_master_SUITE_data/master/config.txt create mode 100644 lib/common_test/test/ct_master_SUITE_data/master/config.xml create mode 100644 lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl diff --git a/lib/common_test/doc/src/run_test.xml b/lib/common_test/doc/src/run_test.xml index d9dd22d411..451d3c5737 100644 --- a/lib/common_test/doc/src/run_test.xml +++ b/lib/common_test/doc/src/run_test.xml @@ -4,7 +4,7 @@
- 20072009 + 20072010 Ericsson AB. All Rights Reserved. @@ -27,8 +27,8 @@ - 2007-07-04 - PA1 + 2010-04-01 + PA2 run_test.xml
run_test @@ -61,6 +61,8 @@ [[-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]]] [-step [config | keep_inactive]] [-config ConfigFile1 ConfigFile2 .. ConfigFileN] + [-userconfig CallbackModule1 ConfigString1 and CallbackModule2 + ConfigString2 and .. and CallbackModuleN ConfigStringN] [-decrypt_key Key] | [-decrypt_file KeyFile] [-logdir LogDir] [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]] @@ -79,6 +81,8 @@
 	run_test -spec TestSpec1 TestSpec2 .. TestSpecN
 	[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
+	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+    ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
 	[-logdir LogDir]
 	[-allow_user_terms]
@@ -98,6 +102,8 @@
     
         run_test -vts [-browser Browser]
 	[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
+	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+    ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
         [-dir TestDir1 TestDir2 .. TestDirN] |
         [-suite Suite [[-group Group] [-case Case]]]
@@ -115,6 +121,8 @@
     
 	run_test -shell 
 	[-config ConfigFile1 ConfigFile2 ... ConfigFileN]
+	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+     ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index b7e9ab88e3..00cf4adf52 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -370,6 +370,9 @@ {config, ConfigFiles}. {config, NodeRefs, ConfigFiles}. + + {userconfig, {CallbackModule, ConfigStrings}}. + {userconfig, NodeRefs, {CallbackModule, ConfigStrings}}. {alias, DirAlias, Dir}. diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 6cc876b42e..c32075332b 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -393,6 +393,9 @@ get_config(Required,Default,Opts) -> %%%

This function performs updating of the configuration data from which the %%% given configuration variable was read, and returns the (possibly) new %%% value of this variable.

+%%%

Note that if some variables were present in the configuration but are not loaded +%%% using this function, they will be removed from the configuration table together +%%% with their aliases.

%%% reload_config(Required)-> ct_config:reload_config(Required). diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 2b0a06871e..eca6817682 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -34,7 +34,8 @@ MODULES= \ ct_skip_SUITE \ ct_error_SUITE \ ct_test_server_if_1_SUITE \ - ct_config_SUITE + ct_config_SUITE \ + ct_master_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 168225feff..7c0c88e76b 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -69,13 +69,9 @@ all(suite) -> testspec_dynamic ]. - %%-------------------------------------------------------------------- %% TEST CASES %%-------------------------------------------------------------------- - -%%%----------------------------------------------------------------- -%%% require(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), run_test(config_static_SUITE, diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl index ae66d4e9bf..b7b2e8f16c 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl @@ -18,7 +18,7 @@ %% %%%------------------------------------------------------------------- -%%% File: ct_config_SUITE +%%% File: config_dynamic_SUITE %%% %%% Description: %%% Test suite for common_test which tests the userconfig functionality @@ -46,33 +46,7 @@ suite() -> {timetrap, {seconds,10}} ]. -% to get it running on development branch (without userconfig features) -% function to print full config is in the ct_util, for me it's moved to ct_config -% two following functions are only for the design period -% after merging of userconfigs to the main branch ct_config:get_all_config/0 -% should be called instead -is_exported(Module, Function, Arity)-> - Exports = Module:module_info(exports), - case lists:keyfind(Function, 1, Exports) of - false-> - false; - {Function, Arity}-> - true; - {Function, _OtherArity}-> - false - end. - -get_all_config()-> - case is_exported(ct_util, get_all_config, 0) of - true-> - {ct_util, ct_util:get_all_config()}; - false-> - {ct_config, ct_config:get_all_config()} - end. - init_per_suite(Config) -> - %{Module, Cfg} = get_all_config(), - %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_suite(_) -> @@ -83,8 +57,6 @@ all() -> [test_get_known_variable, test_localtime_update, test_disappearable_variable_alias]. init_per_testcase(_, Config) -> - %{Module, Cfg} = get_all_config(), - %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_testcase(_, _) -> diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl index d3f07980bc..262417fe1c 100644 --- a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl @@ -18,7 +18,7 @@ %% %%%------------------------------------------------------------------- -%%% File: config_1_SUITE +%%% File: config_static_SUITE %%% %%% Description: %%% Test suite for common_test which tests the get_config and require @@ -49,33 +49,7 @@ suite() -> {x1, {x,suite}} ]. -% to get it running on development branch (without userconfig features) -% function to print full config is in the ct_util, for me it's moved to ct_config -% two following functions are only for the design period -% after merging of userconfigs to the main branch ct_config:get_all_config/0 -% should be called instead -is_exported(Module, Function, Arity)-> - Exports = Module:module_info(exports), - case lists:keyfind(Function, 1, Exports) of - false-> - false; - {Function, Arity}-> - true; - {Function, _OtherArity}-> - false - end. - -get_all_config()-> - case is_exported(ct_util, get_all_config, 0) of - true-> - {ct_util, ct_util:get_all_config()}; - false-> - {ct_config, ct_config:get_all_config()} - end. - init_per_suite(Config) -> - %{Module, Cfg} = get_all_config(), - %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_suite(_) -> @@ -87,8 +61,6 @@ all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide test_get_config_undefined]. init_per_testcase(_, Config) -> - %{Module, Cfg} = get_all_config(), - %ct:pal("CONFIG (handled by ~p):~n~p", [Module, Cfg]), Config. end_per_testcase(_, _) -> diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl new file mode 100644 index 0000000000..a2eaf98e34 --- /dev/null +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -0,0 +1,191 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_master_SUITE +%%% +%%% Description: +%%% Test ct_master. +%%% +%%% The suites used for the test are located in the data directory. +%%%------------------------------------------------------------------- +-module(ct_master_SUITE). +-compile(export_all). + +-include_lib("test_server/include/test_server.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, [{master, true}|Config]). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +all(doc) -> + [""]; + +all(suite) -> + [ + ct_master_test_peer, + ct_master_test_slave + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- +ct_master_test_peer(Config) when is_list(Config)-> + NodeCount = 5, + DataDir = ?config(data_dir, Config), + NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) || + N <- lists:seq(1, NodeCount)], + FileName = filename:join(DataDir, "ct_master_spec.spec"), + Suites = [master_SUITE], + make_spec(DataDir, FileName, NodeNames, Suites, Config), + start_nodes(NodeNames, peer), + run_test(ct_master_test, FileName, Config), + stop_nodes(NodeNames, peer), + file:delete(filename:join(DataDir, FileName)). + +ct_master_test_slave(Config) when is_list(Config)-> + NodeCount = 5, + DataDir = ?config(data_dir, Config), + NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) || + N <- lists:seq(1, NodeCount)], + FileName = filename:join(DataDir, "ct_master_spec.spec"), + Suites = [master_SUITE], + make_spec(DataDir, FileName, NodeNames, Suites, Config), + start_nodes(NodeNames, slave), + run_test(ct_master_test, FileName, Config), + stop_nodes(NodeNames, slave), + file:delete(filename:join(DataDir, FileName)). + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- +make_spec(DataDir, FileName, NodeNames, Suites, Config)-> + {ok, HostName} = inet:gethostname(), + + N = lists:map(fun(NodeName)-> + {node, NodeName, enodename(HostName, NodeName)} + end, + NodeNames), + + C = lists:map(fun(NodeName)-> + Rnd = random:uniform(2), + if Rnd == 1-> + {config, NodeName, "master/config.txt"}; + true-> + {userconfig, NodeName, {ct_config_xml, "master/config.xml"}} + end + end, + NodeNames), + + S = [{suites, NodeNames, filename:join(DataDir, "master"), Suites}], + + PrivDir = ?config(priv_dir, Config), + LD = [{logdir, PrivDir}, {logdir, master, PrivDir}], + + ct_test_support:write_testspec(N++C++S++LD, DataDir, FileName). + +run_test(_Name, FileName, Config)-> + [{FileName, ok}] = ct_test_support:run(ct_master, run, [FileName], Config). + +wait_for_node_alive(_Node, 0)-> + pang; +wait_for_node_alive(Node, N)-> + timer:sleep(1000), + case net_adm:ping(Node) of + pong-> + pong; + pang-> + wait_for_node_alive(Node, N-1) + end. + +wait_for_node_dead(_Node, 0)-> + error; +wait_for_node_dead(Node, N)-> + timer:sleep(1000), + case lists:member(Node, nodes()) of + true-> + wait_for_node_dead(Node, N-1); + false-> + ok + end. + +enodename(HostName, NodeName)-> + list_to_atom(atom_to_list(NodeName)++"@"++HostName). + +start_node(HostName, NodeName, peer)-> + ENodeName = enodename(HostName, NodeName), + Cmd = "erl -detached -noinput -sname "++atom_to_list(NodeName), + open_port({spawn, Cmd}, [stream]), + pong = wait_for_node_alive(ENodeName, 3); +start_node(HostName, NodeName, slave)-> + ENodeName = enodename(HostName, NodeName), + {ok, ENodeName} = + slave:start(list_to_atom(HostName), NodeName). + +stop_node(HostName, NodeName, peer)-> + ENodeName = enodename(HostName, NodeName), + spawn(ENodeName, init, stop, []), + wait_for_node_dead(ENodeName, 3); +stop_node(HostName, NodeName, slave)-> + ENodeName = enodename(HostName, NodeName), + ok = slave:stop(ENodeName). + +start_nodes(NodeNames, Type)-> + {ok, HostName} = inet:gethostname(), + lists:foreach(fun(NodeName)-> + start_node(HostName, NodeName, Type) + end, + NodeNames). + +stop_nodes(NodeNames, Type)-> + {ok, HostName} = inet:gethostname(), + lists:foreach(fun(NodeName)-> + stop_node(HostName, NodeName, Type) + end, + NodeNames). + +reformat_events(Events, EH) -> + ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +expected_events(_)-> +[]. diff --git a/lib/common_test/test/ct_master_SUITE_data/master/config.txt b/lib/common_test/test/ct_master_SUITE_data/master/config.txt new file mode 100644 index 0000000000..3baf9e392c --- /dev/null +++ b/lib/common_test/test/ct_master_SUITE_data/master/config.txt @@ -0,0 +1,2 @@ +{a, b}. +{c, d}. diff --git a/lib/common_test/test/ct_master_SUITE_data/master/config.xml b/lib/common_test/test/ct_master_SUITE_data/master/config.xml new file mode 100644 index 0000000000..c031f45f35 --- /dev/null +++ b/lib/common_test/test/ct_master_SUITE_data/master/config.xml @@ -0,0 +1,4 @@ + + b + d + diff --git a/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl new file mode 100644 index 0000000000..1d05d1ac9a --- /dev/null +++ b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl @@ -0,0 +1,57 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: master_SUITE +%%% +%%% Description: +%%% Test suite for common_test which tests the ct_master functionality +%%%------------------------------------------------------------------- +-module(master_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +suite() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(_) -> + ok. + +all() -> [first_testcase, second_testcase, third_testcase]. + +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(_, _) -> + ok. + +first_testcase(_)-> + b = ct:get_config(a). + +second_testcase(_)-> + d = ct:get_config(c). + +third_testcase(_)-> + A = 4, + A = 2*2. diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 4aa750c37f..0ce103e111 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -102,9 +102,16 @@ end_per_suite(Config) -> init_per_testcase(_TestCase, Config) -> {_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)), - test_server:format("See Common Test logs here:\n" + case lists:keysearch(master, 1, Config) of + false-> + test_server:format("See Common Test logs here:\n" "~s/all_runs.html", - [LogDir,LogDir]), + [LogDir,LogDir]); + {value, _}-> + test_server:format("See CT Master Test logs here:\n" + "~s/master_runs.html", + [LogDir,LogDir]) + end, Config. %%%----------------------------------------------------------------- -- cgit v1.2.3 From dbecad9f8004f0a9b53ebdeb1ce8bde35dca7663 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 8 Apr 2010 18:56:40 +0200 Subject: Introduce ct_slave module --- lib/common_test/doc/src/Makefile | 3 +- lib/common_test/doc/src/ref_man.xml | 1 + lib/common_test/src/Makefile | 3 +- lib/common_test/src/common_test.app.src | 4 +- lib/common_test/src/ct_slave.erl | 416 +++++++++++++++++++++++++++++++ lib/common_test/test/ct_master_SUITE.erl | 91 ++----- 6 files changed, 444 insertions(+), 74 deletions(-) create mode 100644 lib/common_test/src/ct_slave.erl diff --git a/lib/common_test/doc/src/Makefile b/lib/common_test/doc/src/Makefile index a2c014418d..6322860088 100644 --- a/lib/common_test/doc/src/Makefile +++ b/lib/common_test/doc/src/Makefile @@ -45,7 +45,8 @@ CT_MODULES = \ ct_ssh \ ct_rpc \ ct_snmp \ - unix_telnet + unix_telnet \ + ct_slave CT_XML_FILES = $(CT_MODULES:=.xml) diff --git a/lib/common_test/doc/src/ref_man.xml b/lib/common_test/doc/src/ref_man.xml index beb3ed3247..5f84f75acb 100644 --- a/lib/common_test/doc/src/ref_man.xml +++ b/lib/common_test/doc/src/ref_man.xml @@ -75,6 +75,7 @@ + diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile index 4c4382b705..76646c8623 100644 --- a/lib/common_test/src/Makefile +++ b/lib/common_test/src/Makefile @@ -66,7 +66,8 @@ MODULES= \ unix_telnet \ ct_config \ ct_config_plain \ - ct_config_xml + ct_config_xml \ + ct_slave TARGET_MODULES= $(MODULES:%=$(EBIN)/%) diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src index 2f599a7e30..b00aafc87e 100644 --- a/lib/common_test/src/common_test.app.src +++ b/lib/common_test/src/common_test.app.src @@ -45,10 +45,12 @@ vts, ct_config, ct_config_plain, - ct_config_xml + ct_config_xml, + ct_slave ]}, {registered, [ct_logs, ct_util_server, + ct_config_server, ct_make_ref, vts, ct_master, diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl new file mode 100644 index 0000000000..57170220b8 --- /dev/null +++ b/lib/common_test/src/ct_slave.erl @@ -0,0 +1,416 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%%---------------------------------------------------------------------- +%% File : ct_slave.erl +%% Description : CT module for starting nodes for large-scale testing. +%% +%% Created : 7 April 2010 +%%---------------------------------------------------------------------- +-module(ct_slave). + +-export([start/1, start/2, start/3, stop/1, stop/2]). + +-export([slave_started/2, slave_ready/2, monitor_master/1]). + +-record(options, {username, password, boot_timeout, init_timeout, + startup_timeout, startup_functions, monitor_master, + kill_if_fail}). + +%%%----------------------------------------------------------------- +%%% @spec start(Node) -> Result +%%% Node = atom() +%%% Result = {ok, NodeName} | +%%% {error, already_started, NodeName} | +%%% {error, started_not_connected, NodeName} | +%%% {error, boot_timeout, NodeName} | +%%% {error, init_timeout, NodeName} | +%%% {error, startup_timeout, NodeName} +%%% NodeName = atom() +%%% @doc Starts an Erlang node with name Node on the local host. +%%% @see start/3 +start(Node)-> + start(gethostname(), Node). + +%%%----------------------------------------------------------------- +%%% @spec start(Host, Node) -> Result +%%% Node = atom() +%%% Host = atom() +%%% Result = {ok, NodeName} | +%%% {error, already_started, NodeName} | +%%% {error, started_not_connected, NodeName} | +%%% {error, boot_timeout, NodeName} | +%%% {error, init_timeout, NodeName} | +%%% {error, startup_timeout, NodeName} +%%% NodeName = atom() +%%% @doc Starts an Erlang node with name Node on host +%%% Host with the default options. +%%% @see start/3 +start(Host, Node)-> + start(Host, Node, []). + +%%%----------------------------------------------------------------- +%%% @spec start(Host, Node, Opts) -> Result +%%% Node = atom() +%%% Host = atom() +%%% Opts = [OptTuples] +%%% OptTuples = {username, Username} | +%%% {password, Password} | +%%% {boot_timeout, BootTimeout} | {init_timeout, InitTimeout} | +%%% {startup_timeout, StartupTimeout} | +%%% {startup_functions, StartupFunctions} | +%%% {monitor_master, Monitor} | +%%% {kill_if_fail, KillIfFail} +%%% Username = string() +%%% Password = string() +%%% BootTimeout = integer() +%%% InitTimeout = integer() +%%% StartupTimeout = integer() +%%% StartupFunctions = [StartupFunctionSpec] +%%% StartupFunctionSpec = {Module, Function, Arguments} +%%% Module = atom() +%%% Function = atom() +%%% Arguments = [term] +%%% Monitor = bool +%%% KillIfFail = bool +%%% Result = {ok, NodeName} | {error, already_started, NodeName} | +%%% {error, started_not_connected, NodeName} | +%%% {error, boot_timeout, NodeName} | +%%% {error, init_timeout, NodeName} | +%%% {error, startup_timeout, NodeName} +%%% NodeName = atom() +%%% @doc Starts an Erlang node with name Node on host +%%% Host as specified by the combination of options in +%%% Opts. +%%% +%%%

Options Username and Password will be used +%%% to log in onto the remote host Host. +%%% Username, if omitted, defaults to the current user name, +%%% and password is empty by default.

+%%% +%%%

A list of functions specified in the Startup option will be +%%% executed after startup of the node. Note that all used modules should be +%%% present in the code path on the Host.

+%%% +%%%

The timeouts are applied as follows: +%%% +%%% +%%% BootTimeout - time to start the Erlang node, in seconds. +%%% Defaults to 3 seconds. If node does not become pingable within this time, +%%% the result {error, boot_timeout, NodeName} is returned; +%%% +%%% +%%% InitTimeout - time to wait for the node until it calls the +%%% internal callback function informing master about successfull startup. +%%% Defaults to one second. +%%% In case of timed out message the result +%%% {error, init_timeout, NodeName} is returned; +%%% +%%% +%%% StartupTimeout - time to wait intil the node finishes to run +%%% the StartupFunctions. Defaults to one second. +%%% If this timeout occurs, the result +%%% {error, startup_timeout, NodeName} is returned. +%%% +%%%

+%%% +%%%

Option monitor_master specifies, if the slave node should be +%%% stopped in case of master node stop. Defaults to false

+%%% +%%%

Option kill_if_fail specifies, if the slave node should be +%%% killed in case of a timeout during initialization or startup. +%%% Defaults to true. Note that node also may be still alive it the boot +%%% timeout occurred, but it will not be killed in this case.

+%%% +%%%

Special return values are: +%%% +%%% {error, already_started, NodeName} - if the node with +%%% the given name is already started on a given host; +%%% {error, started_not_connected, NodeName} - if node is +%%% started, but not connected to the master node. +%%%

+%%% +start(Host, Node, Options)-> + ENode = enodename(Host, Node), + case net_kernel:longnames() of + ignored-> + {error, not_alive}; + _-> + case is_started(ENode) of + false-> + OptionsRec = fetch_options(Options), + do_start(Host, Node, OptionsRec); + {true, not_connected}-> + {error, started_not_connected, ENode}; + {true, connected}-> + {error, already_started, ENode} + end + end. + +%%% @spec stop(Node) -> Result +%%% Node = atom() +%%% Result = {ok, NodeName} | +%%% {error, not_started, NodeName} | +%%% {error, not_connected, NodeName} | +%%% {error, stop_timeout, NodeName} +%%% NodeName = atom() +%%% @doc Stops the running Erlang node with name Node on +%%% the localhost. +stop(Node)-> + stop(gethostname(), Node). + +%%% @spec stop(Host, Node) -> Result +%%% Host = atom() +%%% Node = atom() +%%% Result = {ok, NodeName} | +%%% {error, not_started, NodeName} | +%%% {error, not_connected, NodeName} | +%%% {error, stop_timeout, NodeName} +%%% NodeName = atom() +%%% @doc Stops the running Erlang node with name Node on +%%% host Host. +stop(Host, Node)-> + ENode = enodename(Host, Node), + case is_started(ENode) of + {true, connected}-> + do_stop(ENode); + {true, not_connected}-> + {error, not_connected, ENode}; + false-> + {error, not_started, ENode} + end. + +%%% fetch an option value from the tagged tuple list with default +get_option_value(Key, OptionList, Default)-> + case lists:keyfind(Key, 1, OptionList) of + false-> + Default; + {Key, Value}-> + Value + end. + +%%% convert option list to the option record, fill all defaults +fetch_options(Options)-> + UserName = get_option_value(username, Options, []), + Password = get_option_value(password, Options, []), + BootTimeout = get_option_value(boot_timeout, Options, 3), + InitTimeout = get_option_value(init_timeout, Options, 1), + StartupTimeout = get_option_value(startup_timeout, Options, 1), + StartupFunctions = get_option_value(startup_functions, Options, []), + Monitor = get_option_value(monitor_master, Options, false), + KillIfFail = get_option_value(kill_if_fail, Options, true), + #options{username=UserName, password=Password, + boot_timeout=BootTimeout, init_timeout=InitTimeout, + startup_timeout=StartupTimeout, startup_functions=StartupFunctions, + monitor_master=Monitor, kill_if_fail=KillIfFail}. + +% send a message when slave node is started +% @hidden +slave_started(ENode, MasterPid)-> + MasterPid ! {node_started, ENode}, + ok. + +% send a message when slave node has finished startup +% @hidden +slave_ready(ENode, MasterPid)-> + MasterPid ! {node_ready, ENode}, + ok. + +% start monitoring of the master node +% @hidden +monitor_master(MasterNode)-> + spawn(fun()->monitor_master_int(MasterNode) end). + +% code of the masterdeath-waiter process +monitor_master_int(MasterNode)-> + erlang:monitor_node(MasterNode, true), + receive + {nodedown, MasterNode}-> + init:stop() + end. + +% check if node is listed in the nodes() +is_connected(ENode)-> + [N||N<-nodes(), N==ENode] == [ENode]. + +% check if node is alive (ping and disconnect if pingable) +is_started(ENode)-> + case is_connected(ENode) of + true-> + {true, connected}; + false-> + case net_adm:ping(ENode) of + pang-> + false; + pong-> + erlang:disconnect_node(ENode), + {true, not_connected} + end + end. + +% make a Erlang node name from name and hostname +enodename(Host, Node)-> + list_to_atom(atom_to_list(Node)++"@"++atom_to_list(Host)). + +% performs actual start of the "slave" node +do_start(Host, Node, Options)-> + ENode = enodename(Host, Node), + Functions = + lists:concat([[{ct_slave, slave_started, [ENode, self()]}], + Options#options.startup_functions, + [{ct_slave, slave_ready, [ENode, self()]}]]), + Functions2 = if + Options#options.monitor_master-> + [{ct_slave, monitor_master, [node()]}|Functions]; + true-> + Functions + end, + MasterHost = gethostname(), + if + MasterHost == Host -> + spawn_local_node(Node); + true-> + spawn_remote_node(Host, Node, Options) + end, + BootTimeout = Options#options.boot_timeout, + InitTimeout = Options#options.init_timeout, + StartupTimeout = Options#options.startup_timeout, + Result = case wait_for_node_alive(ENode, BootTimeout) of + pong-> + call_functions(ENode, Functions2), + receive + {node_started, ENode}-> + receive + {node_ready, ENode}-> + {ok, ENode} + after StartupTimeout*1000-> + {error, startup_timeout, ENode} + end + after InitTimeout*1000 -> + {error, init_timeout, ENode} + end; + pang-> + {error, boot_timeout, ENode} + end, + case Result of + {ok, ENode}-> + ok; + {error, Timeout, ENode} + when ((Timeout==init_timeout) or (Timeout==startup_timeout)) and + Options#options.kill_if_fail-> + do_stop(ENode); + _-> ok + end, + Result. + +% are we using fully qualified hostnames +long_or_short()-> + case net_kernel:longnames() of + true-> + "-name"; + false-> + "-sname" + end. + +% get the localhost's name, depending on the using name policy +gethostname()-> + Hostname = case net_kernel:longnames() of + true-> + net_adm:localhost(); + _-> + {ok, Name}=inet:gethostname(), + Name + end, + list_to_atom(Hostname). + +% get cmd for starting Erlang +get_cmd(Node)-> + "erl -detached -noinput "++ long_or_short() ++ " " ++ atom_to_list(Node). + +% spawn node locally +spawn_local_node(Node)-> + Cmd = get_cmd(Node), + %io:format("Running cmd: ~p~n", [Cmd]), + open_port({spawn, Cmd}, [stream]). + +% start crypto and ssh if not yet started +check_for_ssh_running()-> + case application:get_application(crypto) of + undefined-> + application:start(crypto), + case application:get_application(ssh) of + undefined-> + application:start(ssh); + {ok, ssh}-> + ok + end; + {ok, crypto}-> + ok + end. + +% spawn node remotely +spawn_remote_node(Host, Node, Options)-> + Username = Options#options.username, + Password = Options#options.password, + SSHOptions = case {Username, Password} of + {[], []}-> + []; + {_, []}-> + [{user, Username}]; + {_, _}-> + [{user, Username}, {password, Password}] + end ++ [{silently_accept_hosts, true}], + check_for_ssh_running(), + {ok, SSHConnRef} = ssh:connect(atom_to_list(Host), 22, SSHOptions), + {ok, SSHChannelId} = ssh_connection:session_channel(SSHConnRef, infinity), + ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node), infinity). + +% call functions on a remote Erlang node +call_functions(_Node, [])-> + ok; +call_functions(Node, [{M, F, A}|Functions])-> + rpc:call(Node, M, F, A), + call_functions(Node, Functions). + +% wait N seconds until node is pingable +wait_for_node_alive(_Node, 0)-> + pang; +wait_for_node_alive(Node, N)-> + timer:sleep(1000), + case net_adm:ping(Node) of + pong-> + pong; + pang-> + wait_for_node_alive(Node, N-1) + end. + +% call init:stop on a remote node +do_stop(ENode)-> + spawn(ENode, init, stop, []), + wait_for_node_dead(ENode, 5). + +% wait N seconds until node is disconnected +wait_for_node_dead(Node, 0)-> + {error, stop_timeout, Node}; +wait_for_node_dead(Node, N)-> + timer:sleep(1000), + case lists:member(Node, nodes()) of + true-> + wait_for_node_dead(Node, N-1); + false-> + {ok, Node} + end. diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index a2eaf98e34..ebd399ad64 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -60,37 +60,23 @@ all(doc) -> all(suite) -> [ - ct_master_test_peer, - ct_master_test_slave + ct_master_test ]. %%-------------------------------------------------------------------- %% TEST CASES %%-------------------------------------------------------------------- -ct_master_test_peer(Config) when is_list(Config)-> +ct_master_test(Config) when is_list(Config)-> NodeCount = 5, DataDir = ?config(data_dir, Config), NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) || N <- lists:seq(1, NodeCount)], FileName = filename:join(DataDir, "ct_master_spec.spec"), Suites = [master_SUITE], - make_spec(DataDir, FileName, NodeNames, Suites, Config), - start_nodes(NodeNames, peer), - run_test(ct_master_test, FileName, Config), - stop_nodes(NodeNames, peer), - file:delete(filename:join(DataDir, FileName)). - -ct_master_test_slave(Config) when is_list(Config)-> - NodeCount = 5, - DataDir = ?config(data_dir, Config), - NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) || - N <- lists:seq(1, NodeCount)], - FileName = filename:join(DataDir, "ct_master_spec.spec"), - Suites = [master_SUITE], - make_spec(DataDir, FileName, NodeNames, Suites, Config), - start_nodes(NodeNames, slave), - run_test(ct_master_test, FileName, Config), - stop_nodes(NodeNames, slave), + TSFile = make_spec(DataDir, FileName, NodeNames, Suites, Config), + start_nodes(NodeNames), + [{TSFile, ok}] = run_test(ct_master_test, FileName, Config), + stop_nodes(NodeNames), file:delete(filename:join(DataDir, FileName)). %%%----------------------------------------------------------------- @@ -100,7 +86,7 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> {ok, HostName} = inet:gethostname(), N = lists:map(fun(NodeName)-> - {node, NodeName, enodename(HostName, NodeName)} + {node, NodeName, list_to_atom(atom_to_list(NodeName)++"@"++HostName)} end, NodeNames), @@ -117,67 +103,30 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> S = [{suites, NodeNames, filename:join(DataDir, "master"), Suites}], PrivDir = ?config(priv_dir, Config), - LD = [{logdir, PrivDir}, {logdir, master, PrivDir}], + LD = lists:map(fun(NodeName)-> + {logdir, NodeName, get_log_dir(PrivDir, NodeName)} + end, + NodeNames) ++ [{logdir, master, PrivDir}], ct_test_support:write_testspec(N++C++S++LD, DataDir, FileName). +get_log_dir(PrivDir, NodeName)-> + LogDir = filename:join(PrivDir, io_lib:format("slave.~p", [NodeName])), + file:make_dir(LogDir), + LogDir. + run_test(_Name, FileName, Config)-> [{FileName, ok}] = ct_test_support:run(ct_master, run, [FileName], Config). -wait_for_node_alive(_Node, 0)-> - pang; -wait_for_node_alive(Node, N)-> - timer:sleep(1000), - case net_adm:ping(Node) of - pong-> - pong; - pang-> - wait_for_node_alive(Node, N-1) - end. - -wait_for_node_dead(_Node, 0)-> - error; -wait_for_node_dead(Node, N)-> - timer:sleep(1000), - case lists:member(Node, nodes()) of - true-> - wait_for_node_dead(Node, N-1); - false-> - ok - end. - -enodename(HostName, NodeName)-> - list_to_atom(atom_to_list(NodeName)++"@"++HostName). - -start_node(HostName, NodeName, peer)-> - ENodeName = enodename(HostName, NodeName), - Cmd = "erl -detached -noinput -sname "++atom_to_list(NodeName), - open_port({spawn, Cmd}, [stream]), - pong = wait_for_node_alive(ENodeName, 3); -start_node(HostName, NodeName, slave)-> - ENodeName = enodename(HostName, NodeName), - {ok, ENodeName} = - slave:start(list_to_atom(HostName), NodeName). - -stop_node(HostName, NodeName, peer)-> - ENodeName = enodename(HostName, NodeName), - spawn(ENodeName, init, stop, []), - wait_for_node_dead(ENodeName, 3); -stop_node(HostName, NodeName, slave)-> - ENodeName = enodename(HostName, NodeName), - ok = slave:stop(ENodeName). - -start_nodes(NodeNames, Type)-> - {ok, HostName} = inet:gethostname(), +start_nodes(NodeNames)-> lists:foreach(fun(NodeName)-> - start_node(HostName, NodeName, Type) + {ok, _}=ct_slave:start(NodeName) end, NodeNames). -stop_nodes(NodeNames, Type)-> - {ok, HostName} = inet:gethostname(), +stop_nodes(NodeNames)-> lists:foreach(fun(NodeName)-> - stop_node(HostName, NodeName, Type) + {ok, _}=ct_slave:stop(NodeName) end, NodeNames). -- cgit v1.2.3 From 2a8a08a1752392a5e9e745c2a0929642812333e0 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Tue, 11 May 2010 17:54:13 +0200 Subject: Modify cookie handling in ct_slave --- lib/common_test/src/ct_slave.erl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 57170220b8..bf7ee8863c 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -321,9 +321,9 @@ do_start(Host, Node, Options)-> long_or_short()-> case net_kernel:longnames() of true-> - "-name"; + " -name "; false-> - "-sname" + " -sname " end. % get the localhost's name, depending on the using name policy @@ -339,7 +339,9 @@ gethostname()-> % get cmd for starting Erlang get_cmd(Node)-> - "erl -detached -noinput "++ long_or_short() ++ " " ++ atom_to_list(Node). + Cookie = erlang:get_cookie(), + "erl -detached -noinput -setcookie "++ atom_to_list(Cookie) ++ + long_or_short() ++ atom_to_list(Node). % spawn node locally spawn_local_node(Node)-> -- cgit v1.2.3 From 05f69de43fc8f95baab30e940cd80df86a433e1e Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 12 Apr 2010 15:46:16 +0200 Subject: Improve eval and node_start and add new options for ct_slave --- lib/common_test/src/ct_master.erl | 49 ++++++++++----- lib/common_test/src/ct_slave.erl | 54 ++++++++++------ lib/common_test/src/ct_testspec.erl | 105 +++++++++++++++++++++++++++---- lib/common_test/src/ct_util.hrl | 1 + lib/common_test/test/ct_master_SUITE.erl | 21 ++----- 5 files changed, 168 insertions(+), 62 deletions(-) diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 94afccde79..b14e7527e5 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -28,7 +28,7 @@ -export([abort/0,abort/1,progress/0]). --export([init_master/6, init_node_ctrl/3]). +-export([init_master/7, init_node_ctrl/3]). -export([status/2]). @@ -101,11 +101,12 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS), TSRec=#testspec{logdir=AllLogDirs, config=StdCfgFiles, userconfig=UserCfgFiles, + node_start=AllNSOpts, event_handler=AllEvHs} -> AllCfgFiles = {StdCfgFiles, UserCfgFiles}, RunSkipPerNode = ct_testspec:prepare_tests(TSRec), RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode), - run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1) + run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1) end, [{TS,Result} | run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes)]; run([],_,_,_) -> @@ -161,11 +162,12 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) - {error,Reason}; TSRec=#testspec{logdir=AllLogDirs, config=StdCfgFiles, + node_start=AllNSOpts, userconfig=UserCfgFiles, event_handler=AllEvHs} -> AllCfgFiles = {StdCfgFiles, UserCfgFiles}, {Run,Skip} = ct_testspec:prepare_tests(TSRec,Node), - run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1) + run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1) end, [{TS,Result} | run_on_node(TestSpecs,AllowUserTerms,Node)]; run_on_node([],_,_) -> @@ -187,7 +189,7 @@ run_on_node(TestSpecs,Node) -> run_all([{Node,Run,Skip}|Rest],AllLogDirs, {AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles, - AllEvHs,NodeOpts,LogDirs,Specs) -> + AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) -> LogDir = lists:foldl(fun({N,Dir},_Found) when N == Node -> Dir; @@ -214,19 +216,20 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs, ({_N,_H,_A},Hs) -> Hs; ({H,A},Hs) -> [{H,A}|Hs] end,[],AllEvHs), + NO = {Node,[{prepared_tests,{Run,Skip},Specs}, {logdir,LogDir}, {config,StdCfgFiles}, {event_handler,EvHs}] ++ UserCfgFiles}, - run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],Specs); -run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,Specs) -> + run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],NSOptions,Specs); +run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) -> Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master], MasterLogDir = case lists:keysearch(master,1,AllLogDirs) of {value,{_,Dir}} -> Dir; false -> "." end, log(tty,"Master Logdir","~s",[MasterLogDir]), - start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,Specs), + start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,NSOptions,Specs), ok. @@ -264,17 +267,17 @@ progress() -> %%% MASTER, runs on central controlling node. %%%----------------------------------------------------------------- start_master(NodeOptsList) -> - start_master(NodeOptsList,[],".",[],[]). + start_master(NodeOptsList,[],".",[],[],[]). -start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) -> +start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) -> Master = spawn_link(?MODULE,init_master,[self(),NodeOptsList,EvHandlers, - MasterLogDir,LogDirs,Specs]), + MasterLogDir,LogDirs,NSOptions,Specs]), receive {Master,Result} -> Result end. %%% @hidden -init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) -> +init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) -> case whereis(ct_master) of undefined -> register(ct_master,self()), @@ -327,9 +330,10 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) -> Pid when is_pid(Pid) -> ok end, - init_master1(Parent,NodeOptsList,LogDirs). + init_master1(Parent,NodeOptsList,NSOptions,LogDirs). -init_master1(Parent,NodeOptsList,LogDirs) -> +init_master1(Parent,NodeOptsList,NSOptions,LogDirs) -> + start_nodes(NSOptions), {Inaccessible,NodeOptsList1} = ping_nodes(NodeOptsList,[],[]), case Inaccessible of [] -> @@ -344,7 +348,7 @@ init_master1(Parent,NodeOptsList,LogDirs) -> "Proceeding without: ~p",[Inaccessible]), init_master2(Parent,NodeOptsList1,LogDirs); "r\n" -> - init_master1(Parent,NodeOptsList,LogDirs); + init_master1(Parent,NodeOptsList,NSOptions,LogDirs); _ -> log(html,"Aborting Tests","",[]), ct_master_event:stop(), @@ -698,6 +702,23 @@ reply(Result,To) -> To ! {self(),Result}, ok. +start_nodes([])-> + ok; +start_nodes([{NodeName, Options}|Rest])-> + [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"), + Node=list_to_atom(NodeS), + Host=list_to_atom(HostS), + {value, {callback_module, Callback}, Options2}= + lists:keytake(callback_module, 1, Options), + io:format("Starting node ~p on host ~p with callback ~p...~n", [Node, Host, Callback]), + case Callback:start(Host, Node, Options2) of + {ok, NodeName} -> + io:format("Node ~p started successfully~n", [NodeName]); + {error, Reason, _NodeName} -> + io:format("Failed to start node ~p! Reason: ~p~n", [NodeName, Reason]) + end, + start_nodes(Rest). + %cast(Msg) -> % cast(whereis(ct_master),Msg). diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index bf7ee8863c..09d8820731 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -29,7 +29,7 @@ -record(options, {username, password, boot_timeout, init_timeout, startup_timeout, startup_functions, monitor_master, - kill_if_fail}). + kill_if_fail, erl_flags}). %%%----------------------------------------------------------------- %%% @spec start(Node) -> Result @@ -39,7 +39,8 @@ %%% {error, started_not_connected, NodeName} | %%% {error, boot_timeout, NodeName} | %%% {error, init_timeout, NodeName} | -%%% {error, startup_timeout, NodeName} +%%% {error, startup_timeout, NodeName} | +%%% {error, not_alive, NodeName} %%% NodeName = atom() %%% @doc Starts an Erlang node with name Node on the local host. %%% @see start/3 @@ -55,7 +56,8 @@ start(Node)-> %%% {error, started_not_connected, NodeName} | %%% {error, boot_timeout, NodeName} | %%% {error, init_timeout, NodeName} | -%%% {error, startup_timeout, NodeName} +%%% {error, startup_timeout, NodeName} | +%%% {error, not_alive, NodeName} %%% NodeName = atom() %%% @doc Starts an Erlang node with name Node on host %%% Host with the default options. @@ -74,7 +76,8 @@ start(Host, Node)-> %%% {startup_timeout, StartupTimeout} | %%% {startup_functions, StartupFunctions} | %%% {monitor_master, Monitor} | -%%% {kill_if_fail, KillIfFail} +%%% {kill_if_fail, KillIfFail} | +%%% {erl_flags, ErlangFlags} %%% Username = string() %%% Password = string() %%% BootTimeout = integer() @@ -85,13 +88,15 @@ start(Host, Node)-> %%% Module = atom() %%% Function = atom() %%% Arguments = [term] -%%% Monitor = bool -%%% KillIfFail = bool +%%% Monitor = bool() +%%% KillIfFail = bool() +%%% ErlangFlags = string() %%% Result = {ok, NodeName} | {error, already_started, NodeName} | %%% {error, started_not_connected, NodeName} | %%% {error, boot_timeout, NodeName} | %%% {error, init_timeout, NodeName} | -%%% {error, startup_timeout, NodeName} +%%% {error, startup_timeout, NodeName} | +%%% {error, not_alive, NodeName} %%% NodeName = atom() %%% @doc Starts an Erlang node with name Node on host %%% Host as specified by the combination of options in @@ -129,27 +134,33 @@ start(Host, Node)-> %%%

%%% %%%

Option monitor_master specifies, if the slave node should be -%%% stopped in case of master node stop. Defaults to false

+%%% stopped in case of master node stop. Defaults to true.

%%% %%%

Option kill_if_fail specifies, if the slave node should be %%% killed in case of a timeout during initialization or startup. %%% Defaults to true. Note that node also may be still alive it the boot %%% timeout occurred, but it will not be killed in this case.

%%% +%%%

Option erlang_flags specifies, which flags will be added +%%% to the parameters of the erl executable.

+%%% %%%

Special return values are: %%% %%% {error, already_started, NodeName} - if the node with %%% the given name is already started on a given host; %%% {error, started_not_connected, NodeName} - if node is %%% started, but not connected to the master node. +%%% {error, not_alive, NodeName} - if node on which the +%%% ct_slave:start/3 is called, is not alive. Note that +%%% NodeName is the name of current node in this case. %%%

%%% start(Host, Node, Options)-> ENode = enodename(Host, Node), - case net_kernel:longnames() of - ignored-> - {error, not_alive}; - _-> + case erlang:is_alive() of + false-> + {error, not_alive, node()}; + true-> case is_started(ENode) of false-> OptionsRec = fetch_options(Options), @@ -211,12 +222,13 @@ fetch_options(Options)-> InitTimeout = get_option_value(init_timeout, Options, 1), StartupTimeout = get_option_value(startup_timeout, Options, 1), StartupFunctions = get_option_value(startup_functions, Options, []), - Monitor = get_option_value(monitor_master, Options, false), + Monitor = get_option_value(monitor_master, Options, true), KillIfFail = get_option_value(kill_if_fail, Options, true), + ErlFlags = get_option_value(erl_flags, Options, []), #options{username=UserName, password=Password, boot_timeout=BootTimeout, init_timeout=InitTimeout, startup_timeout=StartupTimeout, startup_functions=StartupFunctions, - monitor_master=Monitor, kill_if_fail=KillIfFail}. + monitor_master=Monitor, kill_if_fail=KillIfFail, erl_flags=ErlFlags}. % send a message when slave node is started % @hidden @@ -282,7 +294,7 @@ do_start(Host, Node, Options)-> MasterHost = gethostname(), if MasterHost == Host -> - spawn_local_node(Node); + spawn_local_node(Node, Options); true-> spawn_remote_node(Host, Node, Options) end, @@ -338,14 +350,15 @@ gethostname()-> list_to_atom(Hostname). % get cmd for starting Erlang -get_cmd(Node)-> +get_cmd(Node, Flags)-> Cookie = erlang:get_cookie(), "erl -detached -noinput -setcookie "++ atom_to_list(Cookie) ++ - long_or_short() ++ atom_to_list(Node). + long_or_short() ++ atom_to_list(Node) ++ " " ++ Flags. % spawn node locally -spawn_local_node(Node)-> - Cmd = get_cmd(Node), +spawn_local_node(Node, Options)-> + ErlFlags = Options#options.erl_flags, + Cmd = get_cmd(Node, ErlFlags), %io:format("Running cmd: ~p~n", [Cmd]), open_port({spawn, Cmd}, [stream]). @@ -368,6 +381,7 @@ check_for_ssh_running()-> spawn_remote_node(Host, Node, Options)-> Username = Options#options.username, Password = Options#options.password, + ErlFlags = Options#options.erl_flags, SSHOptions = case {Username, Password} of {[], []}-> []; @@ -379,7 +393,7 @@ spawn_remote_node(Host, Node, Options)-> check_for_ssh_running(), {ok, SSHConnRef} = ssh:connect(atom_to_list(Host), 22, SSHOptions), {ok, SSHChannelId} = ssh_connection:session_channel(SSHConnRef, infinity), - ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node), infinity). + ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node, ErlFlags), infinity). % call functions on a remote Erlang node call_functions(_Node, [])-> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index a0da079c54..db3efb16e8 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -270,33 +270,52 @@ collect_tests(Terms,TestSpec,Relaxed) -> put(relaxed,Relaxed), TestSpec1 = get_global(Terms,TestSpec), TestSpec2 = get_all_nodes(Terms,TestSpec1), - case catch evaluate(Terms,TestSpec2) of + % filter out node_start options and save them into the specification + {Terms2, TestSpec3} = filter_nodestart_specs(Terms, [], TestSpec2), + % only save the 'global' evals and evals for nodes which have no node_start + {Terms3, TestSpec4} = filter_evals(Terms2, [], TestSpec3), + % after evaluation, only valid terms exist in the specification list + Terms4 = case catch evaluate(Terms3, [], TestSpec4) of {error,{Node,{M,F,A},Reason}} -> io:format("Error! Common Test failed to evaluate ~w:~w/~w on ~w. " - "Reason: ~p~n~n", [M,F,A,Node,Reason]); - _ -> ok + "Reason: ~p~n~n", [M,F,A,Node,Reason]), + Terms3; + NewTerms -> NewTerms end, - add_tests(Terms,TestSpec2). - -evaluate([{eval,NodeRef,{M,F,Args}}|Ts],Spec) -> - Node = ref2node(NodeRef,Spec#testspec.nodes), + add_tests(Terms4,TestSpec4). + +evaluate([{eval,Node,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)-> + EvalTerms = lists:map(fun(Mfa)-> + {eval, Node, Mfa} + end, + Mfas), + evaluate([EvalTerms|Ts], NewTerms, Spec); +evaluate([{eval,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)-> + EvalTerms = lists:map(fun(Mfa)-> + {eval, Mfa} + end, + Mfas), + evaluate([EvalTerms|Ts], NewTerms, Spec); +evaluate([{eval,Node,{M,F,Args}}|Ts],NewTerms,Spec) -> case rpc:call(Node,M,F,Args) of {badrpc,Reason} -> throw({error,{Node,{M,F,length(Args)},Reason}}); _ -> ok end, - evaluate(Ts,Spec); -evaluate([{eval,{M,F,Args}}|Ts],Spec) -> + evaluate(Ts,NewTerms,Spec); +evaluate([{eval,{M,F,Args}}|Ts],NewTerms,Spec) -> case catch apply(M,F,Args) of {'EXIT',Reason} -> throw({error,{node(),{M,F,length(Args)},Reason}}); _ -> ok end, - evaluate(Ts,Spec); -evaluate([],_Spec) -> - ok. + evaluate(Ts,NewTerms,Spec); +evaluate([Term|Ts], NewTerms, Spec)-> + evaluate(Ts, [Term|NewTerms], Spec); +evaluate([], NewTerms, _Spec) -> + NewTerms. get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) -> get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]}); @@ -373,6 +392,65 @@ get_all_nodes([_|Ts],Spec) -> get_all_nodes([],Spec) -> Spec. +filter_nodestart_specs([{node_start, Options}|Ts], NewTerms, Spec) -> + filter_nodestart_specs([{node_start, list_nodes(Spec), Options}|Ts], NewTerms, Spec); +filter_nodestart_specs([{node_start, NodeRef, Options}|Ts], NewTerms, Spec) when is_atom(NodeRef) -> + filter_nodestart_specs([{node_start, [NodeRef], Options}|Ts], NewTerms, Spec); +filter_nodestart_specs([{node_start, NodeRefs, Options}|Ts], NewTerms, Spec=#testspec{node_start=NodeStart})-> + Options2 = case lists:keyfind(callback_module, 1, Options) of + false-> + [{callback_module, ct_slave}|Options]; + {callback_module, _Callback}-> + Options + end, + NSSAdder = fun(NodeRef, NodeStartAcc)-> + Node=ref2node(NodeRef,Spec#testspec.nodes), + case lists:keyfind(Node, 1, NodeStartAcc) of + false-> + [{Node, Options2}|NodeStartAcc]; + {Node, OtherOptions}-> + io:format("~nWarning: There are other options defined for node ~p:" + "~n~w, skipping ~n~w...~n", [Node, OtherOptions, Options2]), + NodeStartAcc + end + end, + NodeStart2 = lists:foldl(NSSAdder, NodeStart, NodeRefs), + filter_nodestart_specs(Ts, NewTerms, Spec#testspec{node_start=NodeStart2}); +filter_nodestart_specs([Term|Ts], NewTerms, Spec)-> + filter_nodestart_specs(Ts, [Term|NewTerms], Spec); +filter_nodestart_specs([], NewTerms, Spec) -> + {NewTerms, Spec}. + +filter_evals([{eval,NodeRefs,Mfa}|Ts], NewTerms, Spec) when is_list(NodeRefs)-> + EvalTerms = lists:map(fun(NodeRef)-> + {eval, NodeRef, Mfa} + end, + NodeRefs), + filter_evals(EvalTerms++Ts, NewTerms, Spec); +filter_evals([{eval,NodeRef,{_,_,_}=Mfa}|Ts],NewTerms,Spec)-> + filter_evals([{eval,NodeRef,[Mfa]}|Ts],NewTerms,Spec); +filter_evals([{eval,NodeRef,[{_,_,_}|_]=Mfas}=EvalTerm|Ts], + NewTerms,Spec=#testspec{node_start=NodeStart})-> + Node=ref2node(NodeRef,Spec#testspec.nodes), + case lists:keyfind(Node, 1, NodeStart) of + false-> + filter_evals(Ts, [EvalTerm|NewTerms], Spec); + {Node, Options}-> + Options2 = case lists:keyfind(startup_functions, 1, Options) of + false-> + [{startup_functions, Mfas}|Options]; + {startup_functions, StartupFunctions}-> + lists:keyreplace(startup_functions, 1, Options, + {startup_functions, StartupFunctions ++ Mfas}) + end, + NodeStart2 = lists:keyreplace(Node, 1, NodeStart, {Node, Options2}), + filter_evals(Ts, NewTerms, Spec#testspec{node_start=NodeStart2}) + end; +filter_evals([Term|Ts], NewTerms, Spec)-> + filter_evals(Ts, [Term|NewTerms], Spec); +filter_evals([], NewTerms, Spec)-> + {NewTerms, Spec}. + save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) -> NodeRefs1 = lists:foldr(fun(all_nodes,NR) -> @@ -794,6 +872,8 @@ valid_terms() -> {cover,3}, {config,2}, {config,3}, + {userconfig, 2}, + {userconfig, 3}, {alias,3}, {logdir,2}, {logdir,3}, @@ -802,7 +882,6 @@ valid_terms() -> {event_handler,4}, {include,2}, {include,3}, - {suites,3}, {suites,4}, {cases,4}, diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index c2f51dfba5..7c8fd67e1d 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -29,6 +29,7 @@ -record(testspec, {spec_dir, nodes=[], + node_start=[], logdir=["."], cover=[], config=[], diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index ebd399ad64..3618b08a74 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -74,9 +74,7 @@ ct_master_test(Config) when is_list(Config)-> FileName = filename:join(DataDir, "ct_master_spec.spec"), Suites = [master_SUITE], TSFile = make_spec(DataDir, FileName, NodeNames, Suites, Config), - start_nodes(NodeNames), [{TSFile, ok}] = run_test(ct_master_test, FileName, Config), - stop_nodes(NodeNames), file:delete(filename:join(DataDir, FileName)). %%%----------------------------------------------------------------- @@ -100,6 +98,11 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> end, NodeNames), + NS = lists:map(fun(NodeName)-> + {node_start, NodeName, [{startup_functions, [{io, format, ["hello, world~n"]}]}]} + end, + NodeNames), + S = [{suites, NodeNames, filename:join(DataDir, "master"), Suites}], PrivDir = ?config(priv_dir, Config), @@ -108,7 +111,7 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> end, NodeNames) ++ [{logdir, master, PrivDir}], - ct_test_support:write_testspec(N++C++S++LD, DataDir, FileName). + ct_test_support:write_testspec(N++C++S++LD++NS, DataDir, FileName). get_log_dir(PrivDir, NodeName)-> LogDir = filename:join(PrivDir, io_lib:format("slave.~p", [NodeName])), @@ -118,18 +121,6 @@ get_log_dir(PrivDir, NodeName)-> run_test(_Name, FileName, Config)-> [{FileName, ok}] = ct_test_support:run(ct_master, run, [FileName], Config). -start_nodes(NodeNames)-> - lists:foreach(fun(NodeName)-> - {ok, _}=ct_slave:start(NodeName) - end, - NodeNames). - -stop_nodes(NodeNames)-> - lists:foreach(fun(NodeName)-> - {ok, _}=ct_slave:stop(NodeName) - end, - NodeNames). - reformat_events(Events, EH) -> ct_test_support:reformat(Events, EH). -- cgit v1.2.3 From 2daac36809c14007d179a118784f48ceb79a30d1 Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Tue, 13 Apr 2010 09:11:59 +0200 Subject: Change monitor_master option to false by default --- lib/common_test/src/ct_master.erl | 5 ++--- lib/common_test/src/ct_slave.erl | 4 ++-- lib/common_test/test/ct_master_SUITE.erl | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index b14e7527e5..ecf516bd98 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -710,12 +710,11 @@ start_nodes([{NodeName, Options}|Rest])-> Host=list_to_atom(HostS), {value, {callback_module, Callback}, Options2}= lists:keytake(callback_module, 1, Options), - io:format("Starting node ~p on host ~p with callback ~p...~n", [Node, Host, Callback]), case Callback:start(Host, Node, Options2) of {ok, NodeName} -> - io:format("Node ~p started successfully~n", [NodeName]); + io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]); {error, Reason, _NodeName} -> - io:format("Failed to start node ~p! Reason: ~p~n", [NodeName, Reason]) + io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason]) end, start_nodes(Rest). diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 09d8820731..476815895c 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -134,7 +134,7 @@ start(Host, Node)-> %%%

%%% %%%

Option monitor_master specifies, if the slave node should be -%%% stopped in case of master node stop. Defaults to true.

+%%% stopped in case of master node stop. Defaults to false.

%%% %%%

Option kill_if_fail specifies, if the slave node should be %%% killed in case of a timeout during initialization or startup. @@ -222,7 +222,7 @@ fetch_options(Options)-> InitTimeout = get_option_value(init_timeout, Options, 1), StartupTimeout = get_option_value(startup_timeout, Options, 1), StartupFunctions = get_option_value(startup_functions, Options, []), - Monitor = get_option_value(monitor_master, Options, true), + Monitor = get_option_value(monitor_master, Options, false), KillIfFail = get_option_value(kill_if_fail, Options, true), ErlFlags = get_option_value(erl_flags, Options, []), #options{username=UserName, password=Password, diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index 3618b08a74..0f0d13f683 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -99,7 +99,8 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> NodeNames), NS = lists:map(fun(NodeName)-> - {node_start, NodeName, [{startup_functions, [{io, format, ["hello, world~n"]}]}]} + {node_start, NodeName, [{startup_functions, [{io, format, ["hello, world~n"]}]}, + {monitor_master, true}]} end, NodeNames), -- cgit v1.2.3 From ebcab814cabc74407f48aba1b41327df80586fcd Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Thu, 15 Apr 2010 12:57:04 +0200 Subject: Document ct_slave and 'eval' terms --- lib/common_test/doc/src/ct_master_chapter.xml | 31 +++++++++++++++++++++++++++ lib/common_test/doc/src/run_test_chapter.xml | 16 +++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml index 79288cfe4c..1622b5b29c 100644 --- a/lib/common_test/doc/src/ct_master_chapter.xml +++ b/lib/common_test/doc/src/ct_master_chapter.xml @@ -194,6 +194,37 @@ current working directory settings are not important.

+
+ Automatic startup of the test target nodes + +

Is is possible to start nodes acting like a test targets automatically + using a new term in the test specification, node_start:

+
+     {node, node1, node1@host1}.
+     {node, node2, node1@host2}.
+     {node, node3, node2@host2}.
+     {node_start, node1, [{callback_module, my_slave_callback}]}.
+     {node_start, [node2, node3], [{username, "ct_user"}, {password, "ct_password"}]}.
+    
+

This test specification declares that node1@host1 is to be started using + user's callback my_slave_callback with no parameters, and nodes node1@host2 and + node2@host2 will be started with the default callback module ct_slave, + using the given user name and password to log into remote host host2.

+

Default ct_slave callback module delivered with the Common Test has the following features: + + Starting the Erlang nodes or local or remote hosts (ssh is used for remote ones); + Ability to start Erlang emulator with the additional flags (any flags supported by erl are supported); + Supervision of the node being start using internal callback functions. Used to prevent hanging of started nodes. Configurable; + Monitoring of the master node by the slaves. Slave node may be stopped in case of master node termination. Configurable; + Execution of the user's functions after slave node is started. Functions can be given as a list of {Module, Function, Arguments} tuples. + +

+

If any eval term is specified for a node which has node_start term + defined for it, the evaluation of the functions will be deferred until the node is started. + The functions to be evaluated are simply added to the end of the startup_functions list + (see ct_slave chapter in the Reference Manual).

+
+ diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 00cf4adf52..f5dc477c6f 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -383,6 +383,9 @@ {event_handler, NodeRefs, EventHandlers}. {event_handler, EventHandlers, InitArgs}. {event_handler, NodeRefs, EventHandlers, InitArgs}. + + {eval, [{Module, Function, Arguments}]}. + {eval, [NodeAlias], [{Module, Function, Arguments}]}.

Test terms:

@@ -455,9 +458,20 @@
       Secondly, the test for system t2 should run. The included suites are
 	t2B and t2C. Included are also test cases test4, test1 and test7 in suite
 	t2A. Note that the test cases will be executed in the specified order.
-      Lastly, all suites for systems t3 are to be completely skipped and this 
+      Lastly, all suites for systems t3 are to be completely skipped and this
 	should be explicitly noted in the log files.
     
+    

It is possible to evaluate any function(s) during processing of the test + specification. This feature may be used e.g. to add some directories to + the code path, initialise some ETS table etc. New eval terms can be + used for this purpose. + If only a list of functions is given, then all functions from the list + will be consequently applied on the current node. If at least one node + alias is given, then the functions will be called remotely on all nodes + specified. Please note that in case when node is defined to be started by + the CT Master, the evaluation will be deferred. + See the Automatic startup of + the test target nodes chapter for details.

It is possible for the user to provide a test specification that includes (for Common Test) unrecognizable terms. If this is desired, the -allow_user_terms flag should be used when starting tests with -- cgit v1.2.3 From 1327bd8956b2d0f6b5a9201ce9ecf361f94733aa Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Mon, 19 Apr 2010 17:28:07 +0200 Subject: Move 'node_start' and 'eval' terms into new 'init' term node_start+eval -> init(node_start, eval) Also include some documentation updates. --- lib/common_test/doc/src/ct_master_chapter.xml | 49 +++++--- lib/common_test/doc/src/run_test_chapter.xml | 16 +-- lib/common_test/src/ct_config_xml.erl | 3 - lib/common_test/src/ct_master.erl | 113 ++++++++++++++----- lib/common_test/src/ct_slave.erl | 1 - lib/common_test/src/ct_testspec.erl | 155 ++++++++++---------------- lib/common_test/src/ct_util.hrl | 2 +- lib/common_test/test/ct_master_SUITE.erl | 7 +- 8 files changed, 184 insertions(+), 162 deletions(-) diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml index 1622b5b29c..4ab891edee 100644 --- a/lib/common_test/doc/src/ct_master_chapter.xml +++ b/lib/common_test/doc/src/ct_master_chapter.xml @@ -102,7 +102,7 @@

ct_master:abort() (stop all) or ct_master:abort(Nodes)

For detailed information about the ct_master API, please see the - manual page for this module.

+ manual page for this module.

Test Specifications @@ -197,32 +197,49 @@
Automatic startup of the test target nodes -

Is is possible to start nodes acting like a test targets automatically - using a new term in the test specification, node_start:

+

Is is possible to perform initial actions on test target nodes + automatically using a new term in the test specification, init.

+

Currently, two sub-terms are supported, node_start and eval.

      {node, node1, node1@host1}.
      {node, node2, node1@host2}.
      {node, node3, node2@host2}.
-     {node_start, node1, [{callback_module, my_slave_callback}]}.
-     {node_start, [node2, node3], [{username, "ct_user"}, {password, "ct_password"}]}.
+     {node, node4, node1@host3}.
+     {init, node1, [{node_start, [{callback_module, my_slave_callback}]}]}.
+     {init, [node2, node3], {node_start, [{username, "ct_user"}, {password, "ct_password"}]}}.
+     {init, node4, {eval, {module, function, []}}}.
     

This test specification declares that node1@host1 is to be started using user's callback my_slave_callback with no parameters, and nodes node1@host2 and node2@host2 will be started with the default callback module ct_slave, - using the given user name and password to log into remote host host2.

-

Default ct_slave callback module delivered with the Common Test has the following features: + using the given user name and password to log into remote host host2. + Also, there will be function module:function/0 evaluated on the + node1@host3, and result of this call will be printed to the log.

+

Default ct_slave callback module + delivered with the Common Test has the following features: - Starting the Erlang nodes or local or remote hosts (ssh is used for remote ones); - Ability to start Erlang emulator with the additional flags (any flags supported by erl are supported); - Supervision of the node being start using internal callback functions. Used to prevent hanging of started nodes. Configurable; - Monitoring of the master node by the slaves. Slave node may be stopped in case of master node termination. Configurable; - Execution of the user's functions after slave node is started. Functions can be given as a list of {Module, Function, Arguments} tuples. + Starting the Erlang nodes or local or remote hosts + (ssh is used for remote ones); + + Ability to start Erlang emulator with the additional flags + (any flags supported by erl are supported); + + Supervision of the node being start using internal callback + functions. Used to prevent hanging of started nodes. Configurable; + + Monitoring of the master node by the slaves. Slave node may be + stopped in case of master node termination. Configurable; + + Execution of the user's functions after slave node is started. + Functions can be given as a list of {Module, Function, Arguments} tuples. +

-

If any eval term is specified for a node which has node_start term - defined for it, the evaluation of the functions will be deferred until the node is started. - The functions to be evaluated are simply added to the end of the startup_functions list - (see ct_slave chapter in the Reference Manual).

+

Note that it is possible to specify eval term for the node as well + as startup_functions in the node_start options list. In this + case first node will be started, then the startup_functions are + executed, and finally functions specified with eval will be called. +

diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index f5dc477c6f..44b4077ec4 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -384,8 +384,8 @@ {event_handler, EventHandlers, InitArgs}. {event_handler, NodeRefs, EventHandlers, InitArgs}. - {eval, [{Module, Function, Arguments}]}. - {eval, [NodeAlias], [{Module, Function, Arguments}]}. + {init, Options}. + {init, [NodeAlias], Options}.

Test terms:

@@ -461,15 +461,9 @@
       Lastly, all suites for systems t3 are to be completely skipped and this
 	should be explicitly noted in the log files.
     
-    

It is possible to evaluate any function(s) during processing of the test - specification. This feature may be used e.g. to add some directories to - the code path, initialise some ETS table etc. New eval terms can be - used for this purpose. - If only a list of functions is given, then all functions from the list - will be consequently applied on the current node. If at least one node - alias is given, then the functions will be called remotely on all nodes - specified. Please note that in case when node is defined to be started by - the CT Master, the evaluation will be deferred. +

It is possible to specify initialization options for nodes defined in the + test specification. Currently, there are options to start the node and/or to + evaluate any function on the node. See the Automatic startup of the test target nodes chapter for details.

It is possible for the user to provide a test specification that diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl index 111d1426c9..4ced80aeac 100755 --- a/lib/common_test/src/ct_config_xml.erl +++ b/lib/common_test/src/ct_config_xml.erl @@ -24,9 +24,6 @@ -module(ct_config_xml). -export([read_config/1, check_parameter/1]). -% DEBUG ONLY --export([list_to_term/1]). - % read config file read_config(ConfigFile) -> case catch do_read_xml_config(ConfigFile) of diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index ecf516bd98..6cabe4c7a2 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -101,12 +101,12 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS), TSRec=#testspec{logdir=AllLogDirs, config=StdCfgFiles, userconfig=UserCfgFiles, - node_start=AllNSOpts, + init=AllInitOpts, event_handler=AllEvHs} -> AllCfgFiles = {StdCfgFiles, UserCfgFiles}, RunSkipPerNode = ct_testspec:prepare_tests(TSRec), RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode), - run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1) + run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1) end, [{TS,Result} | run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes)]; run([],_,_,_) -> @@ -162,12 +162,12 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) - {error,Reason}; TSRec=#testspec{logdir=AllLogDirs, config=StdCfgFiles, - node_start=AllNSOpts, + init=AllInitOpts, userconfig=UserCfgFiles, event_handler=AllEvHs} -> AllCfgFiles = {StdCfgFiles, UserCfgFiles}, {Run,Skip} = ct_testspec:prepare_tests(TSRec,Node), - run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllNSOpts,TS1) + run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1) end, [{TS,Result} | run_on_node(TestSpecs,AllowUserTerms,Node)]; run_on_node([],_,_) -> @@ -189,7 +189,7 @@ run_on_node(TestSpecs,Node) -> run_all([{Node,Run,Skip}|Rest],AllLogDirs, {AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles, - AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) -> + AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) -> LogDir = lists:foldl(fun({N,Dir},_Found) when N == Node -> Dir; @@ -221,15 +221,15 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs, {logdir,LogDir}, {config,StdCfgFiles}, {event_handler,EvHs}] ++ UserCfgFiles}, - run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],NSOptions,Specs); -run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,NSOptions,Specs) -> + run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],InitOptions,Specs); +run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) -> Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master], MasterLogDir = case lists:keysearch(master,1,AllLogDirs) of {value,{_,Dir}} -> Dir; false -> "." end, log(tty,"Master Logdir","~s",[MasterLogDir]), - start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,NSOptions,Specs), + start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,InitOptions,Specs), ok. @@ -269,15 +269,15 @@ progress() -> start_master(NodeOptsList) -> start_master(NodeOptsList,[],".",[],[],[]). -start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) -> +start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) -> Master = spawn_link(?MODULE,init_master,[self(),NodeOptsList,EvHandlers, - MasterLogDir,LogDirs,NSOptions,Specs]), + MasterLogDir,LogDirs,InitOptions,Specs]), receive {Master,Result} -> Result end. %%% @hidden -init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) -> +init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) -> case whereis(ct_master) of undefined -> register(ct_master,self()), @@ -330,11 +330,10 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,NSOptions,Specs) Pid when is_pid(Pid) -> ok end, - init_master1(Parent,NodeOptsList,NSOptions,LogDirs). + init_master1(Parent,NodeOptsList,InitOptions,LogDirs). -init_master1(Parent,NodeOptsList,NSOptions,LogDirs) -> - start_nodes(NSOptions), - {Inaccessible,NodeOptsList1} = ping_nodes(NodeOptsList,[],[]), +init_master1(Parent,NodeOptsList,InitOptions,LogDirs) -> + {Inaccessible,NodeOptsList1,InitOptions1} = init_nodes(NodeOptsList,InitOptions), case Inaccessible of [] -> init_master2(Parent,NodeOptsList,LogDirs); @@ -348,7 +347,7 @@ init_master1(Parent,NodeOptsList,NSOptions,LogDirs) -> "Proceeding without: ~p",[Inaccessible]), init_master2(Parent,NodeOptsList1,LogDirs); "r\n" -> - init_master1(Parent,NodeOptsList,NSOptions,LogDirs); + init_master1(Parent,NodeOptsList,InitOptions1,LogDirs); _ -> log(html,"Aborting Tests","",[]), ct_master_event:stop(), @@ -559,6 +558,9 @@ get_pid(Node,NodeCtrlPids) -> undefined end. +ping_nodes(NodeOptions)-> + ping_nodes(NodeOptions, [], []). + ping_nodes([NO={Node,_Opts}|NOs],Inaccessible,NodeOpts) -> case net_adm:ping(Node) of pong -> @@ -702,21 +704,72 @@ reply(Result,To) -> To ! {self(),Result}, ok. -start_nodes([])-> - ok; -start_nodes([{NodeName, Options}|Rest])-> - [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"), - Node=list_to_atom(NodeS), - Host=list_to_atom(HostS), - {value, {callback_module, Callback}, Options2}= - lists:keytake(callback_module, 1, Options), - case Callback:start(Host, Node, Options2) of - {ok, NodeName} -> - io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]); - {error, Reason, _NodeName} -> - io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason]) +init_nodes(NodeOptions, InitOptions)-> + ping_nodes(NodeOptions), + start_nodes(InitOptions), + eval_on_nodes(InitOptions), + {Inaccessible, NodeOptions1}=ping_nodes(NodeOptions), + InitOptions1 = filter_accessible(InitOptions, Inaccessible), + {Inaccessible, NodeOptions1, InitOptions1}. + +% only nodes which are inaccessible now, should be initiated later +filter_accessible(InitOptions, Inaccessible)-> + [{Node,Option}||{Node,Option}<-InitOptions, lists:member(Node, Inaccessible)]. + +start_nodes(InitOptions)-> + lists:foreach(fun({NodeName, Options})-> + [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"), + Node=list_to_atom(NodeS), + Host=list_to_atom(HostS), + HasNodeStart = lists:keymember(node_start, 1, Options), + IsAlive = lists:member(NodeName, nodes()), + case {HasNodeStart, IsAlive} of + {false, false}-> + io:format("WARNING: Node ~p is not alive but has no node_start option~n", [NodeName]); + {false, true}-> + io:format("Node ~p is alive~n", [NodeName]); + {true, false}-> + {node_start, NodeStart} = lists:keyfind(node_start, 1, Options), + {value, {callback_module, Callback}, NodeStart2}= + lists:keytake(callback_module, 1, NodeStart), + case Callback:start(Host, Node, NodeStart2) of + {ok, NodeName} -> + io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]); + {error, Reason, _NodeName} -> + io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason]) + end; + {true, true}-> + io:format("WARNING: Node ~p is alive but has node_start option~n", [NodeName]) + end + end, + InitOptions). + +eval_on_nodes(InitOptions)-> + lists:foreach(fun({NodeName, Options})-> + HasEval = lists:keymember(eval, 1, Options), + IsAlive = lists:member(NodeName, nodes()), + case {HasEval, IsAlive} of + {false,_}-> + ok; + {true,false}-> + io:format("WARNING: Node ~p is not alive but has eval option ~n", [NodeName]); + {true,true}-> + {eval, MFAs} = lists:keyfind(eval, 1, Options), + evaluate(NodeName, MFAs) + end end, - start_nodes(Rest). + InitOptions). + +evaluate(Node, [{M,F,A}|MFAs])-> + case rpc:call(Node, M, F, A) of + {badrpc,Reason}-> + io:format("WARNING: Failed to call ~p:~p/~p on node ~p due to ~p~n", [M,F,length(A),Node,Reason]); + Result-> + io:format("Called ~p:~p/~p on node ~p, result: ~p~n", [M,F,length(A),Node,Result]) + end, + evaluate(Node, MFAs); +evaluate(_Node, [])-> + ok. %cast(Msg) -> % cast(whereis(ct_master),Msg). diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 476815895c..22cb17e55d 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -359,7 +359,6 @@ get_cmd(Node, Flags)-> spawn_local_node(Node, Options)-> ErlFlags = Options#options.erl_flags, Cmd = get_cmd(Node, ErlFlags), - %io:format("Running cmd: ~p~n", [Cmd]), open_port({spawn, Cmd}, [stream]). % start crypto and ssh if not yet started diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index db3efb16e8..4e34c1513e 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -270,52 +270,8 @@ collect_tests(Terms,TestSpec,Relaxed) -> put(relaxed,Relaxed), TestSpec1 = get_global(Terms,TestSpec), TestSpec2 = get_all_nodes(Terms,TestSpec1), - % filter out node_start options and save them into the specification - {Terms2, TestSpec3} = filter_nodestart_specs(Terms, [], TestSpec2), - % only save the 'global' evals and evals for nodes which have no node_start - {Terms3, TestSpec4} = filter_evals(Terms2, [], TestSpec3), - % after evaluation, only valid terms exist in the specification list - Terms4 = case catch evaluate(Terms3, [], TestSpec4) of - {error,{Node,{M,F,A},Reason}} -> - io:format("Error! Common Test failed to evaluate ~w:~w/~w on ~w. " - "Reason: ~p~n~n", [M,F,A,Node,Reason]), - Terms3; - NewTerms -> NewTerms - end, - add_tests(Terms4,TestSpec4). - -evaluate([{eval,Node,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)-> - EvalTerms = lists:map(fun(Mfa)-> - {eval, Node, Mfa} - end, - Mfas), - evaluate([EvalTerms|Ts], NewTerms, Spec); -evaluate([{eval,[{_,_,_}|_]=Mfas}|Ts],NewTerms,Spec)-> - EvalTerms = lists:map(fun(Mfa)-> - {eval, Mfa} - end, - Mfas), - evaluate([EvalTerms|Ts], NewTerms, Spec); -evaluate([{eval,Node,{M,F,Args}}|Ts],NewTerms,Spec) -> - case rpc:call(Node,M,F,Args) of - {badrpc,Reason} -> - throw({error,{Node,{M,F,length(Args)},Reason}}); - _ -> - ok - end, - evaluate(Ts,NewTerms,Spec); -evaluate([{eval,{M,F,Args}}|Ts],NewTerms,Spec) -> - case catch apply(M,F,Args) of - {'EXIT',Reason} -> - throw({error,{node(),{M,F,length(Args)},Reason}}); - _ -> - ok - end, - evaluate(Ts,NewTerms,Spec); -evaluate([Term|Ts], NewTerms, Spec)-> - evaluate(Ts, [Term|NewTerms], Spec); -evaluate([], NewTerms, _Spec) -> - NewTerms. + {Terms2, TestSpec3} = filter_init_terms(Terms, [], TestSpec2), + add_tests(Terms2,TestSpec3). get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) -> get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]}); @@ -392,64 +348,67 @@ get_all_nodes([_|Ts],Spec) -> get_all_nodes([],Spec) -> Spec. -filter_nodestart_specs([{node_start, Options}|Ts], NewTerms, Spec) -> - filter_nodestart_specs([{node_start, list_nodes(Spec), Options}|Ts], NewTerms, Spec); -filter_nodestart_specs([{node_start, NodeRef, Options}|Ts], NewTerms, Spec) when is_atom(NodeRef) -> - filter_nodestart_specs([{node_start, [NodeRef], Options}|Ts], NewTerms, Spec); -filter_nodestart_specs([{node_start, NodeRefs, Options}|Ts], NewTerms, Spec=#testspec{node_start=NodeStart})-> - Options2 = case lists:keyfind(callback_module, 1, Options) of +filter_init_terms([{init, InitOptions}|Ts], NewTerms, Spec)-> + filter_init_terms([{init, list_nodes(Spec), InitOptions}|Ts], NewTerms, Spec); +filter_init_terms([{init, NodeRef, InitOptions}|Ts], NewTerms, Spec) + when is_atom(NodeRef)-> + filter_init_terms([{init, [NodeRef], InitOptions}|Ts], NewTerms, Spec); +filter_init_terms([{init, NodeRefs, InitOption}|Ts], NewTerms, Spec) when is_tuple(InitOption) -> + filter_init_terms([{init, NodeRefs, [InitOption]}|Ts], NewTerms, Spec); +filter_init_terms([{init, [NodeRef|NodeRefs], InitOptions}|Ts], NewTerms, Spec=#testspec{init=InitData})-> + NodeStartOptions = case lists:keyfind(node_start, 1, InitOptions) of + {node_start, NSOptions}-> + case lists:keyfind(callback_module, 1, NSOptions) of + {callback_module, _Callback}-> + NSOptions; + false-> + [{callback_module, ct_slave}|NSOptions] + end; false-> - [{callback_module, ct_slave}|Options]; - {callback_module, _Callback}-> - Options + [] end, - NSSAdder = fun(NodeRef, NodeStartAcc)-> - Node=ref2node(NodeRef,Spec#testspec.nodes), - case lists:keyfind(Node, 1, NodeStartAcc) of - false-> - [{Node, Options2}|NodeStartAcc]; - {Node, OtherOptions}-> - io:format("~nWarning: There are other options defined for node ~p:" - "~n~w, skipping ~n~w...~n", [Node, OtherOptions, Options2]), - NodeStartAcc - end + EvalTerms = case lists:keyfind(eval, 1, InitOptions) of + {eval, MFA} when is_tuple(MFA)-> + [MFA]; + {eval, MFAs} when is_list(MFAs)-> + MFAs; + false-> + [] end, - NodeStart2 = lists:foldl(NSSAdder, NodeStart, NodeRefs), - filter_nodestart_specs(Ts, NewTerms, Spec#testspec{node_start=NodeStart2}); -filter_nodestart_specs([Term|Ts], NewTerms, Spec)-> - filter_nodestart_specs(Ts, [Term|NewTerms], Spec); -filter_nodestart_specs([], NewTerms, Spec) -> + Node = ref2node(NodeRef,Spec#testspec.nodes), + InitData2 = add_option({node_start, NodeStartOptions}, Node, InitData, true), + InitData3 = add_option({eval, EvalTerms}, Node, InitData2, false), + filter_init_terms([{init, NodeRefs, InitOptions}|Ts], NewTerms, Spec#testspec{init=InitData3}); +filter_init_terms([{init, [], _}|Ts], NewTerms, Spec)-> + filter_init_terms(Ts, NewTerms, Spec); +filter_init_terms([Term|Ts], NewTerms, Spec)-> + filter_init_terms(Ts, [Term|NewTerms], Spec); +filter_init_terms([], NewTerms, Spec)-> {NewTerms, Spec}. -filter_evals([{eval,NodeRefs,Mfa}|Ts], NewTerms, Spec) when is_list(NodeRefs)-> - EvalTerms = lists:map(fun(NodeRef)-> - {eval, NodeRef, Mfa} - end, - NodeRefs), - filter_evals(EvalTerms++Ts, NewTerms, Spec); -filter_evals([{eval,NodeRef,{_,_,_}=Mfa}|Ts],NewTerms,Spec)-> - filter_evals([{eval,NodeRef,[Mfa]}|Ts],NewTerms,Spec); -filter_evals([{eval,NodeRef,[{_,_,_}|_]=Mfas}=EvalTerm|Ts], - NewTerms,Spec=#testspec{node_start=NodeStart})-> - Node=ref2node(NodeRef,Spec#testspec.nodes), - case lists:keyfind(Node, 1, NodeStart) of - false-> - filter_evals(Ts, [EvalTerm|NewTerms], Spec); +add_option([], _, List, _)-> + List; +add_option({Key, Value}, Node, List, WarnIfExists) when is_list(Value)-> + OldOptions = case lists:keyfind(Node, 1, List) of {Node, Options}-> - Options2 = case lists:keyfind(startup_functions, 1, Options) of - false-> - [{startup_functions, Mfas}|Options]; - {startup_functions, StartupFunctions}-> - lists:keyreplace(startup_functions, 1, Options, - {startup_functions, StartupFunctions ++ Mfas}) - end, - NodeStart2 = lists:keyreplace(Node, 1, NodeStart, {Node, Options2}), - filter_evals(Ts, NewTerms, Spec#testspec{node_start=NodeStart2}) - end; -filter_evals([Term|Ts], NewTerms, Spec)-> - filter_evals(Ts, [Term|NewTerms], Spec); -filter_evals([], NewTerms, Spec)-> - {NewTerms, Spec}. + Options; + false-> + [] + end, + NewOption = case lists:keyfind(Key, 1, OldOptions) of + {Key, OldOption} when WarnIfExists, OldOption/=[]-> + io:format("There is an option ~w=~w already defined for node ~p, skipping new ~w~n", + [Key, OldOption, Node, Value]), + OldOption; + {Key, OldOption}-> + OldOption ++ Value; + false-> + Value + end, + lists:keystore(Node, 1, List, + {Node, lists:keystore(Key, 1, OldOptions, {Key, NewOption})}); +add_option({Key, Value}, Node, List, WarnIfExists)-> + add_option({Key, [Value]}, Node, List, WarnIfExists). save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) -> NodeRefs1 = diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 7c8fd67e1d..7ddb7d8c2b 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -29,7 +29,7 @@ -record(testspec, {spec_dir, nodes=[], - node_start=[], + init=[], logdir=["."], cover=[], config=[], diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index 0f0d13f683..2d4a29cde1 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -99,8 +99,11 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> NodeNames), NS = lists:map(fun(NodeName)-> - {node_start, NodeName, [{startup_functions, [{io, format, ["hello, world~n"]}]}, - {monitor_master, true}]} + {init, NodeName, [ + {node_start, [{startup_functions, []}, {monitor_master, false}]}, + {eval, {erlang, nodes, []}} + ] + } end, NodeNames), -- cgit v1.2.3 From 215cdbcb6312caf49cd1fd1b37f0fb5842b5e13d Mon Sep 17 00:00:00 2001 From: Andrey Pampukha Date: Wed, 28 Apr 2010 16:00:12 +0200 Subject: Documentation update --- lib/common_test/doc/src/config_file_chapter.xml | 148 ++++++++++++------------ lib/common_test/src/ct_slave.erl | 8 ++ lib/common_test/src/ct_testspec.erl | 2 +- 3 files changed, 83 insertions(+), 75 deletions(-) diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml index 5199807f48..d402b121b3 100644 --- a/lib/common_test/doc/src/config_file_chapter.xml +++ b/lib/common_test/doc/src/config_file_chapter.xml @@ -180,38 +180,37 @@

- Using own configuration data formats + User specific configuration data formats -

The nature of the configuration variables can be not only plain text - files with the key-value tuples, they also can be loaded from the files in - various formats, fetched via http from the Web, or can be loaded with help - of some driver process. For this purpose, mechanism of plugging in user - configuration handling callback modules is implemented in the Common Test.

+

It is possible for the user to specify configuration data on a + different format than key-value tuples in a text file, as described + so far. The data can e.g. be read from arbitrary files, fetched from + the web over http, or requested from a user specific process. + To support this, Common Test provides a callback module plugin + mechanism to handle configuration data.

- Standard callback modules for loading configuration variables -

Two callback modules for handling of configuration files are provided - with the Common Test application:

+ Default callback modules for handling configuration data +

The Common Test application includes default callback modules + for handling configuration data specified in standard config files + (see above) and in xml files:

- - ct_config_plain - for reading configuration files with - key-value tuples (traditional format). This handler will be tried to - parse configuration file, if no user callback is specified. - - - ct_config_xml - for reading configuration data from the XML - files. - + + ct_config_plain - for reading configuration files with + key-value tuples (standard format). This handler will be used to + parse configuration files if no user callback is specified. + + + ct_config_xml - for reading configuration data from XML + files. +
Using XML configuration files - -

This is an example of the XML configuration file:

- -
-      This is an example of an XML configuration file:

+

     
         "targethost"
@@ -219,8 +218,7 @@
         "letmein"
     
     "/test/loadmodules"
-]]>
-      
+]]>

This configuration file, once read, will produce the same configuration variables as the following text file:

@@ -229,77 +227,79 @@ {username,"tester"}, {password,"letmein"}]}. -{lm_directory, "/test/loadmodules"}. -
+{lm_directory, "/test/loadmodules"}.
- Implementing of the own handler + How to implement a user specific handler + +

The user specific handler can be written to handle special + configuration file formats. The parameter can be either file + name(s) or configuration string(s) (the empty list is valid).

-

Own handler can be written to handle special configuration file formats. - The parameter can be either file name(s) or configuration string (empty list - is valid).

-

The callback module is completely responsible for the - configuration string correctness checks during runtime.

+

The callback module implementing the handler is responsible for + checking correctness of configuration strings.

-

To perform validation of the configuration string, callback module - should have the following function exported:

+

To perform validation of the configuration strings, the callback module + should have the following function exported:

Callback:check_parameter/1

-

Input value will be passed from the Common Test, as defined in the test - specification or given as an option to the run_test.

-

Return value should be any of the following value indicating if given - configuration parameter is valid:

+

The input argument will be passed from Common Test, as defined in the test + specification or given as an option to run_test.

+ +

The return value should be any of the following values indicating if given + configuration parameter is valid:

{ok, {file, FileName}} - parameter is a file name and - file exists; + the file exists, {ok, {config, ConfigString}} - parameter is a config string - and it is correct; + and it is correct, {error, {nofile, FileName}} - there is no file with the given - name in the current directory; + name in the current directory, - {error, {wrong_config, ConfigString}} - configuration string + {error, {wrong_config, ConfigString}} - the configuration string is wrong. -

To perform actual reading, in cases of initial loading of the - configuration variables and runtime re-loading, function

+

To perform reading of configuration data - initially before the tests + start, or as a result of data being reloaded during test execution - + the following function should be exported from the callback module:

+

Callback:read_config/1

-

should be exported from the callback module

-

Input value is the same as for check_parameter/1 function

-

Return value should be either:

+ +

The input argument is the same as for the check_parameter/1 function.

+

The return value should be either:

- {ok, Config} - if configuration variables read successfully; + {ok, Config} - if the configuration variables are read successfully, - {error, Error, ErrorDetails} - if callback module failed to + {error, Error, ErrorDetails} - if the callback module fails to proceed with the given configuration parameters. -

Above, the Config variable is the proper Erlang key-value list, - with possible key-value sublists as values, - e.g. for the configuration files above it will be the following:

+

Config is the proper Erlang key-value list, with possible + key-value sublists as values, like for the configuration file + example above:

         [{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
-        {lm_directory, "/test/loadmodules"}]
-      
+ {lm_directory, "/test/loadmodules"}]
- Examples of the configuration files + Examples of configuration data handling

A config file for using the FTP client to access files on a remote host could look like this:

@@ -311,9 +311,9 @@ {lm_directory, "/test/loadmodules"}. -

XML version shown in chapter above can also be used, but it should be - explicitly specified that ct_config_xml callback module is to be - used by the Common Test.

+

The XML version shown in the chapter above can also be used, but it should be + explicitly specified that the ct_config_xml callback module is to be + used by Common Test.

Example of how to assert that the configuration data is available and use it for an FTP session:

@@ -361,9 +361,9 @@
- Example of own configuration handler -

Simple configuration hanling driver which will ask external server for - configuration data can be implemented this way:

+ Example of user specific configuration handler +

A simple configuration handling driver which will ask an external server for + configuration data can be implemented this way:

 -module(config_driver).
 -export([read_config/1, check_parameter/1]).
@@ -385,17 +385,16 @@ check_parameter(ServerName)->
                 {error, nofile}->
                     {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}}
             end
-    end.
-    
-

Configuration string for this driver may be "config_server", if the - config_server.erl module below is built and is exist in the code path - during test execution:

+ end. + +

The configuration string for this driver may be "config_server", if the + config_server.erl module below is compiled and exists in the code path + during test execution:

 -module(config_server).
 -export([start/0, stop/0, init/1, get_config/0, loop/0]).
 
 -define(REGISTERED_NAME, ct_test_config_server).
--define(vsn, 0.1).
 
 start()->
     case whereis(?REGISTERED_NAME) of
@@ -452,12 +451,13 @@ wait()->
             wait();
         _Pid->
             ok
-    end.
-    
-

There two modules provide the ability to dynamically reload configuration - variables. If the ct:reload_config(localtime) is called from the test - case, all variables which were loaded with the config_driver above, will be - updated with the new values.

+ end. + +

In this example, the handler also provides the ability to dynamically reload + configuration variables. If ct:reload_config(localtime) is called from + the test case function, all variables loaded with config_driver:read_config/1 + will be updated with their latest values, and the new value for variable + localtime will be returned.

diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 22cb17e55d..d2a491e079 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -15,6 +15,14 @@ %% under the License. %% %% %CopyrightEnd% + +%%% @doc Common Test Framework functions for starting and stopping nodes for +%%% Large Scale Testing. +%%% +%%%

This module exports functions which are used by the Common Test Master +%%% to start and stop "slave" nodes. It is the default callback module for the +%%% {init, node_start} term of the Test Specification.

+ %%---------------------------------------------------------------------- %% File : ct_slave.erl %% Description : CT module for starting nodes for large-scale testing. diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 4e34c1513e..582ce5e49f 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -17,7 +17,7 @@ %% %CopyrightEnd% %% -%%% @doc Common Test Framework functions handlig test specifikations. +%%% @doc Common Test Framework functions handlig test specifications. %%% %%%

This module exports functions that are used within CT to %%% scan and parse test specifikations.

-- cgit v1.2.3 From 5cf552a62742c6ddc974ba5491188576d512254e Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 24 May 2010 17:38:19 +0200 Subject: Add run_test program for Common Test Common Test may now be started with the program run_test instead of the legacy shell script with the same name. Minor updates have also been made to the Webtool application. --- Makefile.in | 2 +- erts/Makefile.in | 3 + erts/etc/common/Makefile.in | 12 +- erts/etc/common/run_test.c | 496 +++++++++++++++++++++ lib/common_test/doc/src/ct_master_chapter.xml | 2 +- lib/common_test/doc/src/event_handler_chapter.xml | 2 +- lib/common_test/doc/src/install_chapter.xml | 135 +++--- lib/common_test/doc/src/run_test.xml | 59 ++- lib/common_test/doc/src/run_test_chapter.xml | 21 +- lib/common_test/doc/src/test_structure_chapter.xml | 4 +- lib/common_test/src/ct.erl | 8 +- lib/common_test/src/ct_config.erl | 0 lib/common_test/src/ct_config_plain.erl | 0 lib/common_test/src/ct_config_xml.erl | 0 lib/common_test/src/ct_repeat.erl | 2 +- lib/common_test/src/ct_run.erl | 4 +- lib/webtool/src/webtool.erl | 8 +- 17 files changed, 631 insertions(+), 127 deletions(-) create mode 100644 erts/etc/common/run_test.c mode change 100755 => 100644 lib/common_test/src/ct_config.erl mode change 100755 => 100644 lib/common_test/src/ct_config_plain.erl mode change 100755 => 100644 lib/common_test/src/ct_config_xml.erl diff --git a/Makefile.in b/Makefile.in index ac2015f8e4..7bdcdcc297 100644 --- a/Makefile.in +++ b/Makefile.in @@ -119,7 +119,7 @@ BINDIR = $(DESTDIR)$(EXTRA_PREFIX)$(bindir) # # Erlang base public files # -ERL_BASE_PUB_FILES=erl erlc epmd run_erl to_erl dialyzer typer escript +ERL_BASE_PUB_FILES=erl erlc epmd run_erl to_erl dialyzer typer escript run_test # ERLANG_INST_LIBDIR is the top directory where the Erlang installation # will be located when running. diff --git a/erts/Makefile.in b/erts/Makefile.in index fabf86db7c..4a37bcfb2c 100644 --- a/erts/Makefile.in +++ b/erts/Makefile.in @@ -92,10 +92,12 @@ local_setup: $(ERL_TOP)/bin/escript $(ERL_TOP)/bin/escript.exe \ $(ERL_TOP)/bin/dialyzer $(ERL_TOP)/bin/dialyzer.exe \ $(ERL_TOP)/bin/typer $(ERL_TOP)/bin/typer.exe \ + $(ERL_TOP)/bin/run_test $(ERL_TOP)/bin/run_test.exe \ $(ERL_TOP)/bin/start*.boot $(ERL_TOP)/bin/start*.script @if [ "X$(TARGET)" = "Xwin32" ]; then \ cp $(ERL_TOP)/bin/$(TARGET)/dialyzer.exe $(ERL_TOP)/bin/dialyzer.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/typer.exe $(ERL_TOP)/bin/typer.exe; \ + cp $(ERL_TOP)/bin/$(TARGET)/run_test.exe $(ERL_TOP)/bin/run_test.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/erlc.exe $(ERL_TOP)/bin/erlc.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/erl.exe $(ERL_TOP)/bin/erl.exe; \ cp $(ERL_TOP)/bin/$(TARGET)/werl.exe $(ERL_TOP)/bin/werl.exe; \ @@ -115,6 +117,7 @@ local_setup: $(ERL_TOP)/erts/etc/unix/cerl.src > $(ERL_TOP)/bin/cerl; \ cp $(ERL_TOP)/bin/$(TARGET)/dialyzer $(ERL_TOP)/bin/dialyzer; \ cp $(ERL_TOP)/bin/$(TARGET)/typer $(ERL_TOP)/bin/typer; \ + cp $(ERL_TOP)/bin/$(TARGET)/run_test $(ERL_TOP)/bin/run_test; \ cp $(ERL_TOP)/bin/$(TARGET)/erlc $(ERL_TOP)/bin/erlc; \ cp $(ERL_TOP)/bin/$(TARGET)/escript $(ERL_TOP)/bin/escript; \ chmod 755 $(ERL_TOP)/bin/erl $(ERL_TOP)/bin/erlc \ diff --git a/erts/etc/common/Makefile.in b/erts/etc/common/Makefile.in index d2a5080c68..6551b2999a 100644 --- a/erts/etc/common/Makefile.in +++ b/erts/etc/common/Makefile.in @@ -178,7 +178,7 @@ MC_OUTPUTS= \ MT_FLAG="-MD" endif INET_GETHOST = $(BINDIR)/inet_gethost.exe -INSTALL_EMBEDDED_PROGS += $(BINDIR)/typer.exe $(BINDIR)/dialyzer.exe $(BINDIR)/erlc.exe $(BINDIR)/start_erl.exe $(BINDIR)/escript.exe +INSTALL_EMBEDDED_PROGS += $(BINDIR)/typer.exe $(BINDIR)/dialyzer.exe $(BINDIR)/erlc.exe $(BINDIR)/start_erl.exe $(BINDIR)/escript.exe $(BINDIR)/run_test.exe INSTALL_SRC = $(WINETC)/start_erl.c $(WINETC)/Nmakefile.start_erl ERLEXECDIR=. INSTALL_LIBS = @@ -211,7 +211,7 @@ ERLSRV_OBJECTS= MC_OUTPUTS= INET_GETHOST = $(BINDIR)/inet_gethost@EXEEXT@ INSTALL_EMBEDDED_PROGS += $(BINDIR)/typer@EXEEXT@ $(BINDIR)/dialyzer@EXEEXT@ \ - $(BINDIR)/erlc@EXEEXT@ $(BINDIR)/escript@EXEEXT@ \ + $(BINDIR)/erlc@EXEEXT@ $(BINDIR)/escript@EXEEXT@ $(BINDIR)/run_test@EXEEXT@ \ $(BINDIR)/run_erl $(BINDIR)/to_erl $(BINDIR)/dyn_erl INSTALL_EMBEDDED_DATA = ../unix/start.src ../unix/start_erl.src INSTALL_TOP = Install @@ -274,6 +274,7 @@ endif rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/dyn_erl.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/safe_string.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/typer.o + rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/run_test.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/vxcall.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/erl.o rm -f $(ERL_TOP)/erts/obj*/$(TARGET)/werl.o @@ -349,6 +350,13 @@ $(BINDIR)/escript@EXEEXT@: $(OBJDIR)/escript.o $(OBJDIR)/escript.o: escript.c $(CC) $(CFLAGS) -o $@ -c escript.c +$(BINDIR)/run_test@EXEEXT@: $(OBJDIR)/run_test.o + $(PURIFY) $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/run_test.o -L$(OBJDIR) $(LIBS) + +$(OBJDIR)/run_test.o: run_test.c + $(CC) $(CFLAGS) -o $@ -c run_test.c + + #------------------------------------------------------------------------ # Windows specific targets # The windows platform is quite different from the others. erl/werl are small C programs diff --git a/erts/etc/common/run_test.c b/erts/etc/common/run_test.c new file mode 100644 index 0000000000..027d10acf8 --- /dev/null +++ b/erts/etc/common/run_test.c @@ -0,0 +1,496 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2006-2010. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Common Test front-end. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#ifdef __WIN32__ +#include +#endif + +#include + +#define NO 0 +#define YES 1 + +#define ASIZE(a) (sizeof(a)/sizeof(a[0])) + +static int debug = 0; /* Bit flags for debug printouts. */ + +static char** eargv_base; /* Base of vector. */ +static char** eargv; /* First argument for erl. */ + +static int eargc; /* Number of arguments in eargv. */ + +#ifdef __WIN32__ +# define QUOTE(s) possibly_quote(s) +# define IS_DIRSEP(c) ((c) == '/' || (c) == '\\') +# define ERL_NAME "erl.exe" +#else +# define QUOTE(s) s +# define IS_DIRSEP(c) ((c) == '/') +# define ERL_NAME "erl" +#endif + +#define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s) +#define PUSH(s) eargv[eargc++] = QUOTE(s) +#define PUSH2(s, t) PUSH(s); PUSH(t) +#define PUSH3(s, t, u) PUSH2(s, t); PUSH(u) +#define PUSH4(s, t, u, v) PUSH2(s, t); PUSH2(u, v) + +/* + * The possible modes to start Common Test + */ + +#define NORMAL_MODE 0 +#define VTS_MODE 1 +#define CT_SHELL_MODE 2 +#define MASTER_MODE 3 +#define ERL_SHELL_MODE 4 + +/* + * Distribution + */ + +#define SHORT_NAME 0 +#define FULL_NAME 1 + +/* + * Local functions. + */ + +static void error(char* format, ...); +static char* emalloc(size_t size); +static char* strsave(char* string); +static void push_words(char* src); +static int run_erlang(char* name, char** argv); +static char* get_default_emulator(char* progname); +#ifdef __WIN32__ +static char* possibly_quote(char* arg); +#endif + +/* + * Supply a strerror() function if libc doesn't. + */ +#ifndef HAVE_STRERROR + +extern int sys_nerr; + +#ifndef SYS_ERRLIST_DECLARED +extern const char * const sys_errlist[]; +#endif /* !SYS_ERRLIST_DECLARED */ + +char *strerror(int errnum) +{ + static char *emsg[1024]; + + if (errnum != 0) { + if (errnum > 0 && errnum < sys_nerr) + sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]); + else + sprintf((char *) &emsg[0], "errnum = %d ", errnum); + } + else { + emsg[0] = '\0'; + } + return (char *) &emsg[0]; +} +#endif /* !HAVE_STRERROR */ + +int +main(int argc, char** argv) +{ + int eargv_size; + int eargc_base; /* How many arguments in the base of eargv. */ + char* emulator; + char nodename[100]; + char browser[100]; + int ct_mode; + int dist_mode; + int cnt; + char** argv0 = argv; + + emulator = get_default_emulator(argv[0]); + + /* + * Allocate the argv vector to be used for arguments to Erlang. + * Arrange for starting to pushing information in the middle of + * the array, to allow easy addition of commands in the beginning. + */ + + eargv_size = argc*4+100; + eargv_base = (char **) emalloc(eargv_size*sizeof(char*)); + eargv = eargv_base; + eargc = 0; + push_words(emulator); + eargc_base = eargc; + eargv = eargv + eargv_size/2; + eargc = 0; + + strcpy(nodename, "ct"); + dist_mode = SHORT_NAME; + browser[0] = '\0'; + ct_mode = NORMAL_MODE; + cnt = argc; + + /* + * Check various flags before building command line + */ + + while (cnt > 1) { + if (strcmp(argv[1], "-vts") == 0) { + ct_mode = VTS_MODE; + } + else if (strcmp(argv[1], "-browser") == 0) { + strcpy(browser, argv[2]); + cnt--, argv++; + } + else if (strcmp(argv[1], "-shell") == 0) { + ct_mode = CT_SHELL_MODE; + } + else if (strcmp(argv[1], "-ctmaster") == 0) { + strcpy(nodename, "ct_master"); + ct_mode = MASTER_MODE; + } + else if (strcmp(argv[1], "-ctname") == 0) { + strcpy(nodename, argv[2]); + ct_mode = ERL_SHELL_MODE; + cnt--, argv++; + } + else if (strcmp(argv[1], "-sname") == 0) { + strcpy(nodename, argv[2]); + cnt--, argv++; + } + else if (strcmp(argv[1], "-name") == 0) { + strcpy(nodename, argv[2]); + dist_mode = FULL_NAME; + cnt--, argv++; + } + cnt--, argv++; + } + + argv = argv0; + + /* + * Push initial arguments. + */ + + if (dist_mode == FULL_NAME) { + PUSH2("-name", nodename); + } + else { + PUSH2("-sname", nodename); + } + + /* + * Push everything else + */ + + if (ct_mode == VTS_MODE) { + PUSH4("-s", "webtool", "script_start", "vts"); + if (browser[0] != '\0') PUSH(browser); + PUSH3("-s", "ct_run", "script_start"); + } + else if (ct_mode == CT_SHELL_MODE) { + PUSH3("-s", "ct_run", "script_start"); + } + else if (ct_mode == NORMAL_MODE) { + PUSH3("-s", "ct_run", "script_start"); + PUSH3("-s", "erlang", "halt"); + } + + while (argc > 1) { + if (strcmp(argv[1], "-config") == 0) + PUSH("-ct_config"); + else if (strcmp(argv[1], "-decrypt_key") == 0) + PUSH("-ct_decrypt_key"); + else if (strcmp(argv[1], "-decrypt_file") == 0) + PUSH("-ct_decrypt_file"); + else if ((strcmp(argv[1], "-sname") == 0) || (strcmp(argv[1], "-name") == 0)) + argc--, argv++; + else + PUSH(argv[1]); + + argc--, argv++; + } + + /* + * Move up the commands for invoking the emulator and adjust eargv + * accordingly. + */ + + while (--eargc_base >= 0) { + UNSHIFT(eargv_base[eargc_base]); + } + + /* + * Invoke Erlang with the collected options. + */ + + PUSH(NULL); + + return run_erlang(eargv[0], eargv); +} + +static void +push_words(char* src) +{ + char sbuf[1024]; + char* dst; + + dst = sbuf; + while ((*dst++ = *src++) != '\0') { + if (isspace((int)*src)) { + *dst = '\0'; + PUSH(strsave(sbuf)); + dst = sbuf; + do { + src++; + } while (isspace((int)*src)); + } + } + if (sbuf[0]) + PUSH(strsave(sbuf)); +} +#ifdef __WIN32__ +char *make_commandline(char **argv) +{ + static char *buff = NULL; + static int siz = 0; + int num = 0; + char **arg, *p; + + if (*argv == NULL) { + return ""; + } + for (arg = argv; *arg != NULL; ++arg) { + num += strlen(*arg)+1; + } + if (!siz) { + siz = num; + buff = malloc(siz*sizeof(char)); + } else if (siz < num) { + siz = num; + buff = realloc(buff,siz*sizeof(char)); + } + p = buff; + for (arg = argv; *arg != NULL; ++arg) { + strcpy(p,*arg); + p+=strlen(*arg); + *p++=' '; + } + *(--p) = '\0'; + + if (debug) { + printf("Processed commandline:%s\n",buff); + } + return buff; +} + +int my_spawnvp(char **argv) +{ + STARTUPINFO siStartInfo; + PROCESS_INFORMATION piProcInfo; + DWORD ec; + + memset(&siStartInfo,0,sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + siStartInfo.wShowWindow = SW_HIDE; + siStartInfo.dwFlags |= STARTF_USESHOWWINDOW; + + + if (!CreateProcess(NULL, + make_commandline(argv), + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &siStartInfo, + &piProcInfo)) { + return -1; + } + CloseHandle(piProcInfo.hThread); + + WaitForSingleObject(piProcInfo.hProcess,INFINITE); + if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) { + return 0; + } + return (int) ec; +} +#endif /* __WIN32__ */ + + +static int +run_erlang(char* progname, char** argv) +{ +#ifdef __WIN32__ + int status; +#endif + + if (debug) { + int i = 0; + while (argv[i] != NULL) + printf(" %s", argv[i++]); + printf("\n"); + } + +#ifdef __WIN32__ + /* + * Alas, we must wait here for the program to finish. + * Otherwise, the shell from which we were executed will think + * we are finished and print a prompt and read keyboard input. + */ + + status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/; + if (status == -1) { + fprintf(stderr, "run_test: Error executing '%s': %d", progname, + GetLastError()); + } + return status; +#else + execvp(progname, argv); + error("Error %d executing \'%s\'.", errno, progname); + return 2; +#endif +} + +static void +error(char* format, ...) +{ + char sbuf[1024]; + va_list ap; + + va_start(ap, format); + vsprintf(sbuf, format, ap); + va_end(ap); + fprintf(stderr, "run_test: %s\n", sbuf); + exit(1); +} + +static char* +emalloc(size_t size) +{ + char *p = malloc(size); + if (p == NULL) + error("Insufficient memory"); + return p; +} + +static char* +strsave(char* string) +{ + char* p = emalloc(strlen(string)+1); + strcpy(p, string); + return p; +} + +static char* +get_default_emulator(char* progname) +{ + char sbuf[MAXPATHLEN]; + char* s; + + strcpy(sbuf, progname); + for (s = sbuf+strlen(sbuf); s >= sbuf; s--) { + if (IS_DIRSEP(*s)) { + strcpy(s+1, ERL_NAME); +#ifdef __WIN32__ + if (_access(sbuf, 0) != -1) { + return strsave(sbuf); + } +#else + if (access(sbuf, 1) != -1) { + return strsave(sbuf); + } +#endif + break; + } + } + return ERL_NAME; +} + +#ifdef __WIN32__ +static char* +possibly_quote(char* arg) +{ + int mustQuote = NO; + int n = 0; + char* s; + char* narg; + + if (arg == NULL) { + return arg; + } + + /* + * Scan the string to find out if it needs quoting and return + * the original argument if not. + */ + + for (s = arg; *s; s++, n++) { + switch(*s) { + case ' ': + mustQuote = YES; + continue; + case '"': + mustQuote = YES; + n++; + continue; + case '\\': + if(s[1] == '"') + n++; + continue; + default: + continue; + } + } + if (!mustQuote) { + return arg; + } + + /* + * Insert the quotes and put a backslash in front of every quote + * inside the string. + */ + + s = narg = emalloc(n+2+1); + for (*s++ = '"'; *arg; arg++, s++) { + if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) { + *s++ = '\\'; + } + *s = *arg; + } + if (s[-1] == '\\') { + *s++ ='\\'; + } + *s++ = '"'; + *s = '\0'; + return narg; +} +#endif /* __WIN32__ */ diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml index 4ab891edee..14f318759e 100644 --- a/lib/common_test/doc/src/ct_master_chapter.xml +++ b/lib/common_test/doc/src/ct_master_chapter.xml @@ -186,7 +186,7 @@ Running Test Suites chapter). The result is that any test specified to run on a node with the same name as the Common Test node in question (typically ct@somehost if started - with the run_test script), will be performed. Tests without explicit + with the run_test program), will be performed. Tests without explicit node association will always be performed too of course!

It is recommended that absolute paths are used for log directories, diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml index a550810850..35b684730e 100644 --- a/lib/common_test/doc/src/event_handler_chapter.xml +++ b/lib/common_test/doc/src/event_handler_chapter.xml @@ -74,7 +74,7 @@ example).

It is not possible to specify start arguments to the event handlers when - using the run_test script. You may however pass along start arguments + using the run_test program. You may however pass along start arguments if you use the ct:run_test/1 function. An event_handler tuple in the argument Opts has the following definition (see also ct:run_test/1 in the reference manual):

diff --git a/lib/common_test/doc/src/install_chapter.xml b/lib/common_test/doc/src/install_chapter.xml index e1ff5abf6a..d5a12cd9f3 100644 --- a/lib/common_test/doc/src/install_chapter.xml +++ b/lib/common_test/doc/src/install_chapter.xml @@ -4,7 +4,7 @@
- 20072009 + 20072010 Ericsson AB. All Rights Reserved. @@ -33,82 +33,77 @@ General information -

The two main interfaces for running tests with Common Test - are an executable Bourne shell script named run_test and an - erlang module named ct. The shell script will work on Unix/Linux - (and Linux-like environments such as Cygwin on Windows) and the - ct interface functions can be called from the Erlang shell - (or from any Erlang function) on any supported platform.

- -

The Common Test application is installed with the Erlang/OTP - system and no explicit installation is required to start using - Common Test by means of the interface functions in the ct - module. If you wish to use run_test, however, this script - needs to be generated first, according to the instructions below.

- - -
- Unix/Linux - -

Go to the ]]> directory, located - among the other OTP applications (under the OTP lib directory). Here you - execute the install.sh script with argument local:

+

The two main interfaces for running tests with Common Test + are an executable program named run_test and an + erlang module named ct. The run_test program + is compiled for the underlying operating system (e.g. Unix/Linux + or Windows) during the build of the Erlang/OTP system, and is + installed automatically with other executable programs in + the top level bin directory of Erlang/OTP. + The ct interface functions can be called from the Erlang shell, + or from any Erlang function, on any supported platform.

+ +

A legacy Bourne shell script - also named run_test - exists, + which may be manually generated and installed. This script may be used + instead of the run_test program mentioned above, e.g. if the user + wishes to modify or customize the Common Test start flags in a simpler + way than making changes to the run_test C program.

-

- $ ./install.sh local -

+

The Common Test application is installed with the Erlang/OTP + system and no additional installation step is required to start using + Common Test by means of the run_test executable program, and/or the interface + functions in the ct module. If you wish to use the legacy Bourne + shell script version of run_test, however, this script needs to be + generated first, according to the instructions below.

+ +

Before reading on, please note that since Common Test version + 1.5, the run_test shell script is no longer required for starting + tests with Common Test from the OS command line. The run_test + program (descibed above) is the new recommended command line interface + for Common Test. The shell script exists mainly for legacy reasons and + may not be updated in future releases of Common Test. It may even be removed. +

+ +

Optional step to generate a shell script for starting Common Test:

+

To generate the run_test shell script, navigate to the + ]]> directory, located among the other + OTP applications (under the OTP lib directory). Here execute the + install.sh script with argument local:

+ +

+ $ ./install.sh local +

-

This generates the executable run_test script in the - /priv/bin]]> directory. The script - will include absolute paths to the Common Test and Test Server - application directories, so it's possible to copy or move the script to - a different location on the file system, if desired, without having to - update it. It's of course possible to leave the script under the - priv/bin directory and update the PATH variable accordingly (or - create a link or alias to it).

- -

If you, for any reason, have copied Common Test and Test Server - to a different location than the default OTP lib directory, you can - generate a run_test script with a different top level directory, - simply by specifying the directory, instead of local, when running - install.sh. Example:

- -

- $ install.sh /usr/local/test_tools +

This generates the executable run_test script in the + /priv/bin]]> directory. The script + will include absolute paths to the Common Test and Test Server + application directories, so it's possible to copy or move the script to + a different location on the file system, if desired, without having to + update it. It's of course possible to leave the script under the + priv/bin directory and update the PATH variable accordingly (or + create a link or alias to it).

+ +

If you, for any reason, have copied Common Test and Test Server + to a different location than the default OTP lib directory, you can + generate a run_test script with a different top level directory, + simply by specifying the directory, instead of local, when running + install.sh. Example:

+ +

+ $ install.sh /usr/local/test_tools

Note that the ]]> and - ]]> directories must be located under the - same top directory. Note also that the install script does not copy files - or update environment variables. It only generates the run_test - script.

+ ]]> directories must be located under the + same top directory. Note also that the install script does not copy files + or update environment variables. It only generates the run_test + script.

-

Whenever you install a new version of Erlang/OTP, the run_test - script needs to be regenerated, or updated manually with new directory names - (new version numbers), for it to "see" the latest Common Test and Test Server - versions.

+

Whenever you install a new version of Erlang/OTP, the run_test + script needs to be regenerated, or updated manually with new directory names + (new version numbers), for it to "see" the latest Common Test and Test Server + versions.

-

For more information on the run_test script and the ct - module, please see the reference manual.

-
- -
- Windows - -

On Windows it is very convenient to use Cygwin (www.cygwin.com) - for running Common Test and Erlang, since it enables you to use the - run_test script for starting Common Test. If you are a Cygwin - user, simply follow the instructions above for generating the run_test - script.

- -

If you do not use Cygwin, you have to rely on the API functions - in the ct module (instead of run_test) for running - Common Test as described initially in this chapter.

- -

If you, for any reason, have chosen to store Common Test and Test Server - in a different location than the default OTP lib directory, make - sure the ebin directories of these applications are included - in the Erlang code server path (so the application modules can be loaded).

diff --git a/lib/common_test/doc/src/run_test.xml b/lib/common_test/doc/src/run_test.xml index 451d3c5737..49538a7483 100644 --- a/lib/common_test/doc/src/run_test.xml +++ b/lib/common_test/doc/src/run_test.xml @@ -21,7 +21,7 @@
- The run_test shell script + The run_test program Peter Andersson Peter Andersson @@ -31,38 +31,39 @@ PA2 run_test.xml
- run_test - Shell script used for starting - Common Test from the Unix command line. + run_test + Program used for starting Common Test from the + OS command line. -

The run_test script is automatically generated as Common - Test is installed (please see the Installation chapter in the Common - Test User's Guide for more information). The script accepts a number - of different start flags. Some flags trigger run_test - to start the Common Test application and pass on data to it. Some - flags start an Erlang node prepared for running Common Test in a - particular mode.

-

run_test also accepts Erlang emulator - flags. These are used when run_test calls erl to - start the Erlang node (making it possible to e.g. add directories to - the code server path, change the cookie on the node, start - additional applications, etc).

+

The run_test program is automatically installed with Erlang/OTP + and Common Test (please see the Installation chapter in the Common + Test User's Guide for more information). The program accepts a number + of different start flags. Some flags trigger run_test + to start the Common Test application and pass on data to it. Some + flags start an Erlang node prepared for running Common Test in a + particular mode.

+ +

run_test also accepts Erlang emulator flags. These are used + when run_test calls erl to start the Erlang node + (making it possible to e.g. add directories to the code server path, + change the cookie on the node, start additional applications, etc).

+

If run_test is called without parameters, it prints all valid - start flags to stdout.

+ start flags to stdout.

Run tests from command line
-    	run_test [-dir TestDir1 TestDir2 .. TestDirN] |	
-	[-suite Suite1 Suite2 .. SuiteN 
+	run_test [-dir TestDir1 TestDir2 .. TestDirN] |
+	[-suite Suite1 Suite2 .. SuiteN
 	 [[-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]]]
 	[-step [config | keep_inactive]]
 	[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
 	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
-    ConfigString2 and .. and CallbackModuleN ConfigStringN]
+         ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
 	[-logdir LogDir]
 	[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]
@@ -82,7 +83,7 @@
 	run_test -spec TestSpec1 TestSpec2 .. TestSpecN
 	[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
 	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
-    ConfigString2 and .. and CallbackModuleN ConfigStringN]
+         ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
 	[-logdir LogDir]
 	[-allow_user_terms]
@@ -103,7 +104,7 @@
         run_test -vts [-browser Browser]
 	[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
 	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
-    ConfigString2 and .. and CallbackModuleN ConfigStringN]
+         ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
         [-dir TestDir1 TestDir2 .. TestDirN] |
         [-suite Suite [[-group Group] [-case Case]]]
@@ -119,10 +120,10 @@
   
Run CT in interactive mode
-	run_test -shell 
+	run_test -shell
 	[-config ConfigFile1 ConfigFile2 ... ConfigFileN]
 	[-userconfig CallbackModule1 ConfigString1 and CallbackModule2
-     ConfigString2 and .. and CallbackModuleN ConfigStringN]
+         ConfigString2 and .. and CallbackModuleN ConfigStringN]
 	[-decrypt_key Key] | [-decrypt_file KeyFile]
@@ -137,12 +138,10 @@
- See also -

Please read the Running Test Suites - chapter in the Common Test User's Guide for information about the meaning of the - different start flags.

+ See also +

Please read the Running Test Suites + chapter in the Common Test User's Guide for information about the meaning of the + different start flags.

- - diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 44b4077ec4..917f3374db 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -102,10 +102,10 @@
- Running tests from the UNIX command line + Running tests from the OS command line -

The script run_test can be used for running tests from - the Unix/Linux command line, e.g. +

The run_test program can be used for running tests from + the OS command line, e.g.

-dir ]]> @@ -168,7 +168,7 @@ the current working directory of the Erlang Runtime System during the test run!

-

For details on how to generate the run_test script, see the +

For more information about the run_test program, see the Installation chapter.

@@ -177,7 +177,7 @@ Running tests from the Web based GUI

The web based GUI, VTS, is started with the run_test - script. From the GUI you can load config files, and select + program. From the GUI you can load config files, and select directories, suites and cases to run. You can also state the config files, directories, suites and cases on the command line when starting the web based GUI. @@ -201,10 +201,11 @@

Example:

Note that the browser must run as a separate OS process or VTS will hang!

-

If no specific browser start command is specified, netscape will +

If no specific browser start command is specified, Firefox will be the default browser on Unix platforms and Internet Explorer on Windows. - If Common Test fails to start a browser automatically, start your - favourite browser manually instead and type in the URL that Common Test + If Common Test fails to start a browser automatically, or 'none' is + specified as the value for -browser (i.e. -browser none), start your + favourite browser manually and type in the URL that Common Test displays in the shell.

@@ -214,7 +215,7 @@

Common Test provides an Erlang API for running tests. The main (and most flexible) function for specifying and executing tests is called ct:run_test/1. This function takes the same start parameters as - the run_test script described above, only the flags are instead + the run_test program described above, only the flags are instead given as options in a list of key-value tuples. E.g. a test specified with run_test like:

$ run_test -suite ./my_SUITE -logdir ./results

@@ -240,7 +241,7 @@ manually and call ct:install/1 to install any configuration data you might need (use [] as argument otherwise), then call ct:start_interactive/0 to start Common Test. If you use - the run_test script, you may start the Erlang shell and Common Test + the run_test program, you may start the Erlang shell and Common Test in the same go by using the -shell and, optionally, the -config flag:

diff --git a/lib/common_test/doc/src/test_structure_chapter.xml b/lib/common_test/doc/src/test_structure_chapter.xml index c8628b3a7a..60d01161ae 100644 --- a/lib/common_test/doc/src/test_structure_chapter.xml +++ b/lib/common_test/doc/src/test_structure_chapter.xml @@ -146,8 +146,8 @@ run_test - The name of an executable Bourne shell script that may be - used on Linux/Unix as an interface for specifying and running + The name of an executable program that may be + used as an interface for specifying and running tests with Common Test. diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index c32075332b..2d71a3812d 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -96,7 +96,7 @@ %%% install([{config,["config_node.ctc","config_user.ctc"]}]).

%%% %%%

Note that this function is automatically run by the -%%% run_test script.

+%%% run_test program.

install(Opts) -> ct_run:install(Opts). @@ -169,11 +169,11 @@ run(TestDirs) -> %%% DecryptFile = string() %%% Result = [TestResult] | {error,Reason} %%% @doc Run tests as specified by the combination of options in Opts. -%%% The options are the same as those used with the run_test script. +%%% The options are the same as those used with the run_test program. %%% Note that here a TestDir can be used to point out the path to %%% a Suite. Note also that the option testcase %%% corresponds to the -case option in the run_test -%%% script. Configuration files specified in Opts will be +%%% program. Configuration files specified in Opts will be %%% installed automatically at startup. run_test(Opts) -> ct_run:run_test(Opts). @@ -215,7 +215,7 @@ step(TestDir,Suite,Case,Opts) -> %%% %%%

From this mode all test case support functions can be executed %%% directly from the erlang shell. The interactive mode can also be -%%% started from the unix command line with run_test -shell +%%% started from the OS command line with run_test -shell %%% [-config File...].

%%% %%%

If any functions using "required config data" (e.g. telnet or diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl old mode 100755 new mode 100644 diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl old mode 100755 new mode 100644 diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl old mode 100755 new mode 100644 diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl index 7ac6e045d7..2bd265caf9 100644 --- a/lib/common_test/src/ct_repeat.erl +++ b/lib/common_test/src/ct_repeat.erl @@ -20,7 +20,7 @@ %%% @doc Common Test Framework module that handles repeated test runs %%% %%%

This module exports functions for repeating tests. The following -%%% script flags (or equivalent ct:run_test/1 options) are supported: +%%% start flags (or equivalent ct:run_test/1 options) are supported: %%% -until , StopTime = YYMoMoDDHHMMSS | HHMMSS %%% -duration , DurTime = HHMMSS %%% -force_stop diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 5fd89bc499..4f8e3e1a98 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -49,7 +49,7 @@ %%%----------------------------------------------------------------- %%% @spec script_start() -> void() %%% -%%% @doc Start tests via the run_test script. +%%% @doc Start tests via the run_test program or script. %%% %%%

Example:
./run_test -config config.ctc -dir %%% $TEST_DIR

@@ -390,7 +390,7 @@ script_start4(shell, _ConfigFiles, _EvHandlers, _Test, _Step, _Args, _LogDir) -> %%%----------------------------------------------------------------- %%% @spec script_usage() -> ok -%%% @doc Print script usage information for run_test. +%%% @doc Print usage information for run_test. script_usage() -> io:format("\n\nUsage:\n\n"), io:format("Run tests in web based GUI:\n\n" diff --git a/lib/webtool/src/webtool.erl b/lib/webtool/src/webtool.erl index 51d821751c..006f43f8b3 100644 --- a/lib/webtool/src/webtool.erl +++ b/lib/webtool/src/webtool.erl @@ -139,7 +139,7 @@ script_start([App]) -> DefaultBrowser = case os:type() of {win32,_} -> iexplore; - _ -> netscape + _ -> firefox end, script_start([App,DefaultBrowser]); script_start([App,Browser]) -> @@ -159,6 +159,8 @@ script_start([App,Browser]) -> "http://localhost:" ++ PortStr ++ "/" ++ StartPage end, case Browser of + none -> + ok; iexplore when OSType == win32-> io:format("Starting internet explorer...\n"), {ok,R} = win32reg:open(""), @@ -170,7 +172,7 @@ script_start([App,Browser]) -> _ when OSType == win32 -> io:format("Starting ~w...\n",[Browser]), os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url); - B when B==netscape; B==mozilla -> + B when B==firefox; B==mozilla -> io:format("Sending URL to ~w...",[Browser]), BStr = atom_to_list(Browser), SendCmd = BStr ++ " -raise -remote \'openUrl(" ++ @@ -209,7 +211,7 @@ usage() -> "\nUsage: start_webtool application [ browser ]\n" "\nAvailable applications are: ~p\n" "Default browser is \'iexplore\' (Internet Explorer) on Windows " - "or else \'netscape\'\n", + "or else \'firefox\'\n", [Apps]), stop(). -- cgit v1.2.3 From 80d1a5c5753a18491bfe29740ab5b0af22f0bff2 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 27 May 2010 00:07:32 +0200 Subject: Update Webtool documentation Documentation changed to specify Firefox as default browser on unix/linux. --- lib/webtool/doc/src/start_webtool.xml | 4 ++-- lib/webtool/vsn.mk | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/webtool/doc/src/start_webtool.xml b/lib/webtool/doc/src/start_webtool.xml index 184285c631..7bc94bbf4c 100644 --- a/lib/webtool/doc/src/start_webtool.xml +++ b/lib/webtool/doc/src/start_webtool.xml @@ -56,7 +56,7 @@ Or http://127.0.0.1:8888/ Usage: start_webtool application [ browser ] Available applications are: [orber,appmon,crashdump_viewer,webcover] -Default browser is 'iexplore' (Internet Explorer) on Windows or else 'netscape' +Default browser is 'iexplore' (Internet Explorer) on Windows or else 'firefox'

To start any of the listed applications, give the application name as the first argument, e.g.

@@ -68,7 +68,7 @@ Starting webcover...
 Sending URL to netscape...done        

The WebTool application WebCover is then started and the default browser is used. The default browser is Internet - Explorer on Windows or else Netscape. + Explorer on Windows or else Firefox.

To use another browser, give the browser's start command as the second argument, e.g.

diff --git a/lib/webtool/vsn.mk b/lib/webtool/vsn.mk index 712e3abbaf..6b76883330 100644 --- a/lib/webtool/vsn.mk +++ b/lib/webtool/vsn.mk @@ -1 +1 @@ -WEBTOOL_VSN=0.8.6 +WEBTOOL_VSN=0.8.7 -- cgit v1.2.3 From 7b33aa92bb2558ba04a6436203638fd46592b8d2 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 27 May 2010 00:46:37 +0200 Subject: Improve documentation and fix minor problems General documentation and code updates. --- lib/common_test/Makefile | 6 ++-- lib/common_test/doc/src/ct_master_chapter.xml | 52 ++++++++++++++------------- lib/common_test/doc/src/run_test_chapter.xml | 14 ++++---- lib/common_test/src/ct_config_plain.erl | 2 +- lib/common_test/src/ct_config_xml.erl | 5 ++- lib/common_test/src/ct_run.erl | 46 +----------------------- 6 files changed, 43 insertions(+), 82 deletions(-) diff --git a/lib/common_test/Makefile b/lib/common_test/Makefile index ebca4523ab..c166e48df7 100644 --- a/lib/common_test/Makefile +++ b/lib/common_test/Makefile @@ -25,12 +25,12 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # ifeq ($(findstring linux,$(TARGET)),linux) -SUB_DIRECTORIES = doc/src src priv +SUB_DIRECTORIES = doc/src src else ifeq ($(findstring solaris,$(TARGET)),solaris) -SUB_DIRECTORIES = doc/src src priv +SUB_DIRECTORIES = doc/src src else -SUB_DIRECTORIES = doc/src src priv +SUB_DIRECTORIES = doc/src src endif endif diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml index 14f318759e..bc51957aee 100644 --- a/lib/common_test/doc/src/ct_master_chapter.xml +++ b/lib/common_test/doc/src/ct_master_chapter.xml @@ -30,6 +30,7 @@
+ General

Large scale automated testing requires running multiple independent test sessions in parallel. This is accomplished by running @@ -105,6 +106,7 @@ manual page for this module.

+ Test Specifications

The test specifications used as input to CT Master are fully compatible with the specifications used as input to the regular CT server. The syntax is described in the @@ -195,11 +197,12 @@

- Automatic startup of the test target nodes + Automatic startup of test target nodes -

Is is possible to perform initial actions on test target nodes - automatically using a new term in the test specification, init.

+

Is is possible to automatically start, and perform initial actions, on + test target nodes by using the test specification term init.

Currently, two sub-terms are supported, node_start and eval.

+

Example:

      {node, node1, node1@host1}.
      {node, node2, node1@host2}.
@@ -207,38 +210,39 @@
      {node, node4, node1@host3}.
      {init, node1, [{node_start, [{callback_module, my_slave_callback}]}]}.
      {init, [node2, node3], {node_start, [{username, "ct_user"}, {password, "ct_password"}]}}.
-     {init, node4, {eval, {module, function, []}}}.
-    
+ {init, node4, {eval, {module, function, []}}}. +

This test specification declares that node1@host1 is to be started using - user's callback my_slave_callback with no parameters, and nodes node1@host2 and - node2@host2 will be started with the default callback module ct_slave, - using the given user name and password to log into remote host host2. - Also, there will be function module:function/0 evaluated on the - node1@host3, and result of this call will be printed to the log.

-

Default ct_slave callback module - delivered with the Common Test has the following features: + the user callback function callback_module:my_slave_callback/0, and nodes + node1@host2 and node2@host2 will be started with the default callback + module ct_slave. The given user name and password is used to log into remote + host host2. Also, the function module:function/0 will be evaluated on + node1@host3, and the result of this call will be printed to the log.

+ +

The default ct_slave callback module, + which is part of the Common Test application, has the following features: - Starting the Erlang nodes or local or remote hosts - (ssh is used for remote ones); + Starting Erlang target nodes on local or remote hosts + (ssh is used for communication). - Ability to start Erlang emulator with the additional flags - (any flags supported by erl are supported); + Ability to start an Erlang emulator with additional flags + (any flags supported by erl are supported). - Supervision of the node being start using internal callback - functions. Used to prevent hanging of started nodes. Configurable; + Supervision of a node being started by means of internal callback + functions. Used to prevent hanging nodes. (Configurable). - Monitoring of the master node by the slaves. Slave node may be - stopped in case of master node termination. Configurable; + Monitoring of the master node by the slaves. A slave node may be + stopped in case the master node terminates. (Configurable). - Execution of the user's functions after slave node is started. + Execution of user functions after a slave node is started. Functions can be given as a list of {Module, Function, Arguments} tuples.

-

Note that it is possible to specify eval term for the node as well +

Note that it is possible to specify an eval term for the node as well as startup_functions in the node_start options list. In this - case first node will be started, then the startup_functions are - executed, and finally functions specified with eval will be called. + case first the node will be started, then the startup_functions are + executed, and finally functions specified with eval are called.

diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 917f3374db..7fd3174e71 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -354,13 +354,17 @@

Below is the test specification syntax. Test specifications can be used to run tests both in a single test host environment and in - a distributed Common Test environment. Node parameters are only relevant in the - latter (see the chapter about running Common Test in distributed mode for information). - For details on the event_handler term, see the + a distributed Common Test environment (Large Scale Testing). The init term, + as well as node parameters, are only relevant in the latter (see the + Large Scale Testing + chapter for information). For details on the event_handler term, see the Event Handling chapter.

Config terms:

+      {init, InitOptions}.
+      {init, [NodeAlias], InitOptions}.
+
       {node, NodeAlias, Node}.
  
       {cover, CoverSpecFile}.
@@ -384,9 +388,6 @@
       {event_handler, NodeRefs, EventHandlers}.
       {event_handler, EventHandlers, InitArgs}.
       {event_handler, NodeRefs, EventHandlers, InitArgs}.
-
-      {init, Options}.
-      {init, [NodeAlias], Options}.
     

Test terms:

@@ -404,6 +405,7 @@
     

Types:

+      InitOptions   = term()
       NodeAlias     = atom()
       Node          = node()
       NodeRef       = NodeAlias | Node | master
diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl
index 0fed58e45a..3fbc8af9fb 100644
--- a/lib/common_test/src/ct_config_plain.erl
+++ b/lib/common_test/src/ct_config_plain.erl
@@ -65,7 +65,7 @@ read_config(ConfigFile) ->
 	    end
     end.
 
-% check against existence of config file
+% check if config file exists
 check_parameter(File)->
     case filelib:is_file(File) of
 	true->
diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl
index 4ced80aeac..8a6e75e635 100644
--- a/lib/common_test/src/ct_config_xml.erl
+++ b/lib/common_test/src/ct_config_xml.erl
@@ -33,7 +33,7 @@ read_config(ConfigFile) ->
 	    {error, Error, ErroneousString}
     end.
 
-% check against existence of the file
+% check file exists
 check_parameter(File)->
     case filelib:is_file(File) of
 	true->
@@ -107,8 +107,7 @@ transform_entity({Tag, String})->
 	     throw(Error)
     end.
 
-% transform a string with Erlang terms to the terms
-% stolen from trapexit.org :-)
+% transform a string with Erlang terms
 list_to_term(String) ->
     {ok, T, _} = erl_scan:string(String++"."),
     case catch erl_parse:parse_term(T) of
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 4f8e3e1a98..021bda2951 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -101,11 +101,6 @@ script_start() ->
     Res.
 
 script_start1(Parent, Args) ->
-    case lists:keymember(preload, 1, Args) of
-	true -> preload();
-	false -> ok
-    end,
-
     VtsOrShell = 
 	case lists:keymember(vts, 1, Args) of
 	    true -> 
@@ -467,7 +462,7 @@ install(Opts, LogDir) ->
     case whereis(ct_util_server) of
 	undefined ->
 	    VarFile = variables_file_name(LogDir),
-	    io:format("Varfile=~p~n", [VarFile]),
+	    %% io:format("Varfile=~p~n", [VarFile]),
 	    case file:open(VarFile, [write]) of
 		{ok,Fd} ->
 		    [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts],
@@ -1780,45 +1775,6 @@ stop_trace(true) ->
     dbg:stop_clear();
 stop_trace(false) ->
     ok.
-
-preload() ->
-    io:format("~nLoading Common Test and Test Server modules...~n~n"),
-    preload_mod([ct_logs,
-		 ct_make,
-		 ct_telnet,
-		 ct,
-		 ct_master,
-		 ct_testspec,
-		 ct_cover,
-		 ct_master_event,
-		 ct_util,
-		 ct_event,           
-		 ct_master_logs,
-		 ct_framework,
-		 teln,
-		 ct_ftp,
-		 ct_rpc,
-		 unix_telnet,
-		 ct_gen_conn,
-		 ct_line,
-		 ct_snmp,
-		 test_server_sup,
-		 test_server,
-		 test_server_ctrl,
-		 test_server_h,
-		 test_server_line,
-		 test_server_node]).
-
-preload_mod([M|Ms]) ->
-    case code:is_loaded(M) of
-	false ->
-	    {module,M} = code:load_file(M),
-	    preload_mod(Ms);
-	_ ->
-	    ok
-    end;
-preload_mod([]) ->
-    ok.
     
 ensure_atom(Atom) when is_atom(Atom) ->
     Atom;
-- 
cgit v1.2.3


From 7b48e13f301bcfe25ac55d6bc3cf612707673b16 Mon Sep 17 00:00:00 2001
From: Peter Andersson 
Date: Fri, 28 May 2010 10:59:25 +0200
Subject: Fix error installing the run_test program

---
 erts/etc/unix/Install.src | 1 +
 erts/etc/win32/Install.c  | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/erts/etc/unix/Install.src b/erts/etc/unix/Install.src
index 83f9690782..7dead62ab0 100644
--- a/erts/etc/unix/Install.src
+++ b/erts/etc/unix/Install.src
@@ -89,6 +89,7 @@ cp -p $ERL_ROOT/erts-%I_VSN%/bin/erl .
 cp -p $ERL_ROOT/erts-%I_VSN%/bin/erlc .
 cp -p $ERL_ROOT/erts-%I_VSN%/bin/dialyzer .
 cp -p $ERL_ROOT/erts-%I_VSN%/bin/typer .
+cp -p $ERL_ROOT/erts-%I_VSN%/bin/run_test .
 cp -p $ERL_ROOT/erts-%I_VSN%/bin/escript .
 
 #
diff --git a/erts/etc/win32/Install.c b/erts/etc/win32/Install.c
index 4a559cd8a2..d689d1ca6d 100644
--- a/erts/etc/win32/Install.c
+++ b/erts/etc/win32/Install.c
@@ -45,8 +45,8 @@ int main(int argc, char **argv)
     InitSection *ini_section;
     HANDLE module = GetModuleHandle(NULL);
     char *binaries[] = { "erl.exe", "werl.exe", "erlc.exe",
-			     "dialyzer.exe", "typer.exe",
-			     "escript.exe", NULL };
+			 "dialyzer.exe", "typer.exe",
+			 "escript.exe", "run_test.exe", NULL };
     char *scripts[] = { "start_clean.boot", "start_sasl.boot", NULL };
     char fromname[MAX_PATH];
     char toname[MAX_PATH];
-- 
cgit v1.2.3


From 24f7909acb5d395756ce1912ab426090e369eb84 Mon Sep 17 00:00:00 2001
From: Peter Andersson 
Date: Wed, 2 Jun 2010 13:18:45 +0200
Subject: Add -erl_args flag to run_test program

With -erl_args it's possible to divide the start options on the command line and this way specify explicitly which options are meant for Common Test, and which are meant for other OTP applications (or ERTS).
---
 erts/etc/common/run_test.c | 88 ++++++++++++++++++++++++++++------------------
 1 file changed, 53 insertions(+), 35 deletions(-)

diff --git a/erts/etc/common/run_test.c b/erts/etc/common/run_test.c
index 027d10acf8..d585d79230 100644
--- a/erts/etc/common/run_test.c
+++ b/erts/etc/common/run_test.c
@@ -128,6 +128,7 @@ main(int argc, char** argv)
     int ct_mode;
     int dist_mode;
     int cnt;
+    int erl_args;
     char** argv0 = argv;
 
     emulator = get_default_emulator(argv[0]);
@@ -151,42 +152,50 @@ main(int argc, char** argv)
     dist_mode = SHORT_NAME;
     browser[0] = '\0';
     ct_mode = NORMAL_MODE;
-    cnt = argc;
+    erl_args = argc;
+    cnt = 1;
 
     /*
      * Check various flags before building command line
      */
 
-    while (cnt > 1) {
-	if (strcmp(argv[1], "-vts") == 0) {
-	    ct_mode = VTS_MODE;
-	}
-	else if (strcmp(argv[1], "-browser") == 0) {
-	    strcpy(browser, argv[2]);
-	    cnt--, argv++;
-	}
-	else if (strcmp(argv[1], "-shell") == 0) {
-	    ct_mode = CT_SHELL_MODE;
-	}
-	else if (strcmp(argv[1], "-ctmaster") == 0) {
-	    strcpy(nodename, "ct_master");
-	    ct_mode = MASTER_MODE;
-	}
-	else if (strcmp(argv[1], "-ctname") == 0) {
-	    strcpy(nodename, argv[2]);
-	    ct_mode = ERL_SHELL_MODE;
-	    cnt--, argv++;
+    while (cnt < argc) {
+	if (strcmp(argv[1], "-erl_args") == 0) {
+	    erl_args = cnt;
 	}
 	else if (strcmp(argv[1], "-sname") == 0) {
 	    strcpy(nodename, argv[2]);
-	    cnt--, argv++;
+	    cnt++, argv++;
 	}
 	else if (strcmp(argv[1], "-name") == 0) {
 	    strcpy(nodename, argv[2]);
 	    dist_mode = FULL_NAME;
-	    cnt--, argv++;
+	    cnt++, argv++;
+	}
+	else {
+	    if (cnt < erl_args) {
+		if (strcmp(argv[1], "-vts") == 0) {
+		    ct_mode = VTS_MODE;
+		}
+		else if (strcmp(argv[1], "-browser") == 0) {
+		    strcpy(browser, argv[2]);
+		    cnt++, argv++;
+		}
+		else if (strcmp(argv[1], "-shell") == 0) {
+		    ct_mode = CT_SHELL_MODE;
+		}
+		else if (strcmp(argv[1], "-ctmaster") == 0) {
+		    strcpy(nodename, "ct_master");
+		    ct_mode = MASTER_MODE;
+		}
+		else if (strcmp(argv[1], "-ctname") == 0) {
+		    strcpy(nodename, argv[2]);
+		    ct_mode = ERL_SHELL_MODE;
+		    cnt++, argv++;
+		}
+	    }
 	}
-	cnt--, argv++;
+	cnt++, argv++;
     }
 
     argv = argv0;
@@ -219,19 +228,28 @@ main(int argc, char** argv)
 	PUSH3("-s", "erlang", "halt");
     }
 
-    while (argc > 1) {
-	if (strcmp(argv[1], "-config") == 0)
-	    PUSH("-ct_config");
-	else if (strcmp(argv[1], "-decrypt_key") == 0)
-	    PUSH("-ct_decrypt_key");
-	else if (strcmp(argv[1], "-decrypt_file") == 0)
-	    PUSH("-ct_decrypt_file");
-	else if ((strcmp(argv[1], "-sname") == 0) || (strcmp(argv[1], "-name") == 0))
-	    argc--, argv++;
-	else
+    cnt = 1;
+    while (cnt < argc) {
+	if (strcmp(argv[1], "-erl_args") == 0) {
+	    PUSH("-ct_erl_args");
+	}
+	else if ((strcmp(argv[1], "-sname") == 0) || (strcmp(argv[1], "-name") == 0)) {
+	    cnt++, argv++;
+	}
+	else if (cnt < erl_args) {
+	    if (strcmp(argv[1], "-config") == 0)
+		PUSH("-ct_config");
+	    else if (strcmp(argv[1], "-decrypt_key") == 0)
+		PUSH("-ct_decrypt_key");
+	    else if (strcmp(argv[1], "-decrypt_file") == 0)
+		PUSH("-ct_decrypt_file");
+	    else
+		PUSH(argv[1]);
+	}
+	else {
 	    PUSH(argv[1]);
-
-	argc--, argv++;
+	}
+	cnt++, argv++;
     }
 
     /*
-- 
cgit v1.2.3


From c3a1e56608ebe08f1ddc07273d85ff9c2779de9b Mon Sep 17 00:00:00 2001
From: Peter Andersson 
Date: Thu, 3 Jun 2010 14:14:38 +0200
Subject: Implement support for user controllable timetrap parameters (multiply
 and scale)

Documentation still missing.
---
 lib/common_test/src/ct.erl                         |   39 +-
 lib/common_test/src/ct_run.erl                     | 1226 +++++++++++---------
 .../error/test/cfg_error_3_SUITE.erl               |    3 +-
 lib/test_server/doc/src/test_server.xml            |   16 +
 lib/test_server/doc/src/test_server_ctrl.xml       |   25 +
 lib/test_server/src/test_server.erl                |  314 ++---
 lib/test_server/src/test_server_ctrl.erl           |  999 ++++++++--------
 lib/test_server/src/test_server_sup.erl            |   13 +-
 8 files changed, 1443 insertions(+), 1192 deletions(-)

diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 2d71a3812d..57035719e2 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -64,7 +64,8 @@
 	 print/1, print/2, print/3,
 	 pal/1, pal/2, pal/3,
 	 fail/1, comment/1,
-	 testcases/2, userdata/2, userdata/3]).
+	 testcases/2, userdata/2, userdata/3,
+	 sleep/1]).
 
 %% New API for manipulating with config handlers
 -export([add_config/2, remove_config/2]).
@@ -143,8 +144,9 @@ run(TestDirs) ->
 %%%               {allow_user_terms,Bool} | {logdir,LogDir} | 
 %%%               {silent_connections,Conns} | {cover,CoverSpecFile} | 
 %%%               {step,StepOpts} | {event_handler,EventHandlers} | {include,InclDirs} | 
-%%%               {auto_compile,Bool} | {repeat,N} | {duration,DurTime} | 
-%%%               {until,StopTime} | {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
+%%%               {auto_compile,Bool} | {multiply_timetraps,M} | {scale_timetraps,Bool} |
+%%%               {repeat,N} | {duration,DurTime} | {until,StopTime} |
+%%%               {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
 %%%               {refresh_logs,LogDir} | {basic_html,Bool}
 %%%   CfgFiles = [string()] | string()
 %%%   TestDirs = [string()] | string()
@@ -161,6 +163,7 @@ run(TestDirs) ->
 %%%   EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
 %%%   InitArgs = [term()]
 %%%   InclDirs = [string()] | string()
+%%%   M = integer()
 %%%   N = integer()
 %%%   DurTime = string(HHMMSS)
 %%%   StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS)
@@ -812,8 +815,7 @@ decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
 
 
 %%%-----------------------------------------------------------------
-%%% @spec add_config(Callback, Config) ->
-%%%                                       ok | {error, Reason}
+%%% @spec add_config(Callback, Config) -> ok | {error, Reason}
 %%%       Callback = atom()
 %%%       Config = string()
 %%%       Reason = term()
@@ -827,8 +829,7 @@ add_config(Callback, Config)->
     ct_config:add_config(Callback, Config).
 
 %%%-----------------------------------------------------------------
-%%% @spec remove_config(Callback, Config) ->
-%%%                                          ok
+%%% @spec remove_config(Callback, Config) -> ok
 %%%       Callback = atom()
 %%%       Config = string()
 %%%       Reason = term()
@@ -836,5 +837,27 @@ add_config(Callback, Config)->
 %%% @doc 

This function removes configuration variables (together with %%% their aliases) which were loaded with specified callback module and %%% configuration string.

-remove_config(Callback, Config)-> +remove_config(Callback, Config) -> ct_config:remove_config(Callback, Config). + +%%%----------------------------------------------------------------- +%%% @spec sleep(Time) -> ok +%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity +%%% Hours = integer() +%%% Mins = integer() +%%% Secs = integer() +%%% Millisecs = integer() | float() +%%% +%%% @doc

This function, similar to timer:sleep/1, suspends the test +%%% case for specified time. However, this function also multiplies +%%% Time with the 'multiply_timetraps' value (if set) and under +%%% certain circumstances also scales up the time automatically +%%% if 'scale_timetraps' is set to true (default is false).

+sleep({hours,Hs}) -> + sleep(trunc(Hs * 1000 * 60 * 60)); +sleep({minutes,Ms}) -> + sleep(trunc(Ms * 1000 * 60)); +sleep({seconds,Ss}) -> + sleep(trunc(Ss * 1000)); +sleep(Time) -> + test_server:adjusted_sleep(Time). diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 021bda2951..96703031d2 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -24,7 +24,6 @@ -module(ct_run). - %% Script interface -export([script_start/0,script_usage/0]). @@ -46,11 +45,26 @@ -define(abs(Name), filename:absname(Name)). -define(testdir(Name, Suite), ct_util:get_testdir(Name, Suite)). +-record(opts, {vts, + shell, + cover, + coverspec, + step, + logdir, + config = [], + event_handlers = [], + include = [], + silent_connections, + multiply_timetraps = 1, + scale_timetraps = false, + testspecs = [], + tests}). + %%%----------------------------------------------------------------- %%% @spec script_start() -> void() %%% %%% @doc Start tests via the run_test program or script. -%%% +%%% %%%

Example:
./run_test -config config.ctc -dir %%% $TEST_DIR

%%% @@ -60,12 +74,18 @@ script_start() -> process_flag(trap_exit, true), Args = merge_arguments(init:get_arguments()), + case proplists:get_value(help, Args) of + undefined -> script_start(Args); + _ -> script_usage() + end. + +script_start(Args) -> Tracing = start_trace(Args), - Res = + Res = case ct_repeat:loop_test(script, Args) of - false -> + false -> {ok,Cwd} = file:get_cwd(), - CTVsn = + CTVsn = case filename:basename(code:lib_dir(common_test)) of CTBase when is_list(CTBase) -> case string:tokens(CTBase, "-") of @@ -76,7 +96,7 @@ script_start() -> io:format("~nCommon Test~s starting (cwd is ~s)~n~n", [CTVsn,Cwd]), Self = self(), Pid = spawn_link(fun() -> script_start1(Self, Args) end), - receive + receive {'EXIT',Pid,Reason} -> case Reason of {user_error,What} -> @@ -101,287 +121,285 @@ script_start() -> Res. script_start1(Parent, Args) -> - VtsOrShell = - case lists:keymember(vts, 1, Args) of - true -> - vts; - false -> - case lists:keymember(shell, 1, Args) of - true -> shell; - false -> false - end - end, - LogDir = - case lists:keysearch(logdir, 1, Args) of - {value,{logdir,[LogD]}} -> LogD; - false -> "." - end, - EvHandlers = - case lists:keysearch(event_handler, 1, Args) of - {value,{event_handler,Handlers}} -> - lists:map(fun(H) -> {list_to_atom(H),[]} end, Handlers); - false -> - [] - end, - Cover = - case lists:keysearch(cover, 1, Args) of - {value,{cover,CoverFile}} -> - {cover,?abs(CoverFile)}; - false -> - false - end, - - case lists:keysearch(ct_decrypt_key, 1, Args) of - {value,{_,[DecryptKey]}} -> + %% read general start flags + Vts = get_start_opt(vts, true, Args), + Shell = get_start_opt(shell, true, Args), + Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, Args), + LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), + MultTT = get_start_opt(multiply_timetraps, fun(MT) -> MT end, Args), + ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, Args), + EvHandlers = get_start_opt(event_handler, + fun(Handlers) -> + lists:map(fun(H) -> + {list_to_atom(H),[]} + end, Handlers) end, + [], Args), + + %% check flags and set corresponding application env variables + + %% ct_decrypt_key | ct_decrypt_file + case proplists:get_value(ct_decrypt_key, Args) of + [DecryptKey] -> application:set_env(common_test, decrypt, {key,DecryptKey}); - false -> - case lists:keysearch(ct_decrypt_file, 1, Args) of - {value,{_,[DecryptFile]}} -> - application:set_env(common_test, decrypt, + undefined -> + case proplists:get_value(ct_decrypt_file, Args) of + [DecryptFile] -> + application:set_env(common_test, decrypt, {file,filename:absname(DecryptFile)}); - false -> + undefined -> application:unset_env(common_test, decrypt) end end, - - case lists:keysearch(no_auto_compile, 1, Args) of - {value,_} -> - application:set_env(common_test, auto_compile, false); - false -> - application:set_env(common_test, auto_compile, true), - - InclDirs = - case lists:keysearch(include,1,Args) of - {value,{include,Incl}} when is_list(hd(Incl)) -> - Incl; - {value,{include,Incl}} when is_list(Incl) -> - [Incl]; + %% no_auto_compile + include + IncludeDirs = + case proplists:get_value(no_auto_compile, Args) of + undefined -> + application:set_env(common_test, auto_compile, true), + InclDirs = + case proplists:get_value(include, Args) of + {include,Incl} when is_list(hd(Incl)) -> + Incl; + {include,Incl} when is_list(Incl) -> + [Incl]; + undefined -> + [] + end, + case os:getenv("CT_INCLUDE_PATH") of false -> - [] - end, - case os:getenv("CT_INCLUDE_PATH") of - false -> - application:set_env(common_test, include, InclDirs); - CtInclPath -> - InclDirs1 = string:tokens(CtInclPath,[$:,$ ,$,]), - application:set_env(common_test, include, InclDirs1++InclDirs) - end + application:set_env(common_test, include, InclDirs), + InclDirs; + CtInclPath -> + AllInclDirs = + string:tokens(CtInclPath,[$:,$ ,$,]) ++ InclDirs, + application:set_env(common_test, include, AllInclDirs), + AllInclDirs + end; + _ -> + application:set_env(common_test, auto_compile, false), + [] + end, + %% basic_html - used by ct_logs + case proplists:get_value(basic_html, Args) of + undefined -> + application:set_env(common_test, basic_html, false); + _ -> + application:set_env(common_test, basic_html, true) end, - case lists:keysearch(basic_html, 1, Args) of - {value,_} -> - application:set_env(common_test, basic_html, true); - false -> - application:set_env(common_test, basic_html, false) - end, + StartOpts = #opts({vts = Vts, shell = Shell, cover = Cover, + logdir = LogDir, event_handlers = EvHandlers, + include = IncludeDirs, + multiply_timetraps = MultTT, + scale_timetraps = ScaleTT}), - Result = - case lists:keysearch(refresh_logs, 1, Args) of - {value,{refresh_logs,Refresh}} -> - LogDir1 = case Refresh of - [] -> LogDir; - [RefreshDir] -> ?abs(RefreshDir) - end, - {ok,Cwd} = file:get_cwd(), - file:set_cwd(LogDir1), - timer:sleep(500), % give the shell time to print version etc - io:nl(), - case catch ct_logs:make_all_suites_index(refresh) of - {'EXIT',ASReason} -> - file:set_cwd(Cwd), - {error,{all_suites_index,ASReason}}; - _ -> - case catch ct_logs:make_all_runs_index(refresh) of - {'EXIT',ARReason} -> - file:set_cwd(Cwd), - {error,{all_runs_index,ARReason}}; - _ -> - file:set_cwd(Cwd), - io:format("Logs in ~s refreshed!~n~n", [LogDir1]), - timer:sleep(500), % time to flush io before quitting - ok - end - end; - false -> - Config = ct_config:prepare_config_list(Args), - case Config of - [] -> - case install([{config,[]}, - {event_handler,EvHandlers}], - LogDir) of - ok -> - script_start2(VtsOrShell, [], EvHandlers, - Args, LogDir, Cover); - Error -> - Error - end; - Config -> - case lists:keysearch(spec, 1, Args) of - false -> - case check_and_install_configfiles(Config, - LogDir, EvHandlers) of - ok -> - script_start2(VtsOrShell, Config, - EvHandlers, Args, LogDir, - Cover); - Error -> - Error - end; - _ -> - script_start2(VtsOrShell, Config, - EvHandlers, Args, LogDir, Cover) - end - end - end, + %% check if log files should be refreshed or go on to run tests... + Result = run_or_refresh(StartOpts, Args), + %% send final results to starting process waiting in script_start/0 Parent ! {self(), Result}. -check_and_install_configfiles(Configs, LogDir, EvHandlers) -> - case ct_config:check_config_files(Configs) of - false-> - install([{config,Configs}, - {event_handler,EvHandlers}], LogDir); - {value, {error, {nofile, File}}} -> - {error,{cant_read_config_file,File}}; - {value, {error, {wrong_config, Message}}}-> - {error,{wrong_config, Message}}; - {value, {error, {callback, File}}} -> - {error,{cant_load_callback_module,File}} +run_or_refresh(StartOpts = #opts{logdir = LogDir}, Args) -> + case proplists:get_value(refresh_logs, Args) of + undefined -> + script_start2(StartOpts, Args); + Refresh -> + LogDir1 = case Refresh of + [] -> which_logdir(LogDir); + [RefreshDir] -> ?abs(RefreshDir) + end, + {ok,Cwd} = file:get_cwd(), + file:set_cwd(LogDir1), + %% give the shell time to print version etc + timer:sleep(500), + io:nl(), + case catch ct_logs:make_all_suites_index(refresh) of + {'EXIT',ASReason} -> + file:set_cwd(Cwd), + {error,{all_suites_index,ASReason}}; + _ -> + case catch ct_logs:make_all_runs_index(refresh) of + {'EXIT',ARReason} -> + file:set_cwd(Cwd), + {error,{all_runs_index,ARReason}}; + _ -> + file:set_cwd(Cwd), + io:format("Logs in ~s refreshed!~n~n", [LogDir1]), + timer:sleep(500), % time to flush io before quitting + ok + end + end end. -script_start2(false, ConfigFiles, EvHandlers, Args, LogDir, Cover) -> - case lists:keysearch(spec, 1, Args) of - {value,{spec,[]}} -> +script_start2(StartOpts = #opts{vts = undefined, + shell = undefined}, Args) -> + TestSpec = proplists:get_value(spec, Args), + Opts = + case TestSpec of + Specs when Specs =/= [], Specs =/= undefined -> + %% using testspec as input for test + Relaxed = get_start_opt(allow_user_terms, true, false, Args), + case catch ct_testspec:collect_tests_from_file(Specs, Relaxed) of + {error,Reason} -> + {error,Reason}; + TS -> + SpecStartOpts = get_data_for_node(TS, node()), + + LogDir = choose_val(StartOpts#opts.logdir, + SpecStartOpts#opts.logdir), + + 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]), + AllInclude = merge_vals([StartOpts#opts.include, + SpecStartOpts#opts.include]), + application:set_env(common_test, include, AllInclude), + + StartOpts#opts{testspecs = Specs, + cover = Cover, + logdir = LogDir, + config = SpecStartOpts#opts.config, + event_handlers = AllEvHs, + include = AllInclude, + multiply_timetraps = MultTT, + scale_timetraps = ScaleTT} + end; + _ -> + StartOpts + end, + %% read config/userconfig from start flags + InitConfig = ct_config:prepare_config_list(Args), + case TestSpec of + [] -> {error,no_testspec_specified}; - {value,{spec,Specs}} -> - Relaxed = lists:keymember(allow_user_terms, 1, Args), - %% using testspec as input for test - case catch ct_testspec:collect_tests_from_file(Specs, Relaxed) of - {error,Reason} -> - {error,Reason}; - TS -> - {LogDir1,TSCoverFile,ConfigFiles1,EvHandlers1,Include1} = - get_data_for_node(TS,node()), - UserInclude = - case application:get_env(common_test, include) of - {ok,Include} -> Include++Include1; - _ -> Include1 - end, - application:set_env(common_test, include, UserInclude), - LogDir2 = which_logdir(LogDir,LogDir1), - CoverOpt = case {Cover,TSCoverFile} of - {false,undef} -> []; - {_,undef} -> [Cover]; - {false,_} -> [{cover,TSCoverFile}] - end, - case check_and_install_configfiles( - ConfigFiles++ConfigFiles1, LogDir2, - EvHandlers++EvHandlers1) of - ok -> - {Run,Skip} = ct_testspec:prepare_tests(TS, node()), - do_run(Run, Skip, CoverOpt, Args, LogDir2); - Error -> - Error - end + undefined -> % no testspec used + case check_and_install_configfiles(InitConfig, + which(logdir,Opts#opts.logdir), + Opts#opts.event_handlers) of + ok -> % go on read tests from start flags + script_start3(Opts#opts{config=InitConfig}, Args); + Error -> + Error end; - false -> - script_start3(false, ConfigFiles, EvHandlers, Args, LogDir, Cover) + _ -> % testspec used + %% merge config from start flags with config from testspec + AllConfig = merge_vals([InitConfig, Opts#opts.config]), + case check_and_install_configfiles(AllConfig, + which(logdir,Opts#opts.logdir), + Opts#opts.event_handlers) of + ok -> % read tests from spec + {Run,Skip} = ct_testspec:prepare_tests(TS, node()), + do_run(Run, Skip, Opts#opts{config=AllConfig}, Args); + Error -> + Error + end end; -script_start2(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover) -> - script_start3(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover). -script_start3(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover) -> - case lists:keysearch(dir, 1, Args) of - {value,{dir,[]}} -> - {error,no_dir_specified}; - {value,{dir,Dirs}} -> - script_start4(VtsOrShell, ConfigFiles, EvHandlers, tests(Dirs), - Cover, Args, LogDir); +script_start2(StartOpts, Args) -> + script_start3(StartOpts, Args). + +check_and_install_configfiles(Configs, LogDir, EvHandlers) -> + case ct_config:check_config_files(Configs) of false -> - case lists:keysearch(suite, 1, Args) of - {value,{suite,[]}} -> + install([{config,Configs}, + {event_handler,EvHandlers}], LogDir); + {value,{error,{nofile,File}}} -> + {error,{cant_read_config_file,File}}; + {value,{error,{wrong_config,Message}}}-> + {error,{wrong_config,Message}}; + {value,{error,{callback,File}}} -> + {error,{cant_load_callback_module,File}} + end. + +script_start3(StartOpts = #opts{cover = Cover}, Args) -> + case proplists:get_value(dir, Args) of + [] -> + {error,no_dir_specified}; + Dirs when is_list(Dirs) -> + script_start4(StartOpts#opts{tests = tests(Dirs)}, Args); + undefined -> + case proplists:get_value(suite, Args) of + [] -> {error,no_suite_specified}; - {value,{suite,Suites}} -> - StepOrCover = - case lists:keysearch(step, 1, Args) of - {value,Step} -> Step; - false -> Cover - end, - S2M = fun(S) -> - {filename:dirname(S), - list_to_atom( - filename:rootname(filename:basename(S)))} - end, - DirMods = lists:map(S2M, Suites), - {Specified,GroupsAndCases} = - case {lists:keysearch(group, 1, Args), - lists:keysearch('case', 1, Args)} of - {{value,{_,Gs}},{value,{_,Cs}}} -> {true,Gs++Cs}; - {{value,{_,Gs}},_} -> {true,Gs}; - {_,{value,{_,Cs}}} -> {true,Cs}; - _ -> {false,[]} - end, - if Specified, length(GroupsAndCases) == 0 -> - {error,no_case_or_group_specified}; - Specified, length(DirMods) > 1 -> + Suites when is_list(Suites) -> + StartOpts1 = + get_start_opt(step, + fun(Step) -> + StartOpts#opts{step = Step, + cover = undefined} + end, StartOpts, Args), + DirMods = [suite_to_test(S) || S <- Suites], + case groups_and_cases(proplist:get_value(group, Args), + proplist:get_value(testcase, Args)) of + Error = {error,_} -> + Error; + [] when DirMods =/= [] -> + Ts = tests(DirMods), + script_start4(StartOpts1#opts{tests = Ts}, Args); + GroupsAndCases when length(DirMods) == 1 -> + Ts = tests(DirMods, GroupsAndCases), + script_start4(StartOpts1#opts{tests = Ts}, Args); + [_,_|_] when length(DirMods) > 1 -> {error,multiple_suites_and_cases}; - length(GroupsAndCases) > 0, length(DirMods) == 1 -> - GsAndCs = lists:map(fun(C) -> list_to_atom(C) end, - GroupsAndCases), - script_start4(VtsOrShell, ConfigFiles, EvHandlers, - tests(DirMods, GsAndCs), - StepOrCover, Args, LogDir); - not Specified, length(DirMods) > 0 -> - script_start4(VtsOrShell, ConfigFiles, EvHandlers, - tests(DirMods), - StepOrCover, Args, LogDir); - true -> - {error,incorrect_suite_and_case_options} + _ -> + {error,incorrect_suite_option} end; - false when VtsOrShell=/=false -> - script_start4(VtsOrShell, ConfigFiles, EvHandlers, - [], Cover, Args, LogDir); - false -> - script_usage(), - {error,incorrect_usage} + undefined -> + if StartOpts#opts.vts ; StartOpts#opts.shell -> + script_start4(StartOpts#opts{tests = []}, Args); + true -> + script_usage(), + {error,incorrect_usage} + end end end. -script_start4(vts, ConfigFiles, EvHandlers, Tests, false, _Args, LogDir) -> - vts:init_data(ConfigFiles, EvHandlers, ?abs(LogDir), Tests); -script_start4(shell, ConfigFiles, EvHandlers, _Tests, false, Args, LogDir) -> - Opts = [{config,ConfigFiles},{event_handler,EvHandlers}], +script_start4(#opts{vts = true, config = Config, event_handler = EvHandlers, + tests = Tests, logdir = LogDir}, Args) -> + vts:init_data(Config, EvHandlers, ?abs(LogDir), Tests); + +script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers, + logdir = LogDir, testspecs = Specs}, Args) -> + InstallOpts = [{config,Config},{event_handler,EvHandlers}], if ConfigFiles == [] -> ok; true -> - io:format("\nInstalling: ~p\n\n", [ConfigFiles]) + io:format("\nInstalling: ~p\n\n", [Config]) end, - case install(Opts) of + case install(InstallOpts) of ok -> ct_util:start(interactive, LogDir), - log_ts_names(Args), + log_ts_names(Specs), io:nl(), ok; Error -> Error end; -script_start4(vts, _CfgFs, _EvHs, _Tests, _Cover={cover,_}, _Args, _LogDir) -> - %% Add support later (maybe). - script_usage(), - erlang:halt(); -script_start4(shell, _CfgFs, _EvHs, _Tests, _Cover={cover,_}, _Args, _LogDir) -> - %% Add support later (maybe). - script_usage(); -script_start4(false, _CfgFs, _EvHs, Tests, Cover={cover,_}, Args, LogDir) -> - do_run(Tests, [], [Cover], Args, LogDir); -script_start4(false, _ConfigFiles, _EvHandlers, Tests, false, Args, LogDir) -> - do_run(Tests, [], [], Args, LogDir); -script_start4(false, _ConfigFiles, _EvHandlers, Test, Step, Args, LogDir) -> - do_run(Test, [], [Step], Args, LogDir); -script_start4(vts, _ConfigFiles, _EvHandlers, _Test, _Step, _Args, _LogDir) -> - script_usage(), + +script_start4(#opts{vts = true, cover = Cover}, _) -> + case Cover of + undefined -> + script_usage(); + _ -> + %% Add support later (maybe). + io:format("\nCan't run cover in vts mode.\n\n", []) + end, erlang:halt(); -script_start4(shell, _ConfigFiles, _EvHandlers, _Test, _Step, _Args, _LogDir) -> - script_usage(). + +script_start4(#opts{shell = true, cover = Cover}, _) -> + case Cover of + undefined -> + script_usage(); + _ -> + %% Add support later (maybe). + io:format("\nCan't run cover in interactive mode.\n\n", []) + end; + +script_start4(Opts = #opts{tests = Tests}, Args) -> + do_run(Tests, [], Opts, Args). %%%----------------------------------------------------------------- %%% @spec script_usage() -> ok @@ -394,8 +412,10 @@ script_usage() -> "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" "\n\t[-suite Suite [-case Case]]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" + "\n\t[-include InclDir1 InclDir2 .. InclDirN]" "\n\t[-no_auto_compile]" + "\n\t[-multiply_timetraps]" + "\n\t[-scale_timetraps]" "\n\t[-basic_html]\n\n"), io:format("Run tests from command line:\n\n" "\trun_test [-dir TestDir1 TestDir2 .. TestDirN] |" @@ -409,10 +429,12 @@ script_usage() -> "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" + "\n\t[-include InclDir1 InclDir2 .. InclDirN]" "\n\t[-no_auto_compile]" - "\n\t[-basic_html]" - "\n\t[-repeat N [-force_stop]] |" + "\n\t[-multiply_timetraps]" + "\n\t[-scale_timetraps]" + "\n\t[-basic_html]" + "\n\t[-repeat N [-force_stop]] |" "\n\t[-duration HHMMSS [-force_stop]] |" "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"), io:format("Run tests using test specification:\n\n" @@ -425,10 +447,12 @@ script_usage() -> "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-include InclDir1 InclDir2 .. InclDirN]" + "\n\t[-include InclDir1 InclDir2 .. InclDirN]" "\n\t[-no_auto_compile]" - "\n\t[-basic_html]" - "\n\t[-repeat N [-force_stop]] |" + "\n\t[-multiply_timetraps]" + "\n\t[-scale_timetraps]" + "\n\t[-basic_html]" + "\n\t[-repeat N [-force_stop]] |" "\n\t[-duration HHMMSS [-force_stop]] |" "\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"), io:format("Refresh the HTML index files:\n\n" @@ -439,7 +463,6 @@ script_usage() -> "\trun_test -shell" "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"). - %%%----------------------------------------------------------------- %%% @hidden @@ -462,13 +485,13 @@ install(Opts, LogDir) -> case whereis(ct_util_server) of undefined -> VarFile = variables_file_name(LogDir), - %% io:format("Varfile=~p~n", [VarFile]), + io:format("Varfile=~p~n", [VarFile]), case file:open(VarFile, [write]) of {ok,Fd} -> [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts], file:close(Fd), ok; - {error,Reason} -> + {error,Reason} -> io:format("CT failed to install configuration data. Please " "verify that the log directory exists and that " "write permission is set.\n\n", []), @@ -487,59 +510,58 @@ variables_file_name(Dir) -> filename:join(Dir, "variables-"++atom_to_list(node())). %%%----------------------------------------------------------------- -%%% @hidden +%%% @spec run_test(Opts) -> Result +%%% Opts = [tuple()] +%%% Result = [TestResult] | {error,Reason} +%%% +%%% @doc Start tests from the erlang shell or from an erlang program. %%% @equiv ct:run_test/1 +%%%----------------------------------------------------------------- -%% Opts = [OptTuples] -%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} | -%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} | -%% {logdir,LogDir} | {cover,CoverSpecFile} | {step,StepOpts} | -%% {silent_connections,Conns} | {event_handler,EventHandlers} | -%% {include,InclDirs} | {auto_compile,Bool} | -%% {repeat,N} | {duration,DurTime} | {until,StopTime} | {force_stop,Bool} | -%% {decrypt,KeyOrFile} - -run_test(Opt) when is_tuple(Opt) -> - run_test([Opt]); - -run_test(Opts) when is_list(Opts) -> - case lists:keysearch(refresh_logs, 1, Opts) of - {value,{_,RefreshDir}} -> - refresh_logs(?abs(RefreshDir)), - ok; - false -> - Tracing = start_trace(Opts), +run_test(StartOpt) when is_tuple(StartOpt) -> + run_test([StartOpt]); + +run_test(StartOpts) when is_list(StartOpts) -> + case proplist:get_value(refresh_logs, StartOpts) of + undefined -> + Tracing = start_trace(StartOpts), {ok,Cwd} = file:get_cwd(), - io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]), + io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]), Res = - case ct_repeat:loop_test(func, Opts) of + case ct_repeat:loop_test(func, StartOpts) of false -> - case catch run_test1(Opts) of - {'EXIT',Reason} -> + case catch run_test1(StartOpts) of + {'EXIT',Reason} -> file:set_cwd(Cwd), {error,Reason}; - Result -> + Result -> Result end; Result -> Result end, stop_trace(Tracing), - Res + Res; + RefreshDir -> + refresh_logs(?abs(RefreshDir)), + ok end. -run_test1(Opts) -> - LogDir = - case lists:keysearch(logdir, 1, Opts) of - {value,{_,LD}} when is_list(LD) -> LD; - false -> "." - end, - CfgFiles = ct_config:get_config_file_list(Opts), +run_test1(StartOpts) -> + %% logdir + LogDir = get_start_opt(logdir, fun(LD) when is_list(LD) -> LD end, + ".", StartOpts), + %% config & userconfig + CfgFiles = ct_config:get_config_file_list(StartOpts), + + %% event handlers EvHandlers = - case lists:keysearch(event_handler, 1, Opts) of - {value,{_,H}} when is_atom(H) -> + case proplists:get_value(event_handler, StartOpts) of + undefined -> + []; + H when is_atom(H) -> [{H,[]}]; - {value,{_,H}} -> + H -> Hs = if is_tuple(H) -> [H]; is_list(H) -> H; @@ -554,41 +576,34 @@ run_test1(Opts) -> {EH,Args}; (_) -> [] - end, Hs)); - _ -> - [] - end, - SilentConns = - case lists:keysearch(silent_connections, 1, Opts) of - {value,{_,all}} -> - []; - {value,{_,Conns}} -> - Conns; - _ -> - undefined - end, - Cover = - case lists:keysearch(cover, 1, Opts) of - {value,{_,CoverFile}} -> - [{cover,?abs(CoverFile)}]; - _ -> - [] + end, Hs)) end, + + %% silent connections + SilentConns = get_start_opt(silent_connections, + fun(all) -> []; + (Conns) -> Conns + end, StartOpts), + %% code coverage + Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, StartOpts), + + %% timetrap manipulation + MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts), + ScaleTT = get_start_opt(scale_timetraps, value, false, StartOpts), + + %% auto compile & include files Include = - case lists:keysearch(auto_compile, 1, Opts) of - {value,{auto_compile,ACBool}} -> - application:set_env(common_test, auto_compile, ACBool), - []; - _ -> + case proplists:get_value(auto_compile, StartOpts) of + undefined -> application:set_env(common_test, auto_compile, true), InclDirs = - case lists:keysearch(include, 1, Opts) of - {value,{include,Incl}} when is_list(hd(Incl)) -> - Incl; - {value,{include,Incl}} when is_list(Incl) -> - [Incl]; - false -> - [] + case proplists:get_value(include, StartOpts) of + undefined -> + []; + Incl when is_list(hd(Incl)) -> + Incl; + Incl when is_list(Incl) -> + [Incl] end, case os:getenv("CT_INCLUDE_PATH") of false -> @@ -599,10 +614,14 @@ run_test1(Opts) -> AllInclDirs = InclDirs1++InclDirs, application:set_env(common_test, include, AllInclDirs), AllInclDirs - end + end; + ACBool -> + application:set_env(common_test, auto_compile, ACBool), + [] end, - case lists:keysearch(decrypt, 1, Opts) of + %% decrypt config file + case lists:keysearch(decrypt, 1, StartOpts) of {value,{_,Key={key,_}}} -> application:set_env(common_test, decrypt, Key); {value,{_,{file,KeyFile}}} -> @@ -611,39 +630,35 @@ run_test1(Opts) -> application:unset_env(common_test, decrypt) end, - case lists:keysearch(basic_html, 1, Opts) of - {value,{basic_html,BasicHtmlBool}} -> - application:set_env(common_test, basic_html, BasicHtmlBool); - _ -> - application:set_env(common_test, basic_html, false) + %% basic html - used by ct_logs + case proplists:get_value(basic_html, StartOpts) of + undefined -> + application:set_env(common_test, basic_html, false); + BasicHtmlBool -> + application:set_env(common_test, basic_html, BasicHtmlBool) end, - case lists:keysearch(spec, 1, Opts) of - {value,{_,Specs}} -> - Relaxed = - case lists:keysearch(allow_user_terms, 1, Opts) of - {value,{_,true}} -> true; - _ -> false - end, + %% stepped execution + Step = get_start_opt(step, value, StepOpts), + + Opts = #opts{cover = Cover, step = Step, logdir = LogDir, config = CfgFiles, + event_handlers = EvHandlers, include = Include, + silent_connections = SilentConns, multiply_timetraps = MultiplyTT, + scale_timetraps = ScaleTT}, + + %% test specification + case proplists:get_value(spec, StartOpts) of + undefined -> + case proplists:get_value(prepared_tests, StartOpts) of + undefined -> % use dir|suite|case + run_dir(Opts, StartOpts); + {{Run,Skip},Specs} -> % use prepared tests + run_prepared(Run, Skip, Opts#opts{testspecs = Specs}, StartOpts) + end; + Specs -> + Relaxed = get_start_opt(allow_user_term, value, false), %% using testspec(s) as input for test - run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover, - replace_opt([{silent_connections,SilentConns}], Opts)); - false -> - case lists:keysearch(prepared_tests, 1, Opts) of - {value,{_,{Run,Skip},Specs}} -> % use prepared tests - run_prepared(LogDir, CfgFiles, EvHandlers, - Run, Skip, Cover, - replace_opt([{silent_connections,SilentConns}, - {spec,Specs}],Opts)); - false -> % use dir|suite|case - StepOrCover = - case lists:keysearch(step, 1, Opts) of - {value,Step} -> [Step]; - false -> Cover - end, - run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, - replace_opt([{silent_connections,SilentConns}], Opts)) - end + run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts) end. replace_opt([O={Key,_Val}|Os], Opts) -> @@ -651,83 +666,105 @@ replace_opt([O={Key,_Val}|Os], Opts) -> replace_opt([], Opts) -> Opts. -run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover, Opts) -> +run_spec_file(Relaxed, + Opts = #opts{testspecs = Specs, config = CfgFiles}, + StartOpts) -> Specs1 = case Specs of [X|_] when is_integer(X) -> [Specs]; _ -> Specs end, - AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1), + AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1), log_ts_names(AbsSpecs), case catch ct_testspec:collect_tests_from_file(AbsSpecs, Relaxed) of {error,CTReason} -> exit(CTReason); TS -> - {LogDir1,TSCoverFile,CfgFiles1,EvHandlers1,Include1} = - get_data_for_node(TS, node()), - application:set_env(common_test, include, Include++Include1), - LogDir2 = which_logdir(LogDir, LogDir1), - CoverOpt = case {Cover,TSCoverFile} of - {[],undef} -> []; - {_,undef} -> Cover; - {[],_} -> [{cover,TSCoverFile}] - end, - case check_and_install_configfiles(CfgFiles++CfgFiles1, LogDir2, - EvHandlers++EvHandlers1) of + SpecOpts = get_data_for_node(TS, node()), + LogDir = choose_val(Opts#opts.logdir, + SpecOpts#opts.logdir), + AllConfig = merge_vals([CfgFiles, SpecOpts#opts.config]), + Cover = choose_val(Opts#opts.cover, + SpecOpts#opts.cover), + MultTT = choose_val(Opts#opts.multiply_timetraps, + SpecOpts#opts.multiply_timetraps), + ScaleTT = choose_val(Opts#opts.scale_timetraps, + SpecOpts#opts.scale_timetraps), + AllEvHs = merge_vals([Opts#opts.event_handlers, + SpecOpts#opts.event_handlers]), + AllInclude = merge_vals([Opts#opts.include, + SpecOpts#opts.include]), + application:set_env(common_test, include, AllInclude), + + case check_and_install_configfiles(AllConfig, + which(logdir,LogDir), + AllEvHs) of ok -> + Opts1 = Opts#opts{testspecs = Specs, + cover = Cover, + logdir = LogDir, + config = AllConfig, + event_handlers = AllEvHs, + include = AllInclude, + testspecs = AbsSpecs, + multiply_timetraps = MultTT, + scale_timetraps = ScaleTT} + {Run,Skip} = ct_testspec:prepare_tests(TS, node()), - do_run(Run, Skip, CoverOpt, - replace_opt([{spec,AbsSpecs}], Opts), - LogDir2); + do_run(Run, Skip, Opts1, StartOpts); {error,GCFReason} -> exit(GCFReason) end end. -run_prepared(LogDir, CfgFiles, EvHandlers, Run, Skip, Cover, Opts) -> +run_prepared(Run, Skip, Opts = #opts{logdir = LogDir, + config = CfgFiles, + event_handlers = EvHandlers}, + StartOpts) -> case check_and_install_configfiles(CfgFiles, LogDir, EvHandlers) of ok -> - do_run(Run, Skip, Cover, Opts, LogDir); + do_run(Run, Skip, Opts, StartOpts); {error,Reason} -> exit(Reason) - end. + end. check_config_file(Callback, File)-> case Callback:check_parameter(File) of - {ok, {file, File}}-> + {ok,{file,File}}-> ?abs(File); - {ok, {config, _}}-> + {ok,{config,_}}-> File; - {error, {wrong_config, Message}}-> - exit({wrong_config, {Callback, Message}}); - {error, {nofile, File}}-> - exit({no_such_file, ?abs(File)}) + {nok,{wrong_config,Message}}-> + exit({wrong_config,{Callback,Message}}); + {nok,{nofile,File}}-> + exit({no_such_file,?abs(File)}) end. -run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> - AbsCfgFiles = - lists:map(fun({Callback, FileList})-> - case code:is_loaded(Callback) of - {file, _Path}-> - ok; - false-> - case code:load_file(Callback) of - {module, Callback}-> - ok; - {error, _}-> - exit({no_such_module, Callback}) - end - end, - {Callback, - lists:map(fun(File)-> - check_config_file(Callback, File) - end, FileList)} - end, - CfgFiles), +run_dir(Opts = #opts{logdir = LogDir, + config = CfgFiles, + event_handlers = EvHandlers}, StartOpts) -> + AbsCfgFiles = + lists:map(fun({Callback,FileList})-> + case code:is_loaded(Callback) of + {file,_Path}-> + ok; + false -> + case code:load_file(Callback) of + {module,Callback}-> + ok; + {error,_}-> + exit({no_such_module,Callback}) + end + end, + {Callback, + lists:map(fun(File)-> + check_config_file(Callback, File) + end, FileList)} + end, CfgFiles), case install([{config,AbsCfgFiles},{event_handler,EvHandlers}], LogDir) of ok -> ok; {error,IReason} -> exit(IReason) end, - case lists:keysearch(dir,1,Opts) of + case lists:keysearch(dir, 1, Opts) of {value,{_,Dirs=[Dir|_]}} when not is_integer(Dir), length(Dirs)>1 -> %% multiple dirs (no suite) @@ -754,12 +791,12 @@ run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> do_run(tests(lists:map(S2M, Suites)), [], StepOrCover, Opts, LogDir); _ -> exit(no_tests_specified) - end; + end; {value,{_,Dir}} -> case lists:keysearch(suite, 1, Opts) of {value,{_,Suite}} when is_integer(hd(Suite)) ; is_atom(Suite) -> - Mod = if is_atom(Suite) -> Suite; - true -> list_to_atom(Suite) + Mod = if is_atom(Suite) -> Suite; + true -> list_to_atom(Suite) end, case listify(proplists:get_value(group, Opts, [])) ++ listify(proplists:get_value(testcase, Opts, [])) of @@ -770,77 +807,90 @@ run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) -> end; {value,{_,Suites=[Suite|_]}} when is_list(Suite) -> Mods = lists:map(fun(Str) -> list_to_atom(Str) end, Suites), - do_run(tests(delistify(Dir), Mods), [], StepOrCover, Opts, LogDir); + do_run(tests(delistify(Dir), Mods), [], StepOrCover, Opts, LogDir); {value,{_,Suites}} -> - do_run(tests(delistify(Dir), Suites), [], StepOrCover, Opts, LogDir); + do_run(tests(delistify(Dir), Suites), [], StepOrCover, Opts, LogDir); false -> % no suite, only dir do_run(tests(listify(Dir)), [], StepOrCover, Opts, LogDir) - end + end end. %%%----------------------------------------------------------------- -%%% @hidden +%%% @spec run_testspec(TestSpec) -> Result +%%% TestSpec = [term()] %%% +%%% @doc Run test specified by TestSpec. The terms are +%%% the same as those used in test specification files. +%%% @equiv ct:run_testspec/1 +%%%----------------------------------------------------------------- -%% using testspec(s) as input for test run_testspec(TestSpec) -> {ok,Cwd} = file:get_cwd(), io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]), case catch run_testspec1(TestSpec) of - {'EXIT',Reason} -> + {'EXIT',Reason} -> file:set_cwd(Cwd), {error,Reason}; - Result -> + Result -> Result end. run_testspec1(TestSpec) -> - case ct_testspec:collect_tests_from_list(TestSpec,false) of + case ct_testspec:collect_tests_from_list(TestSpec, false) of {error,CTReason} -> exit(CTReason); TS -> - {LogDir,TSCoverFile,CfgFiles,EvHandlers,Include} = - get_data_for_node(TS,node()), - case os:getenv("CT_INCLUDE_PATH") of - false -> - application:set_env(common_test, include, Include); - CtInclPath -> - EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]), - application:set_env(common_test, include, EnvInclude++Include) - end, - CoverOpt = if TSCoverFile == undef -> []; - true -> [{cover,TSCoverFile}] - end, - case check_and_install_configfiles(CfgFiles,LogDir,EvHandlers) of + Opts = get_data_for_node(TS, node()), + + AllInclude = + case os:getenv("CT_INCLUDE_PATH") of + false -> + Opts#opts.include; + CtInclPath -> + EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]), + EnvInclude++Opts#opts.include, + end, + application:set_env(common_test, include, AllInclude), + + case check_and_install_configfiles(Opts#opts.config, + which(logdir,Opts#opts.logdir), + Opts#opts.event_handlers) of ok -> - {Run,Skip} = ct_testspec:prepare_tests(TS,node()), - do_run(Run,Skip,CoverOpt,[],LogDir); + Opts1 = Opts#opts{testspecs = [TestSpec], + include = AllInclude}, + {Run,Skip} = ct_testspec:prepare_tests(TS, node()), + do_run(Run, Skip, Opts1, []); {error,GCFReason} -> exit(GCFReason) end end. - get_data_for_node(#testspec{logdir=LogDirs, cover=CoverFs, config=Cfgs, userconfig=UsrCfgs, event_handler=EvHs, - include=Incl}, Node) -> - LogDir = case lists:keysearch(Node,1,LogDirs) of - {value,{Node,Dir}} -> Dir; - false -> "." + include=Incl, + multiply_timetraps=MTs, + scale_timetraps=STs}, Node) -> + LogDir = case proplists:get_value(Node, LogDirs) of + undefined -> "."; + Dir -> Dir end, - Cover = case lists:keysearch(Node,1,CoverFs) of - {value,{Node,CovFile}} -> CovFile; - false -> undef - end, - ConfigFiles = [{?ct_config_txt, F} || {N,F} <- Cfgs, N==Node] ++ - [CBF || {N, CBF} <- UsrCfgs, N==Node], + Cover = proplists:get_value(Node, CoverFs), + MT = proplists:get_value(Node, MTs), + ST = proplists:get_value(Node, STs), + 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], Include = [I || {N,I} <- Incl, N==Node], - {LogDir,Cover,ConfigFiles,EvHandlers,Include}. - + #opts{logdir = LogDir, + cover = Cover, + config = ConfigFiles, + event_handlers = EvHandlers, + include = Include, + multiply_timetraps = MT, + scale_timetraps = ST}. refresh_logs(LogDir) -> {ok,Cwd} = file:get_cwd(), @@ -865,11 +915,27 @@ refresh_logs(LogDir) -> end end. -which_logdir(".",Dir) -> +which(logdir, undefined) -> + "."; +which(logdir, Dir) -> Dir; -which_logdir(Dir,_) -> - Dir. - +which(multiply_timetraps, undefined) -> + 1; +which(multiply_timetraps, MT) -> + MT; +which(scale_timetraps, undefined) -> + false; +which(scale_timetraps, ST) -> + ST. + +choose_val(undefined, V1) -> + V1; +choose_val(V0, _V1) -> + V0. + +merge_vals(Vs) -> + lists:append(Vs). + listify([C|_]=Str) when is_integer(C) -> [Str]; listify(L) when is_list(L) -> L; listify(E) -> [E]. @@ -899,6 +965,22 @@ run(TestDirs) -> install([]), do_run(tests(TestDirs), []). +suite_to_test(Suite) -> + {filename:dirname(Suite),list_to_atom(filename:rootname(filename:basename(Suite)))}. + +groups_and_cases(Gs, Cs) when (Gs == undefined ; Gs == []), + (Cs == undefined ; Cs == []) -> + []; +groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] -> + [list_to_atom(C) || C <- Cs]; +groups_and_cases(Gs, Cs) when Cs == undefined ; Cs == [] -> + [{list_to_atom(G),all} || G <- Gs]; +groups_and_cases([G], Cs) -> + [{list_to_atom(G),[list_to_atom(C) || C <- Cs]}]; +groups_and_cases([_,_|_] , Cs) when Cs =/= [] -> + {error,multiple_groups_and_cases}; +groups_and_cases(Gs, Cs) -> + {error,incorrect_group_or_case_option}. tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) -> [{?testdir(TestDir,Suites),ensure_atom(Suites),all}]; @@ -915,30 +997,25 @@ tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) -> tests(TestDirs) when is_list(TestDirs), is_list(hd(TestDirs)) -> [{?testdir(TestDir,all),all,all} || TestDir <- TestDirs]. -do_run(Tests, Opt) -> - do_run(Tests, [], Opt, [], "."). - -do_run(Tests, Opt, LogDir) -> - do_run(Tests, [], Opt, [], LogDir). +do_run(Tests, Opt, LogDir) when is_list(Opt) -> + do_run(Tests, [], #opts{tests = Tests, logdir = LogDir}, []). -do_run(Tests, Skip, Opt, Args, LogDir) -> +do_run(Tests, Skip, Opts, Args) -> + #opts{cover = Cover} = Opts case code:which(test_server) of non_existing -> exit({error,no_path_to_test_server}); _ -> - Opt1 = - case lists:keysearch(cover, 1, Opt) of - {value,{_,CoverFile}} -> - case ct_cover:get_spec(CoverFile) of - {error,Reason} -> - exit({error,Reason}); - Spec -> - [{cover_spec,Spec} | - lists:keydelete(cover, 1, Opt)] - end; - _ -> - Opt - end, + Opts1 = if Cover == undefined -> + Opts; + true -> + case ct_cover:get_spec(Cover) of + {error,Reason} -> + exit({error,Reason}); + CoverSpec -> + Opts#opts{coverspec = CoverSpec} + end + end, %% This env variable is used by test_server to determine %% which framework it runs under. case os:getenv("TEST_SERVER_FRAMEWORK") of @@ -956,7 +1033,6 @@ do_run(Tests, Skip, Opt, Args, LogDir) -> "To enter the interactive mode again, " "run ct:start_interactive()\n\n",[]), {error,interactive_mode}; - _Pid -> %% save style sheet info case lists:keysearch(stylesheet, 1, Args) of @@ -982,7 +1058,7 @@ do_run(Tests, Skip, Opt, Args, LogDir) -> _ -> ok end, - log_ts_names(Args), + log_ts_names(Opts1#opts.testspecs), TestSuites = suite_tuples(Tests), {SuiteMakeErrors,AllMakeErrors} = @@ -1000,7 +1076,7 @@ do_run(Tests, Skip, Opt, Args, LogDir) -> SavedErrors = save_make_errors(SuiteMakeErrors), ct_repeat:log_loop_info(Args), {Tests1,Skip1} = final_tests(Tests,[],Skip,SavedErrors), - R = do_run_test(Tests1, Skip1, Opt1), + R = do_run_test(Tests1, Skip1, Opts1), ct_util:stop(normal), R; false -> @@ -1026,7 +1102,7 @@ auto_compile(TestSuites) -> case application:get_env(common_test, include) of {ok,UserInclDirs} when length(UserInclDirs) > 0 -> io:format("Including the following directories:~n"), - [begin io:format("~p~n",[UserInclDir]), {i,UserInclDir} end || + [begin io:format("~p~n",[UserInclDir]), {i,UserInclDir} end || UserInclDir <- UserInclDirs]; _ -> [] @@ -1034,11 +1110,11 @@ auto_compile(TestSuites) -> SuiteMakeErrors = lists:flatmap(fun({TestDir,Suite} = TS) -> case run_make(suites, TestDir, Suite, UserInclude) of - {error,{make_failed,Bad}} -> + {error,{make_failed,Bad}} -> [{TS,Bad}]; - {error,_} -> + {error,_} -> [{TS,[filename:join(TestDir,"*_SUITE")]}]; - _ -> + _ -> [] end end, TestSuites), @@ -1062,13 +1138,13 @@ auto_compile(TestSuites) -> true -> % already visited {Done,Failed} end - end, {[],[]}, TestSuites), + end, {[],[]}, TestSuites), {SuiteMakeErrors,lists:reverse(HelpMakeErrors)}. %% verify that specified test suites exist (if auto compile is disabled) verify_suites(TestSuites) -> io:nl(), - Verify = + Verify = fun({Dir,Suite},NotFound) -> case locate_test_dir(Dir, Suite) of {ok,TestDir} -> @@ -1077,11 +1153,11 @@ verify_suites(TestSuites) -> true -> Beam = filename:join(TestDir, atom_to_list(Suite)++".beam"), case filelib:is_regular(Beam) of - true -> + true -> NotFound; - false -> + false -> Name = filename:join(TestDir, atom_to_list(Suite)), - io:format("Suite ~w not found in directory ~s~n", + io:format("Suite ~w not found in directory ~s~n", [Suite,TestDir]), [{{Dir,Suite},[Name]} | NotFound] end @@ -1093,8 +1169,7 @@ verify_suites(TestSuites) -> end end, lists:reverse(lists:foldl(Verify, [], TestSuites)). - - + save_make_errors([]) -> []; save_make_errors(Errors) -> @@ -1110,7 +1185,7 @@ get_bad_suites([{{_TestDir,_Suite},Failed}|Errors], BadSuites) -> get_bad_suites([], BadSuites) -> BadSuites. - + %%%----------------------------------------------------------------- %%% @hidden @@ -1121,7 +1196,7 @@ step(TestDir, Suite, Case) -> %%%----------------------------------------------------------------- %%% @hidden %%% @equiv ct:step/4 -step(TestDir, Suite, Case, Opts) when is_list(TestDir), is_atom(Suite), is_atom(Case), +step(TestDir, Suite, Case, Opts) when is_list(TestDir), is_atom(Suite), is_atom(Case), Suite =/= all, Case =/= all -> do_run([{TestDir,Suite,Case}], [{step,Opts}]). @@ -1154,7 +1229,7 @@ final_tests([{TestDir,Suites,_}|Tests], Skip1 = [{TD,S,"Make failed"} || {{TD,S},_} <- Bad, S1 <- Suites, S == S1, TD == TestDir], - Final1 = [{TestDir,S,all} || S <- Suites], + Final1 = [{TestDir,S,all} || S <- Suites], final_tests(Tests, lists:reverse(Final1)++Final, Skip++Skip1, Bad); final_tests([{TestDir,all,all}|Tests], Final, Skip, Bad) -> @@ -1187,7 +1262,7 @@ final_tests([{TestDir,Suite,Cases}|Tests], Final, Skip, Bad) -> final_tests([], Final, Skip, _Bad) -> {lists:reverse(Final),Skip}. -continue([]) -> +continue([]) -> true; continue(_MakeErrors) -> io:nl(), @@ -1228,7 +1303,7 @@ set_group_leader_same_as_shell() -> false end end, - case [P || P <- processes(), GS2or3(P), + case [P || P <- processes(), GS2or3(P), true == lists:keymember(shell,1,element(2,process_info(P,dictionary)))] of [GL|_] -> group_leader(GL, self()); @@ -1252,29 +1327,29 @@ check_and_add([{TestDir0,M,_} | Tests], Added) -> check_and_add([], _) -> ok. -do_run_test(Tests, Skip, Opt) -> +do_run_test(Tests, Skip, Opts) -> case check_and_add(Tests, []) of ok -> ct_util:set_testdata({stats,{0,0,{0,0}}}), ct_util:set_testdata({cover,undefined}), test_server_ctrl:start_link(local), - case lists:keysearch(cover_spec, 1, Opt) of - {value,{_,CovData={CovFile, - CovNodes, - _CovImport, - CovExport, - #cover{app = CovApp, - level = CovLevel, - excl_mods = CovExcl, - incl_mods = CovIncl, - cross = CovCross, - src = _CovSrc}}}} -> + case Opts#opts.coverspec of + CovData={CovFile, + CovNodes, + _CovImport, + CovExport, + #cover{app = CovApp, + level = CovLevel, + excl_mods = CovExcl, + incl_mods = CovIncl, + cross = CovCross, + src = _CovSrc}} -> ct_logs:log("COVER INFO","Using cover specification file: ~s~n" "App: ~w~n" "Cross cover: ~w~n" "Including ~w modules~n" "Excluding ~w modules", - [CovFile,CovApp,CovCross,length(CovIncl),length(CovExcl)]), + [CovFile,CovApp,CovCross,length(CovIncl),length(CovExcl)]), %% cover export file will be used for export and import %% between tests so make sure it doesn't exist initially @@ -1307,33 +1382,37 @@ do_run_test(Tests, Skip, Opt) -> true; _ -> false - end, + end, %% let test_server expand the test tuples and count no of cases {Suites,NoOfCases} = count_test_cases(Tests, Skip), Suites1 = delete_dups(Suites), NoOfTests = length(Tests), NoOfSuites = length(Suites1), - ct_util:warn_duplicates(Suites1), + ct_util:warn_duplicates(Suites1), {ok,Cwd} = file:get_cwd(), io:format("~nCWD set to: ~p~n", [Cwd]), if NoOfCases == unknown -> - io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n", + io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n", [NoOfTests,NoOfSuites]), - ct_logs:log("TEST INFO","~w test(s), ~w suite(s)", + ct_logs:log("TEST INFO","~w test(s), ~w suite(s)", [NoOfTests,NoOfSuites]); true -> - io:format("~nTEST INFO: ~w test(s), ~w case(s) in ~w suite(s)~n~n", + io:format("~nTEST INFO: ~w test(s), ~w case(s) in ~w suite(s)~n~n", [NoOfTests,NoOfCases,NoOfSuites]), - ct_logs:log("TEST INFO","~w test(s), ~w case(s) in ~w suite(s)", + ct_logs:log("TEST INFO","~w test(s), ~w case(s) in ~w suite(s)", [NoOfTests,NoOfCases,NoOfSuites]) end, + + test_server_ctrl:multiply_timetraps(Opts#opts.multiply_timetraps), + test_server_ctrl:scale_timetraps(Opts#opts.scale_timetraps), + ct_event:notify(#event{name=start_info, node=node(), data={NoOfTests,NoOfSuites,NoOfCases}}), - CleanUp = add_jobs(Tests, Skip, Opt, []), + CleanUp = add_jobs(Tests, Skip, Opts, []), unlink(whereis(test_server_ctrl)), - catch test_server_ctrl:wait_finish(), - %% check if last testcase has left a "dead" trace window + catch test_server_ctrl:wait_finish(), + %% check if last testcase has left a "dead" trace window %% behind, and if so, kill it case ct_util:get_testdata(interpret) of {_What,kill,{TCPid,AttPid}} -> @@ -1341,8 +1420,8 @@ do_run_test(Tests, Skip, Opt) -> _ -> ok end, - lists:foreach(fun(Suite) -> - maybe_cleanup_interpret(Suite, Opt) + lists:foreach(fun(Suite) -> + maybe_cleanup_interpret(Suite, Opt) end, CleanUp); Error -> Error @@ -1358,7 +1437,7 @@ count_test_cases(Tests, Skip) -> SendResult = fun(Me, Result) -> Me ! {no_of_cases,Result} end, TSPid = test_server_ctrl:start_get_totals(SendResult), Ref = erlang:monitor(process, TSPid), - add_jobs(Tests, Skip, [], []), + add_jobs(Tests, Skip, #opts{}, []), {Suites,NoOfCases} = count_test_cases1(length(Tests), 0, [], Ref), erlang:demonitor(Ref), test_server_ctrl:stop_get_totals(), @@ -1368,11 +1447,11 @@ count_test_cases1(0, N, Suites, _) -> {lists:flatten(Suites), N}; count_test_cases1(Jobs, N, Suites, Ref) -> receive - {no_of_cases,{Ss,N1}} -> + {no_of_cases,{Ss,N1}} -> count_test_cases1(Jobs-1, add_known(N,N1), [Ss|Suites], Ref); - {'DOWN', Ref, _, _, _} -> + {'DOWN', Ref, _, _, _} -> {[],0} - end. + end. add_known(unknown, _) -> unknown; @@ -1381,72 +1460,72 @@ add_known(_, unknown) -> add_known(N, N1) -> N+N1. -add_jobs([{TestDir,all,_}|Tests], Skip, Opt, CleanUp) -> +add_jobs([{TestDir,all,_}|Tests], Skip, Opts, CleanUp) -> Name = get_name(TestDir), case catch test_server_ctrl:add_dir_with_skip(Name, TestDir, skiplist(TestDir,Skip)) of - {'EXIT',_} -> + {'EXIT',_} -> CleanUp; _ -> wait_for_idle(), - add_jobs(Tests, Skip, Opt, CleanUp) + add_jobs(Tests, Skip, Opts, CleanUp) end; -add_jobs([{TestDir,[Suite],all}|Tests], Skip, Opt, CleanUp) when is_atom(Suite) -> - add_jobs([{TestDir,Suite,all}|Tests], Skip, Opt, CleanUp); -add_jobs([{TestDir,Suites,all}|Tests], Skip, Opt, CleanUp) when is_list(Suites) -> +add_jobs([{TestDir,[Suite],all}|Tests], Skip, Opts, CleanUp) when is_atom(Suite) -> + add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp); +add_jobs([{TestDir,Suites,all}|Tests], Skip, Opts, CleanUp) when is_list(Suites) -> Name = get_name(TestDir) ++ ".suites", case catch test_server_ctrl:add_module_with_skip(Name, Suites, skiplist(TestDir,Skip)) of - {'EXIT',_} -> + {'EXIT',_} -> CleanUp; _ -> wait_for_idle(), - add_jobs(Tests, Skip, Opt, CleanUp) + add_jobs(Tests, Skip, Opts, CleanUp) end; -add_jobs([{TestDir,Suite,all}|Tests], Skip, Opt, CleanUp) -> - case maybe_interpret(Suite, all, Opt) of +add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) -> + case maybe_interpret(Suite, all, Opts) of ok -> Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite), case catch test_server_ctrl:add_module_with_skip(Name, [Suite], skiplist(TestDir,Skip)) of - {'EXIT',_} -> + {'EXIT',_} -> CleanUp; _ -> wait_for_idle(), - add_jobs(Tests, Skip, Opt, [Suite|CleanUp]) + add_jobs(Tests, Skip, Opts, [Suite|CleanUp]) end; Error -> Error end; -add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opt, CleanUp) when is_atom(Case) -> - add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opt, CleanUp); -add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opt, CleanUp) when is_list(Cases) -> - case maybe_interpret(Suite, Cases, Opt) of +add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opts, CleanUp) when is_atom(Case) -> + add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp); +add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opts, CleanUp) when is_list(Cases) -> + case maybe_interpret(Suite, Cases, Opts) of ok -> Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases", case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases, skiplist(TestDir,Skip)) of - {'EXIT',_} -> + {'EXIT',_} -> CleanUp; _ -> wait_for_idle(), - add_jobs(Tests, Skip, Opt, [Suite|CleanUp]) + add_jobs(Tests, Skip, Opts, [Suite|CleanUp]) end; Error -> Error end; -add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opt, CleanUp) when is_atom(Case) -> - case maybe_interpret(Suite, Case, Opt) of +add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp) when is_atom(Case) -> + case maybe_interpret(Suite, Case, Opts) of ok -> - Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++ + Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++ atom_to_list(Case), case catch test_server_ctrl:add_case_with_skip(Name, Suite, Case, skiplist(TestDir,Skip)) of - {'EXIT',_} -> + {'EXIT',_} -> CleanUp; _ -> wait_for_idle(), - add_jobs(Tests, Skip, Opt, [Suite|CleanUp]) + add_jobs(Tests, Skip, Opts, [Suite|CleanUp]) end; Error -> Error @@ -1496,7 +1575,7 @@ get_name(Dir) -> end, Base = filename:basename(TestDir), case filename:basename(filename:dirname(TestDir)) of - "" -> + "" -> Base; TopDir -> TopDir ++ "." ++ Base @@ -1527,10 +1606,10 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> {i,CtInclude}, {i,XmerlInclude}, debug_info], - Result = + Result = if Mod == all ; Targets == helpmods -> case (catch ct_make:all([noexec|ErlFlags])) of - {'EXIT',_} = Failure -> + {'EXIT',_} = Failure -> Failure; MakeInfo -> FileTest = fun(F, suites) -> is_suite(F); @@ -1549,7 +1628,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> true -> (catch ct_make:files([Mod], [load|ErlFlags])) end, - + ok = file:set_cwd(Cwd), %% send finished_make notification ct_event:notify(#event{name=finished_make, @@ -1563,7 +1642,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> {error,{make_crashed,TestDir,Reason}}; {error,ModInfo} -> io:format("{error,make_failed}\n", []), - Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo, + Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo, R == error], {error,{make_failed,Bad}} end; @@ -1575,8 +1654,8 @@ run_make(Targets, TestDir0, Mod, UserInclude) -> get_dir(App, Dir) -> filename:join(code:lib_dir(App), Dir). -maybe_interpret(Suite, Cases, [{step,StepOpts}]) -> - %% if other suite has run before this one, check if last testcase +maybe_interpret(Suite, Cases, #opts{step = StepOpts}) when StepOpts =/= undefined -> + %% if other suite has run before this one, check if last testcase %% has left a "dead" trace window behind, and if so, kill it case ct_util:get_testdata(interpret) of {_What,kill,{TCPid,AttPid}} -> @@ -1619,7 +1698,7 @@ maybe_interpret2(Suite, Cases, StepOpts) -> WinOp = case lists:member(keep_inactive, ensure_atom(StepOpts)) of true -> no_kill; false -> kill - end, + end, ct_util:set_testdata({interpret,{{Suite,Cases},WinOp, {undefined,undefined}}}), ok. @@ -1640,18 +1719,15 @@ maybe_cleanup_interpret(Suite, [{step,_}]) -> maybe_cleanup_interpret(_, _) -> ok. -log_ts_names(Args) -> - case lists:keysearch(spec, 1, Args) of - {value,{_,Specs}} -> - List = lists:map(fun(Name) -> - Name ++ " " - end, Specs), - ct_logs:log("Test Specification file(s)", "~s", - [lists:flatten(List)]); - _ -> - ok - end. - +log_ts_names([]) -> + ok; +log_ts_names(Specs) -> + List = lists:map(fun(Name) -> + Name ++ " " + end, Specs), + ct_logs:log("Test Specification file(s)", "~s", + [lists:flatten(List)]). + merge_arguments(Args) -> merge_arguments(Args, []). @@ -1659,6 +1735,8 @@ merge_arguments([LogDir={logdir,_}|Args], Merged) -> merge_arguments(Args, handle_arg(replace, LogDir, Merged)); merge_arguments([CoverFile={cover,_}|Args], Merged) -> merge_arguments(Args, handle_arg(replace, CoverFile, Merged)); +merge_arguments([{'case',TC}|Args], Merged) -> + merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged)); merge_arguments([Arg={_,_}|Args], Merged) -> merge_arguments(Args, handle_arg(merge, Arg, Merged)); merge_arguments([], Merged) -> @@ -1673,6 +1751,23 @@ handle_arg(Op, Arg, [Other|Merged]) -> handle_arg(_,Arg,[]) -> [Arg]. +get_start_opt(Key, IfExists, Args) -> + get_start_opt(Key, IfExists, undefined, Args); + +get_start_opt(Key, IfExists, IfNotExists, Args) -> + case lists:keysearch(Key, 1, Args) of + {value,{Key,Val}} when is_function(IfExists) -> + IfExists(Val); + {value,{Key,Val}} when IfExists == value -> + Val; + {value,{Key,_Val}} -> + IfExists; + _ when is_function(IfNotExists) -> + IfNotExists(); + _ -> + IfNotExists + end. + locate_test_dir(Dir, Suite) -> TestDir = case ct_util:is_test_dir(Dir) of true -> Dir; @@ -1737,18 +1832,18 @@ start_trace(Args) -> case file:consult(TraceSpec) of {ok,Terms} -> case catch do_trace(Terms) of - ok -> + ok -> true; {_,Error} -> io:format("Warning! Tracing not started. Reason: ~p~n~n", [Error]), false - end; + end; {_,Error} -> io:format("Warning! Tracing not started. Reason: ~p~n~n", [Error]), false - end; + end; false -> false end. @@ -1760,22 +1855,22 @@ do_trace(Terms) -> case dbg:tpl(M,[{'_',[],[{return_trace}]}]) of {error,What} -> exit({error,{tracing_failed,What}}); _ -> ok - end; + end; ({f,M,F}) -> case dbg:tpl(M,F,[{'_',[],[{return_trace}]}]) of {error,What} -> exit({error,{tracing_failed,What}}); _ -> ok - end; + end; (Huh) -> exit({error,{unrecognized_trace_term,Huh}}) end, Terms), ok. - + stop_trace(true) -> dbg:stop_clear(); stop_trace(false) -> ok. - + ensure_atom(Atom) when is_atom(Atom) -> Atom; ensure_atom(String) when is_list(String), is_integer(hd(String)) -> @@ -1784,4 +1879,3 @@ ensure_atom(List) when is_list(List) -> [ensure_atom(Item) || Item <- List]; ensure_atom(Other) -> Other. - diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl index bf01bb52d9..08c57887ef 100644 --- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl @@ -37,7 +37,8 @@ suite() -> %%-------------------------------------------------------------------- init_per_suite(Config) -> timer:sleep(5000), - Config. + exit(shouldnt_happen). +% Config. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config0) -> void() | {save_config,Config1} diff --git a/lib/test_server/doc/src/test_server.xml b/lib/test_server/doc/src/test_server.xml index 6e75425862..0cae75d692 100644 --- a/lib/test_server/doc/src/test_server.xml +++ b/lib/test_server/doc/src/test_server.xml @@ -166,6 +166,22 @@ numbers.

+ + adjusted_sleep(MSecs) -> ok + Suspens the calling task for a specified time. + + MSecs = integer() | float() | infinity + The default number of milliseconds to sleep + + +

This function suspends the calling process for at least the + supplied number of milliseconds. The function behaves the same + way as test_server:sleep/1, only MSecs + will be multiplied by the 'multiply_timetraps' value, if set, + and also automatically scaled up if 'scale_timetraps' is set + to true (which it is by default).

+
+
hours(N) -> MSecs minutes(N) -> MSecs diff --git a/lib/test_server/doc/src/test_server_ctrl.xml b/lib/test_server/doc/src/test_server_ctrl.xml index 8b60849b61..2368c4bacc 100644 --- a/lib/test_server/doc/src/test_server_ctrl.xml +++ b/lib/test_server/doc/src/test_server_ctrl.xml @@ -375,6 +375,31 @@ Optional, if not given the test server controller node multiplied by N.

+ + scale_timetraps(Bool) -> ok + . + + Bool = true | false + + +

This function should be called before a test is started. + The parameter specifies if test_server should attempt + to automatically scale the timetrap value in order to compensate + for delays caused by e.g. the cover tool.

+
+
+ + get_timetrap_parameters() -> {N,Bool} + Read the parameter values that affect timetraps. + + N = integer() | infinity + Bool = true | false + + +

This function may be called to read the values set by + multiply_timetraps/1 and scale_timetraps/1.

+
+
cover(Application,Analyse) -> ok cover(CoverFile,Analyse) -> ok diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 7db103a4c6..7fb708778c 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -35,7 +35,7 @@ -export([fail/0,fail/1,format/1,format/2,format/3]). -export([capture_start/0,capture_stop/0,capture_get/0]). -export([messages_get/0]). --export([hours/1,minutes/1,seconds/1,sleep/1,timecall/3]). +-export([hours/1,minutes/1,seconds/1,sleep/1,adjusted_sleep/1,timecall/3]). -export([timetrap_scale_factor/0,timetrap/1,timetrap_cancel/1]). -export([m_out_of_n/3,do_times/4,do_times/2]). -export([call_crash/3,call_crash/4,call_crash/5]). @@ -89,14 +89,14 @@ init(Host,Port,Starter) -> global:register_name(?MODULE,self()), process_flag(trap_exit,true), test_server_sup:cleanup_crash_dumps(), - case gen_tcp:connect(Host,Port, [binary, - {reuseaddr,true}, + case gen_tcp:connect(Host,Port, [binary, + {reuseaddr,true}, {packet,2}]) of - {ok,MainSock} -> + {ok,MainSock} -> Starter ! {self(),started}, request(MainSock,{target_info,init_target_info()}), loop(#state{controller={Host,MainSock}}); - Error -> + Error -> Starter ! {self(),{error, {could_not_contact_controller,Error}}} end. @@ -127,7 +127,7 @@ loop(#state{controller={_,MainSock}} = State) -> halt(); {'EXIT',Pid,Reason} -> case lists:keysearch(Pid,1,State#state.jobs) of - {value,{Pid,Name}} -> + {value,{Pid,Name}} -> case Reason of normal -> ignore; _other -> request(MainSock,{job_proc_killed,Name,Reason}) @@ -157,14 +157,14 @@ init_purify() -> job(Host,Port,Starter) -> process_flag(trap_exit,true), init_purify(), - case gen_tcp:connect(Host,Port, [binary, - {reuseaddr,true}, + case gen_tcp:connect(Host,Port, [binary, + {reuseaddr,true}, {packet,4}, {active,false}]) of {ok,JobSock} -> Starter ! {self(),started}, job(JobSock); - Error -> + Error -> Starter ! {self(),{error, {could_not_contact_controller,Error}}} end. @@ -192,7 +192,7 @@ get_jobdir() -> true -> {ok,Cwd} = file:get_cwd(), Cwd ++ "/" ++ Basename; - false -> + false -> filename:absname(Basename) end. @@ -216,7 +216,7 @@ send_privdir(JobDir,JobSock) -> del_dir(Dir) -> case file:read_file_info(Dir) of - {ok,#file_info{type=directory}} -> + {ok,#file_info{type=directory}} -> {ok,Cont} = file:list_dir(Dir), lists:foreach(fun(F) -> del_dir(filename:join(Dir,F)) end, Cont), ok = file:del_dir(Dir); @@ -227,7 +227,7 @@ del_dir(Dir) -> catch file:delete(Dir), ok end. - + %% %% Receive and decode request on job socket %% @@ -237,7 +237,7 @@ job_loop(JobSock) -> ok -> job_loop(JobSock); {stop,R} -> R end. - + decode_job({{beam,Mod,Which},Beam}) -> % FIXME, shared directory structure on host and target required, % "Library beams" are not loaded from HOST... /Patrik @@ -254,7 +254,7 @@ decode_job({{datadir,Tarfile0},Archive}) -> ok = erl_tar:extract(Tarfile,[compressed,{cwd,JobDir}]), ok = file:delete(Tarfile), ok; -decode_job({test_case,Case}) -> +decode_job({test_case,Case}) -> Result = run_test_case_apply(Case), JobSock = get(test_server_job_sock), request(JobSock,{test_case_result,Result}), @@ -266,11 +266,11 @@ decode_job({test_case,Case}) -> request(JobSock,{{crash_dumps,filename:basename(TarFile)},TarBin}) end, ok; -decode_job({sync_apply,{M,F,A}}) -> +decode_job({sync_apply,{M,F,A}}) -> R = apply(M,F,A), request(get(test_server_job_sock),{sync_result,R}), ok; -decode_job(job_done) -> +decode_job(job_done) -> {stop,stopped}. %% @@ -282,9 +282,9 @@ decode_job(job_done) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% cover_compile({App,Include,Exclude,Cross}) -> +%% cover_compile({App,Include,Exclude,Cross}) -> %% {ok,AnalyseModules} | {error,Reason} -%% +%% %% App = atom() , name of application to be compiled %% Exclude = [atom()], list of modules to exclude %% Include = [atom()], list of modules outside of App that should be included @@ -293,7 +293,7 @@ decode_job(job_done) -> %% in the cover compilation, but that shall not be part of %% the cover analysis for this application. %% -%% Cover compile the given application. Return {ok,AnalyseMods} if application +%% Cover compile the given application. Return {ok,AnalyseMods} if application %% is found, else {error,application_not_found}. cover_compile({none,_Exclude,Include,Cross}) -> @@ -330,7 +330,7 @@ cover_compile({App,all,Include,Cross}) -> end; cover_compile({App,Exclude,Include,Cross}) -> case code:lib_dir(App) of - {error,bad_name} -> + {error,bad_name} -> case Include++Cross of [] -> io:format("\nWARNING: Can't find lib_dir for \'~w\'\n" @@ -366,7 +366,7 @@ cover_compile({App,Exclude,Include,Cross}) -> {ok,AnalyseMods} end end. - + module_names(Beams) -> [list_to_atom(filename:basename(filename:rootname(Beam))) || Beam <- Beams]. @@ -380,11 +380,11 @@ do_cover_compile1([Dont|Rest]) when Dont=:=cover; Dont=:=test_server_ctrl -> do_cover_compile1(Rest); do_cover_compile1([M|Rest]) -> - case {code:is_sticky(M),code:is_loaded(M)} of + case {code:is_sticky(M),code:is_loaded(M)} of {true,_} -> code:unstick_mod(M), case cover:compile_beam(M) of - {ok,_} -> + {ok,_} -> ok; Error -> io:fwrite("\nWARNING: Could not cover compile ~w: ~p\n", @@ -402,7 +402,7 @@ do_cover_compile1([M|Rest]) -> end; {false,_} -> case cover:compile_beam(M) of - {ok,_} -> + {ok,_} -> ok; Error -> io:fwrite("\nWARNING: Could not cover compile ~w: ~p\n", @@ -415,14 +415,14 @@ do_cover_compile1([]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% cover_analyse(Analyse,Modules) -> [{M,{Cov,NotCov,Details}}] -%% +%% %% Analyse = {details,Dir} | details | {overview,void()} | overview %% Modules = [atom()], the modules to analyse %% %% Cover analysis. If this is a remote target, analyse_to_file can not be used. %% In that case the analyse level 'line' is used instead if Analyse==details. %% -%% If this is a local target, the test directory is given +%% If this is a local target, the test directory is given %% (Analyse=={details,Dir}) and analyse_to_file can be used directly. %% %% If Analyse==overview | {overview,Dir} analyse_to_file is not used, only @@ -432,12 +432,12 @@ do_cover_compile1([]) -> %% all.coverdata in that directory. cover_analyse(Analyse,Modules) -> io:fwrite("Cover analysing...\n",[]), - DetailsFun = + DetailsFun = case Analyse of {details,Dir} -> case cover:export(filename:join(Dir,"all.coverdata")) of ok -> - fun(M) -> + fun(M) -> OutFile = filename:join(Dir, atom_to_list(M) ++ ".COVER.html"), @@ -451,7 +451,7 @@ cover_analyse(Analyse,Modules) -> Error -> fun(_) -> Error end end; - details -> + details -> fun(M) -> case cover:analyse(M,line) of {ok,Lines} -> @@ -489,7 +489,7 @@ cover_analyse(Analyse,Modules) -> unstick_all_sticky(Node) -> lists:filter( - fun(M) -> + fun(M) -> case code:is_sticky(M) of true -> rpc:call(Node,code,unstick_mod,[M]), @@ -502,24 +502,24 @@ unstick_all_sticky(Node) -> stick_all_sticky(Node,Sticky) -> lists:foreach( - fun(M) -> + fun(M) -> rpc:call(Node,code,stick_mod,[M]) end, Sticky). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_case_apply(Mod,Func,Args,Name,RunInit,MultiplyTimetrap) -> +%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData) -> %% {Time,Value,Loc,Opts,Comment} | {died,Reason,unknown,Comment} -%% +%% %% Time = float() (seconds) %% Value = term() %% Loc = term() %% Comment = string() %% Reason = term() %% -%% Spawns off a process (case process) that actually runs the test suite. -%% The case process will have the job process as group leader, which makes +%% Spawns off a process (case process) that actually runs the test suite. +%% The case process will have the job process as group leader, which makes %% it possible to capture all it's output from io:format/2, etc. %% %% The job process then sits down and waits for news from the case process. @@ -535,40 +535,43 @@ stick_all_sticky(Node,Sticky) -> %% called or the comment given by the return value {comment,Comment} from %% a test case. %% -%% {died,Reason,unknown,Comment} is returned if the test case was killed +%% {died,Reason,unknown,Comment} is returned if the test case was killed %% by some other process. Reason is the kill reason provided. %% -%% MultiplyTimetrap indicates a possible extension of all timetraps -%% Timetraps will be multiplied by this integer. If it is infinity, no -%% timetraps will be started at all. +%% TimetrapData = {MultiplyTimetrap,ScaleTimetrap}, which indicates a +%% possible extension of all timetraps. Timetraps will be multiplied by +%% MultiplyTimetrap. If it is infinity, no timetraps will be started at all. +%% ScaleTimetrap indicates if test_server should attemp to automatically +%% compensate timetraps for runtime delays introduced by e.g. tools like +%% cover. -run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,MultiplyTimetrap}) -> +run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) -> purify_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]), case os:getenv("TS_RUN_VALGRIND") of - false -> + false -> ok; _ -> os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++ atom_to_list(Func)++"-") end, test_server_h:testcase({Mod,Func,1}), - ProcBef = erlang:system_info(process_count), - Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap), + ProcBef = erlang:system_info(process_count), + Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData), ProcAft = erlang:system_info(process_count), purify_new_leaks(), DetFail = get(test_server_detected_fail), {Result,DetFail,ProcBef,ProcAft}. - -run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) -> + +run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> case get(test_server_job_dir) of undefined -> %% i'm a local target - do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap); + do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData); JobDir -> %% i'm a remote target case Args of [Config] when is_list(Config) -> - {value,{data_dir,HostDataDir}} = + {value,{data_dir,HostDataDir}} = lists:keysearch(data_dir, 1, Config), DataBase = filename:basename(HostDataDir), TargetDataDir = filename:join(JobDir, DataBase), @@ -578,18 +581,18 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) -> Config2 = lists:keyreplace(priv_dir, 1, Config1, {priv_dir,TargetPrivDir}), do_run_test_case_apply(Mod, Func, [Config2], Name, RunInit, - MultiplyTimetrap); + TimetrapData); _other -> do_run_test_case_apply(Mod, Func, Args, Name, RunInit, - MultiplyTimetrap) + TimetrapData) end end. -do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) -> +do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> {ok,Cwd} = file:get_cwd(), Args2Print = case Args of - [Args1] when is_list(Args1) -> + [Args1] when is_list(Args1) -> lists:keydelete(tc_group_result, 1, Args1); - _ -> + _ -> Args end, print(minor, "Test case started with:\n~s:~s(~p)\n", [Mod,Func,Args2Print]), @@ -600,11 +603,11 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) -> OldGLeader = group_leader(), %% Set ourself to group leader for the spawned process group_leader(self(),self()), - Pid = + Pid = spawn_link( - fun() -> - run_test_case_eval(Mod, Func, Args, Name, Ref, - RunInit, MultiplyTimetrap, + fun() -> + run_test_case_eval(Mod, Func, Args, Name, Ref, + RunInit, TimetrapData, TCCallback) end), group_leader(OldGLeader, self()), @@ -641,13 +644,13 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> receive {'DOWN', Mon, process, Pid, _} -> Comment - after 10000 -> + after 10000 -> %% Pid is probably trapping exits, hit it harder... exit(Pid, kill), %% here's the only place we know Reason, so we save %% it as a comment, potentially replacing user data Error = lists:flatten(io_lib:format("Aborted: ~p",[Reason])), - Error1 = lists:flatten([string:strip(S,left) || + Error1 = lists:flatten([string:strip(S,left) || S <- string:tokens(Error,[$\n])]), if length(Error1) > 63 -> string:substr(Error1,1,60) ++ "..."; @@ -719,7 +722,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> {comment,NewComment} -> Terminate1 = case Terminate of - {true,{Time,Value,Loc,Opts,_OldComment}} -> + {true,{Time,Value,Loc,Opts,_OldComment}} -> {true,{Time,Value,mod_loc(Loc),Opts,NewComment}}; Other -> Other @@ -746,16 +749,16 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> {Mod,_Func} = get_mf(Loc1), spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal}, Loc1,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); {testcase_aborted,Reason,Loc} -> Loc1 = mod_loc(Loc), {Mod,Func} = get_mf(Loc1), spawn_fw_call(Mod,Func,Pid,{testcase_aborted,Reason}, Loc1,self(),Comment), run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); - killed -> + killed -> %% result of an exit(TestCase,kill) call, which is the - %% only way to abort a testcase process that traps exits + %% only way to abort a testcase process that traps exits %% (see abort_current_testcase) spawn_fw_call(undefined,undefined,Pid,testcase_aborted_or_killed, unknown,self(),Comment), @@ -763,7 +766,7 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> {fw_error,{FwMod,FwFunc,FwError}} -> spawn_fw_call(FwMod,FwFunc,Pid,{framework_error,FwError}, unknown,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); _ -> %% the testcase has terminated because of Reason (e.g. an exit %% because a linked process failed) @@ -773,25 +776,25 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> end; {_FwCallPid,fw_notify_done,RetVal} -> %% the framework has been notified, we're finished - run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); {'EXIT',_FwCallPid,{fw_notify_done,Func,Error}} -> %% a framework function failed CB = os:getenv("TEST_SERVER_FRAMEWORK"), Loc = case CB of - false -> + false -> {test_server,Func}; - _ -> + _ -> {list_to_atom(CB),Func} end, RetVal = {died,{framework_error,Loc,Error},Loc,"Framework error"}, run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); {failed,File,Line} -> - put(test_server_detected_fail, + put(test_server_detected_fail, [{File, Line}| get(test_server_detected_fail)]), run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); _Other when not is_tuple(_Other) -> %% ignore anything not generated by test server - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); _Other when element(1, _Other) /= 'EXIT', element(1, _Other) /= started, element(1, _Other) /= finished, @@ -824,7 +827,7 @@ spawn_fw_call(Mod,{init_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why, FwCall = fun() -> Skip = {skip,{failed,{Mod,init_per_testcase,Why}}}, - %% if init_per_testcase fails, the test case + %% if init_per_testcase fails, the test case %% should be skipped case catch test_server_sup:framework_call( end_tc,[?pl2a(Mod),Func,{Pid,Skip,[[]]}]) of @@ -869,7 +872,7 @@ spawn_fw_call(FwMod,FwFunc,_Pid,{framework_error,FwError},_,SendTo,_Comment) -> fun() -> test_server_sup:framework_call(report, [framework_error, {{FwMod,FwFunc},FwError}]), - Comment = + Comment = lists:flatten( io_lib:format("" "WARNING! ~w:~w failed!", [FwMod,FwFunc])), @@ -953,9 +956,11 @@ job_proxy_msgloop() -> %% A test case is known to have failed if it returns {'EXIT', _} tuple, %% or sends a message {failed, File, Line} to it's group_leader -run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, - MultiplyTimetrap, TCCallback) -> - put(test_server_multiply_timetraps,MultiplyTimetrap), +run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, + TimetrapData, TCCallback) -> + + put(test_server_multiply_timetraps,TimetrapData), + {{Time,Value},Loc,Opts} = case test_server_sup:framework_call(init_tc,[?pl2a(Mod),Func,Args0], {ok,Args0}) of @@ -1036,7 +1041,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> {{error,ReasonToFail},{failed,ReasonToFail},EndConf1}; {failed,{_,end_per_testcase,_}} = Failure -> % unexpected termination {Failure,TSReturn,EndConf1}; - _ -> + _ -> {FWReturn,TSReturn,EndConf1} end, case test_server_sup:framework_call(end_tc, [?pl2a(Mod), Func, @@ -1067,7 +1072,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> {{T,Return2},Loc,Opts} end. -%% the return value is a list and we have to check if it contains +%% the return value is a list and we have to check if it contains %% the result of an end conf case or if it's a Config list process_return_val([Return], M,F,A, Loc, Final) when is_list(Return) -> ReturnTags = [skip,skip_and_save,save_config,comment,return_group_result], @@ -1090,16 +1095,16 @@ process_return_val([Return], M,F,A, Loc, Final) when is_list(Return) -> process_return_val(Return, M,F,A, Loc, Final) -> process_return_val1(Return, M,F,A, Loc, Final, []). -process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts) when E=='EXIT'; +process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts) when E=='EXIT'; E==failed -> fw_error_notify(M,F,A, TCError, mod_loc(Loc)), test_server_sup:framework_call(end_tc, [?pl2a(M),F,{{error,TCError}, [[{tc_status,{failed,TCError}}|Args]]}]), {Failed,SaveOpts}; -process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) -> +process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) -> process_return_val1(Opts, M,F,[[SaveCfg|Args]], Loc, Final, SaveOpts); -process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) -> +process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) -> process_return_val1(Opts, M,F,[[{save_config,SaveCfg}|Args]], Loc, {skip,Why}, SaveOpts); process_return_val1([GR={return_group_result,_}|Opts], M,F,A, Loc, Final, SaveOpts) -> process_return_val1(Opts, M,F,A, Loc, Final, [GR|SaveOpts]); @@ -1138,7 +1143,7 @@ init_per_testcase(Mod, Func, Args) -> case erlang:function_exported(Mod,init_per_testcase,2) of true -> case catch my_apply(Mod, init_per_testcase, [Func|Args]) of - {'$test_server_ok',{Skip,Reason}} when Skip==skip; + {'$test_server_ok',{Skip,Reason}} when Skip==skip; Skip==skipped -> {skip,Reason}; {'$test_server_ok',Res={skip_and_save,_,_}} -> @@ -1149,31 +1154,31 @@ init_per_testcase(Mod, Func, Args) -> [] -> {ok,NewConf}; Bad -> - group_leader() ! {printout,12, + group_leader() ! {printout,12, "ERROR! init_per_testcase has returned " - "bad elements in Config: ~p\n",[Bad]}, + "bad elements in Config: ~p\n",[Bad]}, {skip,{failed,{Mod,init_per_testcase,bad_return}}} end; {'$test_server_ok',_Other} -> - group_leader() ! {printout,12, + group_leader() ! {printout,12, "ERROR! init_per_testcase did not return " - "a Config list.\n",[]}, + "a Config list.\n",[]}, {skip,{failed,{Mod,init_per_testcase,bad_return}}}; {'EXIT',Reason} -> Line = get_loc(), FormattedLoc = test_server_sup:format_loc(mod_loc(Line)), - group_leader() ! {printout,12, + group_leader() ! {printout,12, "ERROR! init_per_testcase crashed!\n" "\tLocation: ~s\n\tReason: ~p\n", - [FormattedLoc,Reason]}, + [FormattedLoc,Reason]}, {skip,{failed,{Mod,init_per_testcase,Reason}}}; Other -> Line = get_loc(), FormattedLoc = test_server_sup:format_loc(mod_loc(Line)), - group_leader() ! {printout,12, + group_leader() ! {printout,12, "ERROR! init_per_testcase thrown!\n" "\tLocation: ~s\n\tReason: ~p\n", - [FormattedLoc, Other]}, + [FormattedLoc, Other]}, {skip,{failed,{Mod,init_per_testcase,Other}}} end; false -> @@ -1182,7 +1187,7 @@ init_per_testcase(Mod, Func, Args) -> [Config] = Args, {ok, Config} end. - + end_per_testcase(Mod, Func, Conf) -> case erlang:function_exported(Mod,end_per_testcase,2) of true -> @@ -1211,11 +1216,11 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) -> comment(io_lib:format("" "WARNING: ~w crashed!" "\n",[EndFunc])), - group_leader() ! {printout,12, + group_leader() ! {printout,12, "WARNING: ~w crashed!\n" "Reason: ~p\n" "Line: ~s\n", - [EndFunc, Reason, + [EndFunc, Reason, test_server_sup:format_loc( mod_loc(get_loc()))]}, {failed,{Mod,end_per_testcase,Why}}; @@ -1223,13 +1228,13 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) -> comment(io_lib:format("" "WARNING: ~w thrown!" "\n",[EndFunc])), - group_leader() ! {printout,12, + group_leader() ! {printout,12, "WARNING: ~w thrown!\n" "Reason: ~p\n" "Line: ~s\n", - [EndFunc, Other, + [EndFunc, Other, test_server_sup:format_loc( - mod_loc(get_loc()))]}, + mod_loc(get_loc()))]}, {failed,{Mod,end_per_testcase,Other}} end. @@ -1254,7 +1259,7 @@ get_mf(_) -> {undefined,undefined}. mod_loc(Loc) -> %% handle diff line num versions - case Loc of + case Loc of [{{_M,_F},_L}|_] -> [{?pl2a(M),F,L} || {{M,F},L} <- Loc]; [{_M,_F}|_] -> @@ -1286,7 +1291,7 @@ fw_error_notify(Mod, Func, Args, Error, Loc) -> %% Args = [term()] %% %% Just like io:format, except that depending on the Detail value, the output -%% is directed to console, major and/or minor log files. +%% is directed to console, major and/or minor log files. print(Detail,Format,Args) -> local_or_remote_apply({test_server_ctrl,print,[Detail,Format,Args]}). @@ -1296,11 +1301,11 @@ print(Detail,Format,Args) -> %% %% Prints Leader followed by a time stamp (date and time). Depending on %% the Detail value, the output is directed to console, major and/or minor -%% log files. +%% log files. print_timestamp(Detail,Leader) -> local_or_remote_apply({test_server_ctrl,print_timestamp,[Detail,Leader]}). - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% lookup_config(Key,Config) -> {value,{Key,Value}} | undefined @@ -1326,11 +1331,11 @@ ts_tc(M, F, A) -> Val = (catch my_apply(M, F, A)), After = erlang:now(), Result = case Val of - {'$test_server_ok', R} -> + {'$test_server_ok', R} -> R; % test case ok - {'EXIT',_Reason} = R -> + {'EXIT',_Reason} = R -> R; % test case crashed - Other -> + Other -> {failed, {thrown,Other}} % test case was thrown end, Elapsed = @@ -1352,7 +1357,7 @@ my_apply(M, F, A) -> %% in an attempt to keep this modules small (yeah, right!) %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unicode_to_latin1(Chars) when is_list(Chars); is_binary(Chars) -> - lists:flatten( + lists:flatten( [ case X of High when High > 255 -> io_lib:format("\\{~.8B}",[X]); @@ -1459,6 +1464,44 @@ sleep(MSecs) -> end, ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% adjusted_sleep(Time) -> ok +%% Time = integer() | float() | infinity +%% +%% Sleeps the specified number of milliseconds, multiplied by the +%% 'multiply_timetraps' value (if set) and possibly also automatically scaled +%% up if 'scale_timetraps' is set to true (which is default). +%% This function also accepts floating point numbers (which are truncated) and +%% the atom 'infinity'. +adjusted_sleep(infinity) -> + receive + after infinity -> + ok + end; +adjusted_sleep(MSecs) -> + {Multiplier,ScaleFactor} = + case test_server_ctrl:get_timetrap_parameters() of + {undefined,undefined} -> + {1,1}; + {undefined,false} -> + {1,1}; + {undefined,true} -> + {1,timetrap_scale_factor()}; + {infinity,_} -> + {infinity,1}; + {Mult,undefined} -> + {Mult,1}; + {Mult,false} -> + {Mult,1}; + {Mult,true} -> + {Mult,timetrap_scale_factor()} + end, + receive + after trunc(MSecs*Multiplier*ScaleFactor) -> + ok + end, + ok. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% fail(Reason) -> exit({suite_failed,Reason}) %% @@ -1509,9 +1552,9 @@ break(Comment) -> receive continue -> ok end. spawn_break_process(Pid) -> - spawn(fun() -> + spawn(fun() -> register(test_server_break_process,self()), - receive + receive continue -> continue(Pid); cancel -> ok end @@ -1561,20 +1604,20 @@ timetrap_scale_factor() -> %% timetrap(Timeout) -> Handle %% Handle = term() %% -%% Creates a time trap, that will kill the calling process if the +%% Creates a time trap, that will kill the calling process if the %% trap is not cancelled with timetrap_cancel/1, within Timeout milliseconds. timetrap(Timeout0) -> Timeout = time_ms(Timeout0), cancel_default_timetrap(), case get(test_server_multiply_timetraps) of - undefined -> timetrap1(Timeout); - infinity -> infinity; - Int -> timetrap1(Timeout*Int) + undefined -> timetrap1(Timeout, true); + {infinity,_} -> infinity; + {Int,Scale} -> timetrap1(Timeout*Int, Scale) end. -timetrap1(Timeout) -> - Ref = spawn_link(test_server_sup,timetrap,[Timeout,self()]), +timetrap1(Timeout, Scale) -> + Ref = spawn_link(test_server_sup,timetrap,[Timeout,Scale,self()]), case get(test_server_timetraps) of undefined -> put(test_server_timetraps,[Ref]); List -> put(test_server_timetraps,[Ref|List]) @@ -1582,7 +1625,6 @@ timetrap1(Timeout) -> Ref. ensure_timetrap(Config) -> - %format("ensure_timetrap:~p~n",[Config]), case get(test_server_timetraps) of [_|_] -> ok; @@ -1623,7 +1665,7 @@ cancel_default_timetrap() -> time_ms({hours,N}) -> hours(N); time_ms({minutes,N}) -> minutes(N); time_ms({seconds,N}) -> seconds(N); -time_ms({Other,_N}) -> +time_ms({Other,_N}) -> format("=== ERROR: Invalid time specification: ~p. " "Should be seconds, minutes, or hours.~n", [Other]), exit({invalid_time_spec,Other}); @@ -1770,14 +1812,14 @@ call_crash(Time,Crash,M,F,A) -> %% by the test server after completion of the test case %% Therefore it is IMPORTANT that the USER terminates %% the node!! -%% {erl, ReleaseList} - Use an Erlang emulator determined by ReleaseList -%% when starting nodes, instead of the same emulator +%% {erl, ReleaseList} - Use an Erlang emulator determined by ReleaseList +%% when starting nodes, instead of the same emulator %% as the test server is running. ReleaseList is a list -%% of specifiers, where a specifier is either -%% {release, Rel}, {prog, Prog}, or 'this'. Rel is -%% either the name of a release, e.g., "r7a" or -%% 'latest'. 'this' means using the same emulator as -%% the test server. Prog is the name of an emulator +%% of specifiers, where a specifier is either +%% {release, Rel}, {prog, Prog}, or 'this'. Rel is +%% either the name of a release, e.g., "r7a" or +%% 'latest'. 'this' means using the same emulator as +%% the test server. Prog is the name of an emulator %% executable. If the list has more than one element, %% one of them is picked randomly. (Only %% works on Solaris and Linux, and the test @@ -1792,13 +1834,13 @@ call_crash(Time,Crash,M,F,A) -> %% peer nodes. %% Note that slave nodes always act as if they had %% fail_on_error==false. -%% +%% start_node(Name, Type, Options) -> lists:foreach( - fun(N) -> + fun(N) -> case firstname(N) of - Name -> + Name -> format("=== WARNING: Trying to start node \'~w\' when node" " with same first name exists: ~w", [Name, N]); _other -> ok @@ -1817,19 +1859,19 @@ start_node(Name, Type, Options) -> %% Cannot run cover on shielded node or on a node started %% by a shielded node. Cover = case is_cover() of - true -> + true -> not is_shielded(Name) andalso same_version(Node); - false -> + false -> false end, net_adm:ping(Node), case Cover of - true -> + true -> Sticky = unstick_all_sticky(Node), cover:start(Node), stick_all_sticky(Node,Sticky); - _ -> + _ -> ok end, {ok,Node}; @@ -1857,7 +1899,7 @@ wait_for_node(Slave) -> self(), {test_server_ctrl,wait_for_node,[Slave]}}, receive {sync_result,R} -> R end. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% stop_node(Name) -> true|false @@ -1867,7 +1909,7 @@ wait_for_node(Slave) -> stop_node(Slave) -> Nocover = is_shielded(Slave) orelse not same_version(Slave), case is_cover() of - true when not Nocover -> + true when not Nocover -> Sticky = unstick_all_sticky(Slave), cover:stop(Slave), stick_all_sticky(Slave,Sticky); @@ -1895,10 +1937,10 @@ stop_node(Slave) -> %% with the {cleanup,false} option, or it was started %% in some other way than test_server:start_node/3 format("=== WARNING: Attempt to stop a nonexisting slavenode (~p)~n" - "=== Trying to kill it anyway!!!", + "=== Trying to kill it anyway!!!", [Slave]), case net_adm:ping(Slave)of - pong -> + pong -> slave:stop(Slave), true; pang -> @@ -1918,7 +1960,7 @@ is_release_available(Release) -> self(), {test_server_ctrl,is_release_available,[Release]}}, receive {sync_result,R} -> R end. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% run_on_shielded_node(Fun, CArgs) -> term() @@ -1937,7 +1979,7 @@ is_release_available(Release) -> %% %% Fun - Function to execute %% CArg - Extra command line arguments to use when starting -%% the shielded node. +%% the shielded node. %% %% If Fun is successfully executed, the result is returned. %% @@ -2037,8 +2079,8 @@ is_native(Mod) -> %% The given String will occur in the comment field %% of the table on the test suite result page. If %% called several times, only the last comment is -%% printed. -%% comment/1 is also overwritten by the return value +%% printed. +%% comment/1 is also overwritten by the return value %% {comment,Comment} or fail/1 (which prints Reason %% as a comment). comment(String) -> @@ -2154,7 +2196,7 @@ purify_new_fds_inuse() -> {'EXIT', _} -> false; Inuse when is_integer(Inuse) -> Inuse end. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% purify_format(Format, Args) -> ok %% Format = string() @@ -2202,9 +2244,9 @@ local_or_remote_apply({M,F,A} = MFA) -> request(Sock,Request) -> gen_tcp:send(Sock,<<1,(term_to_binary(Request))/binary>>). -%% +%% %% Generic receive function for communication with host -%% +%% recv(Sock) -> case gen_tcp:recv(Sock,0) of {error,closed} -> diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 5c8ef0b703..49c8bfa2c9 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -27,7 +27,7 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% MODULE DEPENDENCIES: -%% HARD TO REMOVE: erlang, lists, io_lib, gen_server, file, io, string, +%% HARD TO REMOVE: erlang, lists, io_lib, gen_server, file, io, string, %% code, ets, rpc, gen_tcp, inet, erl_tar, sets, %% test_server, test_server_sup, test_server_node %% EASIER TO REMOVE: filename, filelib, lib, re @@ -36,7 +36,7 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ARCHITECTURE -%% +%% %% The Erlang Test Server can be run on the target machine (local target) %% or towards a remote target. The execution flow is mainly the same in %% both cases, but with a remote target the test cases are (obviously) @@ -44,11 +44,11 @@ %% socket connections because the host should not be introduced as an %% additional node in the distributed erlang system in which the test %% cases are run. -%% -%% +%% +%% %% Local Target: %% ============= -%% +%% %% ----- %% | | test_server_ctrl ({global,test_server}) %% ----- (test_server_ctrl.erl) @@ -62,33 +62,33 @@ %% ----- %% | | CaseProc %% ----- (test_server.erl) -%% -%% -%% +%% +%% +%% %% test_server_ctrl is the main process in the system. It is a registered %% process, and it will always be alive when testing is ongoing. %% test_server_ctrl initiates testing and monitors JobProc(s). -%% -%% When target is local, and Test Server is *not* being used by a framework -%% application (where it might cause duplicate name problems in a distributed -%% test environment), the process is globally registered as 'test_server' +%% +%% When target is local, and Test Server is *not* being used by a framework +%% application (where it might cause duplicate name problems in a distributed +%% test environment), the process is globally registered as 'test_server' %% to be able to simulate the {global,test_server} process on a remote target. -%% -%% JobProc is spawned for each 'job' added to the test_server_ctrl. +%% +%% JobProc is spawned for each 'job' added to the test_server_ctrl. %% A job can mean one test case, one test suite or one spec. %% JobProc creates and writes logs and presents results from testing. %% JobProc is the group leader for CaseProc. -%% +%% %% CaseProc is spawned for each test case. It runs the test case and %% sends results and any other information to its group leader - JobProc. -%% -%% -%% +%% +%% +%% %% Remote Target: %% ============== -%% +%% %% HOST TARGET -%% +%% %% ----- MainSock ----- %% test_server_ctrl | |- - - - - - -| | {global,test_server} %% (test_server_ctrl.erl) ----- ----- (test_server.erl) @@ -102,36 +102,36 @@ %% ----- %% | | CaseProc %% ----- (test_server.erl) -%% -%% -%% -%% +%% +%% +%% +%% %% A separate test_server process only exists when target is remote. It %% is then the main process on target. It is started when test_server_ctrl %% is started, and a socket connection is established between %% test_server_ctrl and test_server. The following information can be sent %% over MainSock: -%% +%% %% HOST TARGET %% -> {target_info, TargetInfo} (during initiation) %% <- {job_proc_killed,Name,Reason} (if a JobProcT dies unexpectedly) %% -> {job,Port,Name} (to start a new JobProcT) -%% -%% +%% +%% %% When target is remote, JobProc is split into to processes: JobProcH %% executing on Host and JobProcT executing on Target. (The two processes %% execute the same code as JobProc does when target is local.) JobProcH %% and JobProcT communicates over a socket connection. The following %% information can be sent over JobSock: -%% +%% %% HOST TARGET %% -> {test_case, Case} To start a new test case %% -> {beam,Mod} .beam file as binary to be loaded %% on target, e.g. a test suite %% -> {datadir,Tarfile} Content of the datadir for a test suite %% <- {apply,MFA} MFA to be applied on host, ignore return; -%% (apply is used for printing information in -%% log or console) +%% (apply is used for printing information in +%% log or console) %% <- {sync_apply,MFA} MFA to be applied on host, wait for return %% (used for starting and stopping slave nodes) %% -> {sync_apply,MFA} MFA to be applied on target, wait for return @@ -141,7 +141,7 @@ %% <- {crash_dumps,Tarfile} When a test case is finished %% -> job_done When a job is finished %% <- {privdir,Privdir} When a job is finished -%% +%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -161,7 +161,7 @@ abort_current_testcase/1, abort/0]). -export([start_get_totals/1, stop_get_totals/0]). -export([get_levels/0, set_levels/3]). --export([multiply_timetraps/1]). +-export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]). -export([cover/2, cover/3, cover/7, cross_cover_analyse/1, cross_cover_analyse/2, trc/1, stop_trace/0]). -export([testcase_callback/1]). @@ -207,13 +207,15 @@ -define(pl2a(M), test_server_sup:package_atom(M)). -define(void_fun, fun() -> ok end). --define(mod_result(X), if X == skip -> skipped; - X == auto_skip -> skipped; +-define(mod_result(X), if X == skip -> skipped; + X == auto_skip -> skipped; true -> X end). --record(state,{jobs=[],levels={1,19,10},multiply_timetraps=1,finish=false, +-record(state,{jobs=[],levels={1,19,10}, + multiply_timetraps=1,scale_timetraps=true, + finish=false, target_info, trc=false, cover=false, wait_for_node=[], - testcase_callback=undefined, idle_notify=[], + testcase_callback=undefined, idle_notify=[], get_totals=false, random_seed=undefined}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -222,14 +224,14 @@ add_dir(Name, Job=[Dir|_Dirs]) when is_list(Dir) -> add_job(cast_to_list(Name), lists:map(fun(D)-> {dir,cast_to_list(D)} end, Job)); -add_dir(Name, Dir) -> +add_dir(Name, Dir) -> add_job(cast_to_list(Name), {dir,cast_to_list(Dir)}). add_dir(Name, Job=[Dir|_Dirs], Pattern) when is_list(Dir) -> add_job(cast_to_list(Name), lists:map(fun(D)-> {dir,cast_to_list(D), cast_to_list(Pattern)} end, Job)); -add_dir(Name, Dir, Pattern) -> +add_dir(Name, Dir, Pattern) -> add_job(cast_to_list(Name), {dir,cast_to_list(Dir),cast_to_list(Pattern)}). add_module(Mod) when is_atom(Mod) -> @@ -256,14 +258,14 @@ add_spec(Spec) -> false -> {error,nofile} end. -%% This version of the interface is to be used if there are +%% This version of the interface is to be used if there are %% suites or cases that should be skipped. add_dir_with_skip(Name, Job=[Dir|_Dirs], Skip) when is_list(Dir) -> add_job(cast_to_list(Name), lists:map(fun(D)-> {dir,cast_to_list(D)} end, Job), Skip); -add_dir_with_skip(Name, Dir, Skip) -> +add_dir_with_skip(Name, Dir, Skip) -> add_job(cast_to_list(Name), {dir,cast_to_list(Dir)}, Skip). add_dir_with_skip(Name, Job=[Dir|_Dirs], Pattern, Skip) when is_list(Dir) -> @@ -271,7 +273,7 @@ add_dir_with_skip(Name, Job=[Dir|_Dirs], Pattern, Skip) when is_list(Dir) -> lists:map(fun(D)-> {dir,cast_to_list(D), cast_to_list(Pattern)} end, Job), Skip); -add_dir_with_skip(Name, Dir, Pattern, Skip) -> +add_dir_with_skip(Name, Dir, Pattern, Skip) -> add_job(cast_to_list(Name), {dir,cast_to_list(Dir),cast_to_list(Pattern)}, Skip). @@ -295,15 +297,14 @@ add_cases_with_skip(Name, Mod, Cases, Skip) when is_atom(Mod), is_list(Cases) -> add_tests_with_skip(LogDir, Tests, Skip) -> add_job(LogDir, - lists:map(fun({Dir,all,all}) -> + lists:map(fun({Dir,all,all}) -> {Dir,{dir,Dir}}; - ({Dir,Mods,all}) -> + ({Dir,Mods,all}) -> {Dir,lists:map(fun(M) -> {M,all} end, Mods)}; ({Dir,Mod,Cases}) -> {Dir,{Mod,Cases}} end, Tests), Skip). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% COMMAND LINE INTERFACE @@ -315,7 +316,7 @@ parse_cmd_line(['SPEC',Spec|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) -> case file:consult(Spec) of {ok, TermList} -> Name = filename:rootname(Spec), - parse_cmd_line(Cmds, TermList++SpecList, [Name|Names], Param, + parse_cmd_line(Cmds, TermList++SpecList, [Name|Names], Param, Trc, Cov, TCCB); {error,Reason} -> io:format("Can't open ~s: ~p\n", @@ -406,7 +407,7 @@ run_test(CommandLine) -> end, testcase_callback(TCCB), add_job(Name, {command_line,SpecList}), - + %% adding of jobs involves file i/o which may take long time %% when running a nfs mounted file system (VxWorks). case controller_call(get_target_info) of @@ -479,6 +480,12 @@ set_levels(Show, Major, Minor) -> multiply_timetraps(N) -> controller_call({multiply_timetraps,N}). +scale_timetraps(Bool) -> + controller_call({scale_timetraps,Bool}). + +get_timetrap_parameters() -> + controller_call(get_timetrap_parameters). + trc(TraceFile) -> controller_call({trace,TraceFile}, 2*?ACCEPT_TIMEOUT). @@ -551,7 +558,7 @@ controller_call(Arg, Timeout) -> Other -> Other end. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -617,7 +624,7 @@ contact_main_target(local) -> case os:getenv("TEST_SERVER_FRAMEWORK") of false -> %% Local target! The global test_server process implemented by - %% test_server.erl will not be started, so we simulate it by + %% test_server.erl will not be started, so we simulate it by %% globally registering this process instead. global:sync(), case global:whereis_name(test_server) of @@ -681,9 +688,9 @@ read_parameters([], Par) when Par#par.type==undefined -> read_parameters([], Par) when Par#par.target==undefined -> {error, {missing_mandatory_parameter,target}}; read_parameters([], Par0) -> - Par = + Par = case {Par0#par.type, Par0#par.master} of - {ose, undefined} -> + {ose, undefined} -> %% Use this node as master and bootserver for target %% and slave nodes Par0#par{master = atom_to_list(node()), @@ -691,10 +698,10 @@ read_parameters([], Par0) -> {ose, _Master} -> %% Master for target and slave nodes was defined in parameterfile Par0; - _ -> + _ -> %% Use target as master for slave nodes, %% (No master is used for target) - Par0#par{master="test_server@" ++ Par0#par.target} + Par0#par{master="test_server@" ++ Par0#par.target} end, {ok,Par}. @@ -708,7 +715,7 @@ naming() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call(kill_slavenodes, From, State) -> ok %% -%% Kill all slave nodes that remain after a test case +%% Kill all slave nodes that remain after a test case %% is completed. %% handle_call(kill_slavenodes, _From, State) -> @@ -736,7 +743,7 @@ handle_call(get_hosts, _From, State) -> {reply, Hosts, State}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% handle_call({add_job,Dir,Name,TopCase,Skip}, _, State) -> +%% handle_call({add_job,Dir,Name,TopCase,Skip}, _, State) -> %% ok | {error,Reason} %% %% Dir = string() @@ -760,7 +767,7 @@ handle_call(get_hosts, _From, State) -> handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> LogDir = Dir ++ ?logdir_ext, - ExtraTools = + ExtraTools = case State#state.cover of false -> []; {App,Analyse} -> [{cover,App,Analyse}] @@ -776,19 +783,21 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> {spec,SpecName} -> Pid = spawn_tester( ?MODULE, do_spec, - [SpecName,State#state.multiply_timetraps], - LogDir, Name, State#state.levels, + [SpecName,{State#state.multiply_timetraps, + State#state.scale_timetraps}], + LogDir, Name, State#state.levels, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], - {reply, ok, State#state{jobs=NewJobs}}; + {reply, ok, State#state{jobs=NewJobs}}; {command_line,SpecList} -> Pid = spawn_tester( ?MODULE, do_spec_list, - [SpecList,State#state.multiply_timetraps], - LogDir, Name, State#state.levels, + [SpecList,{State#state.multiply_timetraps, + State#state.scale_timetraps}], + LogDir, Name, State#state.levels, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], - {reply, ok, State#state{jobs=NewJobs}}; + {reply, ok, State#state{jobs=NewJobs}}; TopCase -> case State#state.get_totals of {CliPid,Fun} -> @@ -798,10 +807,11 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> _ -> Cfg = make_config([]), Pid = spawn_tester( - ?MODULE, do_test_cases, + ?MODULE, do_test_cases, [TopCase,Skip,Cfg, - State#state.multiply_timetraps], - LogDir, Name, State#state.levels, + {State#state.multiply_timetraps, + State#state.scale_timetraps}], + LogDir, Name, State#state.levels, State#state.testcase_callback, ExtraTools1), NewJobs = [{Name,Pid}|State#state.jobs], {reply, ok, State#state{jobs=NewJobs}} @@ -827,7 +837,7 @@ handle_call(jobs, _From, State) -> %% handle_call({abort_current_testcase,Reason}, _, State) -> Result %% Reason = term() %% Result = ok | {error,no_testcase_running} -%% +%% %% Attempts to abort the test case that's currently running. handle_call({abort_current_testcase,Reason}, _From, State) -> @@ -855,7 +865,7 @@ handle_call({abort_current_testcase,Reason}, _From, State) -> handle_call({finish,Fini}, _From, State) -> case State#state.jobs of [] -> - lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, + lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, State#state.idle_notify), State2 = State#state{finish=false}, {stop,shutdown,{ok,self()}, State2}; @@ -878,7 +888,7 @@ handle_call({idle_notify,Fun}, {Cli,_Ref}, State) -> {reply, {ok,self()}, State}; _ -> Subscribed = State#state.idle_notify, - {reply, {ok,self()}, + {reply, {ok,self()}, State#state{idle_notify=[{Cli,Fun}|Subscribed]}} end; @@ -891,7 +901,7 @@ handle_call({idle_notify,Fun}, {Cli,_Ref}, State) -> handle_call({start_get_totals,Fun}, {Cli,_Ref}, State) -> {reply, {ok,self()}, State#state{get_totals={Cli,Fun}}}; - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call(stop_get_totals, From, State) -> ok %% @@ -941,12 +951,32 @@ handle_call({set_levels,Show,Major,Minor}, _From, State) -> handle_call({multiply_timetraps,N}, _From, State) -> {reply,ok,State#state{multiply_timetraps=N}}; +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% handle_call({scale_timetraps,Bool}, _, State) -> ok +%% Bool = true | false +%% +%% Specifies if test_server should scale the timetrap value +%% automatically if e.g. cover is running. + +handle_call({scale_timetraps,Bool}, _From, State) -> + {reply,ok,State#state{scale_timetraps=Bool}}; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% handle_call(get_timetrap_parameters, _, State) -> {Multiplier,Scale} +%% Multiplier = integer() | infinity +%% Scale = true | false +%% +%% Returns the parameter values that affect timetraps. + +handle_call(get_timetrap_parameters, _From, State) -> + {reply,{State#state.multiply_timetraps,State#state.scale_timetraps},State}; + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call({trace,TraceFile}, _, State) -> ok | {error,Reason} %% -%% Starts a separate node (trace control node) which +%% Starts a separate node (trace control node) which %% starts tracing on target and all slave nodes -%% +%% %% TraceFile is a text file with elements of type %% {Trace,Mod,TracePattern}. %% {Trace,Mod,Func,TracePattern}. @@ -955,10 +985,10 @@ handle_call({multiply_timetraps,N}, _From, State) -> %% Trace = tp | tpl; local or global call trace %% Mod,Func = atom(), Arity=integer(); defines what to trace %% TracePattern = [] | match_spec() -%% +%% %% The 'call' trace flag is set on all processes, and then %% the given trace patterns are set. - + handle_call({trace,TraceFile}, _From, State=#state{trc=false}) -> TI = State#state.target_info, case test_server_node:start_tracer_node(TraceFile, TI) of @@ -993,7 +1023,7 @@ handle_call({cover,App,Analyse}, _From, State) -> %% handle_call({testcase_callback,{Mod,Func}}, _, State) -> ok | {error,Reason} %% %% Add a callback function that will be called before and after every -%% test case (on the test case process): +%% test case (on the test case process): %% %% Mod:Func(Suite,TestCase,InitOrEnd,Config) %% @@ -1001,9 +1031,9 @@ handle_call({cover,App,Analyse}, _From, State) -> handle_call({testcase_callback,ModFunc}, _From, State) -> case ModFunc of - {Mod,Func} -> + {Mod,Func} -> case code:is_loaded(Mod) of - {file,_} -> + {file,_} -> ok; false -> code:load_file(Mod) @@ -1065,15 +1095,15 @@ handle_call({start_node, Name, Type, Options}, From, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call({wait_for_node,Node}, _, State) -> ok %% -%% Waits for a new node to take contact. Used if +%% Waits for a new node to take contact. Used if %% node is started with option {wait,false} handle_call({wait_for_node, Node}, From, State) -> - NewWaitList = + NewWaitList = case ets:lookup(slave_tab,Node) of - [] -> + [] -> [{Node,From}|State#state.wait_for_node]; - _ -> + _ -> gen_server:reply(From,ok), State#state.wait_for_node end, @@ -1086,7 +1116,7 @@ handle_call({wait_for_node, Node}, From, State) -> %% - the node is really stopped by test_server when this returns. handle_call({stop_node, Name}, _From, State) -> - R = test_server_node:stop_node(Name, State#state.target_info), + R = test_server_node:stop_node(Name, State#state.target_info), {reply, R, State}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1112,7 +1142,7 @@ handle_cast({node_started,Node}, State) -> false -> ok; Trc -> test_server_node:trace_nodes(Trc, [Node]) end, - NewWaitList = + NewWaitList = case lists:keysearch(Node,1,State#state.wait_for_node) of {value,{Node,From}} -> gen_server:reply(From, ok), @@ -1128,10 +1158,10 @@ handle_cast({node_started,Node}, State) -> %% Reason = term() %% %% Handles exit messages from linked processes. Only test suites and -%% possibly a target client are expected to be linked. +%% possibly a target client are expected to be linked. %% When a test suite terminates, it is removed from the job queue. %% If a target client terminates it means that we lost contact with -%% target. The test_server_ctrl process is terminated, and teminate/2 +%% target. The test_server_ctrl process is terminated, and teminate/2 %% will do the cleanup handle_info({'EXIT',Pid,Reason}, State) -> @@ -1139,7 +1169,7 @@ handle_info({'EXIT',Pid,Reason}, State) -> false -> TI = State#state.target_info, case TI#target_info.target_client of - Pid -> + Pid -> %% The target client died - lost contact with target {stop,{lost_contact_with_target,Reason},State}; _other -> @@ -1160,13 +1190,13 @@ handle_info({'EXIT',Pid,Reason}, State) -> State2 = State#state{jobs=NewJobs}, case NewJobs of [] -> - lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, + lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, State2#state.idle_notify), case State2#state.finish of false -> {noreply,State2#state{idle_notify=[]}}; _ -> % true | abort - %% test_server:finish() has been called and + %% test_server:finish() has been called and %% there are no jobs in the job queue => %% stop the test_server_ctrl {stop,shutdown,State2#state{finish=false}} @@ -1174,7 +1204,7 @@ handle_info({'EXIT',Pid,Reason}, State) -> _ -> % pending jobs case State2#state.finish of abort -> % abort test now! - lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, + lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end, State2#state.idle_notify), {stop,shutdown,State2#state{finish=false}}; _ -> % true | false @@ -1194,9 +1224,9 @@ handle_info({tcp,_MainSock,<<1,Request/binary>>}, State) -> case binary_to_term(Request) of {job_proc_killed,Name,Reason} -> %% The only purpose of this is to inform the user about what - %% happened on target. + %% happened on target. %% The local job proc will soon be killed by the closed socket or - %% because the job is finished. Then the above clause ('EXIT') will + %% because the job is finished. Then the above clause ('EXIT') will %% handle the problem. io:format("Suite ~s was killed on remote target with reason" " ~p\n", [Name,Reason]); @@ -1204,13 +1234,13 @@ handle_info({tcp,_MainSock,<<1,Request/binary>>}, State) -> ignore end, {noreply,State}; - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_info({tcp_closed,Sock}, State) %% %% A Socket was closed. This indicates that a node died. -%% This can be +%% This can be %% *Target node (if remote) %% *Slave or peer node started by a test suite %% *Trace controll node @@ -1221,10 +1251,10 @@ handle_info({tcp_closed,Sock}, State=#state{trc=Sock}) -> {noreply,State#state{trc=false}}; handle_info({tcp_closed,Sock}, State) -> case test_server_node:nodedown(Sock,State#state.target_info) of - target_died -> + target_died -> %% terminate/2 will do the cleanup {stop,target_died,State}; - _ -> + _ -> {noreply,State} end; @@ -1260,7 +1290,7 @@ kill_all_jobs([]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, +%% spawn_tester(Mod, Func, Args, Dir, Name, Levels, %% TestCaseCallback, ExtraTools) -> Pid %% Mod = atom() %% Func = atom() @@ -1268,23 +1298,23 @@ kill_all_jobs([]) -> %% Dir = string() %% Name = string() %% Levels = {integer(),integer(),integer()} -%% TestCaseCallback = {CBMod,CBFunc} | undefined +%% TestCaseCallback = {CBMod,CBFunc} | undefined %% ExtraTools = [ExtraTool,...] %% ExtraTool = CoverInfo | TraceInfo | RandomSeed %% %% Spawns a test suite execute-process, just an ordinary spawn, except %% that it will set a lot of dictionary information before starting the %% named function. Also, the execution is timed and protected by a catch. -%% When the named function is done executing, a summary of the results +%% When the named function is done executing, a summary of the results %% is printed to the log files. spawn_tester(Mod, Func, Args, Dir, Name, Levels, TCCallback, ExtraTools) -> spawn_link( - fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels, - TCCallback, ExtraTools) + fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels, + TCCallback, ExtraTools) end). -init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, +init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, TCCallback, ExtraTools) -> process_flag(trap_exit, true), put(test_server_name, Name), @@ -1324,7 +1354,7 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev}, {Skipped,_} -> {Skipped,io_lib:format(", ~p Skipped", [Skipped])} end, OkN = get(test_server_ok), - FailedN = get(test_server_failed), + FailedN = get(test_server_failed), print(html,"TOTAL" "~.3fs~s~p Ok, ~p Failed~s of ~p\n", [Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]). @@ -1338,9 +1368,9 @@ ts_tc(M, F, A) -> {Elapsed,Val}. elapsed_time(Before, After) -> - (element(1,After)*1000000000000 + + (element(1,After)*1000000000000 + element(2,After)*1000000 + element(3,After)) - - (element(1,Before)*1000000000000 + + (element(1,Before)*1000000000000 + element(2,Before)*1000000 + element(3,Before)). start_extra_tools(ExtraTools) -> @@ -1378,28 +1408,32 @@ stop_extra_tools([], _) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% do_spec(SpecName, MultiplyTimetrap) -> {error,Reason} | exit(Result) +%% do_spec(SpecName, TimetrapSpec) -> {error,Reason} | exit(Result) %% SpecName = string() +%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap} %% MultiplyTimetrap = integer() | infinity +%% ScaleTimetrap = bool() %% %% Reads the named test suite specification file, and executes it. %% -%% This function is meant to be called by a process created by +%% This function is meant to be called by a process created by %% spawn_tester/7, which sets up some necessary dictionary values. -do_spec(SpecName, MultiplyTimetrap) when is_list(SpecName) -> +do_spec(SpecName, TimetrapSpec) when is_list(SpecName) -> case file:consult(SpecName) of {ok,TermList} -> - do_spec_list(TermList,MultiplyTimetrap); + do_spec_list(TermList,TimetrapSpec); {error,Reason} -> io:format("Can't open ~s: ~p\n", [SpecName,Reason]), {error,{cant_open_spec,Reason}} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% do_spec_list(TermList) -> exit(Result) +%% do_spec_list(TermList, TimetrapSpec) -> exit(Result) %% TermList = [term()|...] +%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap} %% MultiplyTimetrap = integer() | infinity +%% ScaleTimetrap = bool() %% %% Executes a list of test suite specification commands. The following %% commands are available, and may occur zero or more times (if several, @@ -1422,21 +1456,21 @@ do_spec(SpecName, MultiplyTimetrap) when is_list(SpecName) -> %% nodenames will be generated from the local host. %% %% {hosts, Hosts} Specifies a list of available hosts on which to start -%% slave nodes. It is used when the {remote, true} option is given to the +%% slave nodes. It is used when the {remote, true} option is given to the %% test_server:start_node/3 function. Also, if {require_nodenames, Num} is -%% contained in the TermList, the generated nodenames will be spread over +%% contained in the TermList, the generated nodenames will be spread over %% all hosts given in this Hosts list. The hostnames are given as atoms or %% strings. -%% +%% %% {diskless, true} is kept for backwards compatiblilty and %% should not be used. Use a configuration test case instead. -%% -%% This function is meant to be called by a process created by +%% +%% This function is meant to be called by a process created by %% spawn_tester/7, which sets up some necessary dictionary values. -do_spec_list(TermList0, MultiplyTimetrap) -> +do_spec_list(TermList0, TimetrapSpec) -> Nodes = [], - TermList = + TermList = case lists:keysearch(hosts, 1, TermList0) of {value, {hosts, Hosts0}} -> Hosts = lists:map(fun(H) -> cast_to_list(H) end, Hosts0), @@ -1447,7 +1481,7 @@ do_spec_list(TermList0, MultiplyTimetrap) -> end, DefaultConfig = make_config([{nodes,Nodes}]), {TopCases,SkipList,Config} = do_spec_terms(TermList, [], [], DefaultConfig), - do_test_cases(TopCases, SkipList, Config, MultiplyTimetrap). + do_test_cases(TopCases, SkipList, Config, TimetrapSpec). do_spec_terms([], TopCases, SkipList, Config) -> {TopCases,SkipList,Config}; @@ -1470,21 +1504,21 @@ do_spec_terms([{default_timeout,Tmo}|Terms], TopCases, SkipList, Config) -> do_spec_terms([{require_nodenames,NumNames}|Terms], TopCases, SkipList, Config) -> NodeNames0=generate_nodenames(NumNames), NodeNames=lists:delete([], NodeNames0), - do_spec_terms(Terms, TopCases, SkipList, + do_spec_terms(Terms, TopCases, SkipList, update_config(Config, {nodenames,NodeNames})); do_spec_terms([Other|Terms], TopCases, SkipList, Config) -> io:format("** WARNING: Spec file contains unknown directive ~p\n", [Other]), do_spec_terms(Terms, TopCases, SkipList, Config). - + generate_nodenames(Num) -> Hosts = case controller_call(get_hosts) of - [] -> + [] -> TI = controller_call(get_target_info), [TI#target_info.host]; - List -> + List -> List end, generate_nodenames2(Num, Hosts, []). @@ -1511,7 +1545,7 @@ temp_nodename([Chr|Base], Acc) -> %% NoOfCases = integer() | unknown %% %% Counts the test cases that are about to run and returns that number. -%% If there's a conf group in TestSpec with a repeat property, the total number +%% If there's a conf group in TestSpec with a repeat property, the total number %% of cases can not be calculated and NoOfCases = unknown. count_test_cases(TopCases, SkipCases) when is_list(TopCases) -> case collect_all_cases(TopCases, SkipCases) of @@ -1522,14 +1556,14 @@ count_test_cases(TopCases, SkipCases) when is_list(TopCases) -> case remove_conf(TestSpec) of {repeats,_} -> unknown; - TestSpec1 -> + TestSpec1 -> length(TestSpec1) end} end; count_test_cases(TopCase, SkipCases) -> count_test_cases([TopCase], SkipCases). - + remove_conf(Cases) -> remove_conf(Cases, [], false). @@ -1543,8 +1577,8 @@ remove_conf([{conf, _Ref, Props, _MF}|Cases], NoConf, Repeats) -> end; remove_conf([{make,_Ref,_MF}|Cases], NoConf, Repeats) -> remove_conf(Cases, NoConf, Repeats); -remove_conf([{skip_case,{Type,_Ref,_MF,_Cmt}}|Cases], - NoConf, Repeats) when Type==conf; +remove_conf([{skip_case,{Type,_Ref,_MF,_Cmt}}|Cases], + NoConf, Repeats) when Type==conf; Type==make -> remove_conf(Cases, NoConf, Repeats); remove_conf([C|Cases], NoConf, Repeats) -> @@ -1582,22 +1616,30 @@ add_mod(Mod, Mods) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) -> +%% do_test_cases(TopCases, SkipCases, Config, TimetrapSpec) -> %% exit(Result) %% %% TopCases = term() (See collect_cases/3) %% SkipCases = term() (See collect_cases/3) %% Config = term() (See collect_cases/3) +%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap} %% MultiplyTimetrap = integer() | infinity +%% ScaleTimetrap = bool() %% %% Initializes and starts the test run, for "ordinary" test suites. %% Creates log directories and log files, inserts initial timestamps and %% configuration information into the log files. %% -%% This function is meant to be called by a process created by +%% This function is meant to be called by a process created by %% spawn_tester/7, which sets up some necessary dictionary values. - -do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCases) -> +do_test_cases(TopCases, SkipCases, + Config, MultiplyTimetrap) when is_integer(MultiplyTimetrap); + MultiplyTimetrap == infinity -> + do_test_cases(TopCases, SkipCases, Config, {MultiplyTimetrap,true}); + +do_test_cases(TopCases, SkipCases, + Config, TimetrapData) when is_list(TopCases), + is_tuple(TimetrapData) -> start_log_file(), case collect_all_cases(TopCases, SkipCases) of {error,Why} -> @@ -1607,10 +1649,10 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas N = case remove_conf(TestSpec0) of {repeats,_} -> unknown; TS -> length(TS) - end, + end, put(test_server_cases, N), put(test_server_case_num, 0), - TestSpec = + TestSpec = add_init_and_end_per_suite(TestSpec0, undefined, undefined), TI = get_target_info(), print(1, "Starting test~s", [print_if_known(N, {", ~w test cases",[N]}, @@ -1643,7 +1685,7 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas [TI#target_info.version, TI#target_info.root_dir]); _ -> case test_server_sup:framework_call(target_info, []) of - TargetInfo when is_list(TargetInfo), + TargetInfo when is_list(TargetInfo), length(TargetInfo) > 0 -> print(html, "

Target:
\n"), print(html, "~s\n", [TargetInfo]); @@ -1681,12 +1723,12 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas print(major, "=otp_release ~s", [TI#target_info.otp_release]), print(major, "=started ~s", [lists:flatten(timestamp_get(""))]), - run_test_cases(TestSpec, Config, MultiplyTimetrap) + run_test_cases(TestSpec, Config, TimetrapData) end; -do_test_cases(TopCase, SkipCases, Config, MultiplyTimetrap) -> +do_test_cases(TopCase, SkipCases, Config, TimetrapSpec) -> %% when not list(TopCase) - do_test_cases([TopCase], SkipCases, Config, MultiplyTimetrap). + do_test_cases([TopCase], SkipCases, Config, TimetrapSpec). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1742,7 +1784,7 @@ start_log_file() -> make_html_link(LinkName, Target, Explanation) -> %% if possible use a relative reference to Target. - TargetL = filename:split(Target), + TargetL = filename:split(Target), PwdL = filename:split(filename:dirname(LinkName)), Href = case lists:prefix(PwdL, TargetL) of true -> @@ -1782,7 +1824,7 @@ start_minor_log_file(Mod, Func) -> start_minor_log_file1(Mod, Func, LogDir, AbsName); {ok,_} -> %% special case, duplicate names {_,S,Us} = now(), - Name1_0 = + Name1_0 = lists:flatten(io_lib:format("~s.~s.~w.~w~s", [Mod,Func,S, trunc(Us/1000), ?html_ext])), @@ -1853,7 +1895,7 @@ html_convert_modules(TestSpec, _Config) -> %% Retrieve a list of modules out of the test spec. html_isolate_modules(List) -> html_isolate_modules(List, sets:new()). - + html_isolate_modules([], Set) -> sets:to_list(Set); html_isolate_modules([{skip_case,_}|Cases], Set) -> html_isolate_modules(Cases, Set); @@ -1919,36 +1961,36 @@ add_init_and_end_per_suite([{make,_,_}=Case|Cases], LastMod, LastRef) -> [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)]; add_init_and_end_per_suite([{skip_case,{{Mod,all},_}}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_end_per_suite_and_skip(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([{skip_case,{{Mod,_},_}}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_init_and_end_per_suite(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([{skip_case,{conf,_,{Mod,_},_}}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_init_and_end_per_suite(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([{skip_case,_}=Case|Cases], LastMod, LastRef) -> [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)]; -add_init_and_end_per_suite([{conf,_,_,{Mod,_}}=Case|Cases], LastMod, LastRef) +add_init_and_end_per_suite([{conf,_,_,{Mod,_}}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_init_and_end_per_suite(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([{conf,_,_,_}=Case|Cases], LastMod, LastRef) -> [Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)]; -add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef) +add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_init_and_end_per_suite(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([{Mod,_,_}=Case|Cases], LastMod, LastRef) when Mod =/= LastMod -> - {PreCases, NextMod, NextRef} = + {PreCases, NextMod, NextRef} = do_add_init_and_end_per_suite(LastMod, LastRef, Mod), PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)]; add_init_and_end_per_suite([Case|Cases], LastMod, LastRef)-> @@ -1965,7 +2007,7 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod) -> false -> code:load_file(Mod); _ -> ok end, - {Init,NextMod,NextRef} = + {Init,NextMod,NextRef} = case erlang:function_exported(Mod, init_per_suite, 1) of true -> Ref = make_ref(), @@ -1973,15 +2015,15 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod) -> false -> {[],Mod,undefined} end, - Cases = + Cases = if LastRef==undefined -> Init; LastRef==skipped_suite -> Init; true -> - %% Adding end_per_suite here without checking if the + %% Adding end_per_suite here without checking if the %% function is actually exported. This is because a - %% conf case must have an end case - so if it doesn't + %% conf case must have an end case - so if it doesn't %% exist, it will only fail... [{conf,LastRef,[],{LastMod,end_per_suite}}|Init] end, @@ -1997,12 +2039,12 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_cases(TestSpec, Config, MultiplyTimetrap) -> exit(Result) +%% run_test_cases(TestSpec, Config, TimetrapData) -> exit(Result) %% %% If remote target, a socket connection is established. %% Runs the specified tests, then displays/logs the summary. -run_test_cases(TestSpec, Config, MultiplyTimetrap) -> +run_test_cases(TestSpec, Config, TimetrapData) -> maybe_open_job_sock(), @@ -2010,10 +2052,10 @@ run_test_cases(TestSpec, Config, MultiplyTimetrap) -> %%! For readable tracing... %%! Config1 = [{data_dir,""},{priv_dir,""},{nodes,[]}], - %%! run_test_cases_loop(TestSpec, [[]], MultiplyTimetrap, [], []), + %%! run_test_cases_loop(TestSpec, [[]], TimetrapData, [], []), + + run_test_cases_loop(TestSpec, [Config], TimetrapData, [], []), - run_test_cases_loop(TestSpec, [Config], MultiplyTimetrap, [], []), - maybe_get_privdir(), {AllSkippedN,UserSkipN,AutoSkipN,SkipStr} = @@ -2060,10 +2102,10 @@ maybe_open_job_sock() -> %% tar packet containing the privdir created by the test case. maybe_get_privdir() -> case get(test_server_ctrl_job_sock) of - undefined -> + undefined -> %% local target ok; - Sock -> + Sock -> %% remote target request(Sock, job_done), gen_tcp:close(Sock) @@ -2071,37 +2113,39 @@ maybe_get_privdir() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_cases_loop(TestCases, Config, MultiplyTimetrap, Mode, Status) -> ok +%% run_test_cases_loop(TestCases, Config, TimetrapData, Mode, Status) -> ok %% TestCases = [Test,...] %% Config = [[{Key,Val},...],...] +%% TimetrapData = {MultiplyTimetrap,ScaleTimetrap} %% MultiplyTimetrap = integer() | infinity +%% ScaleTimetrap = bool() %% Mode = [{Ref,[Prop,..],StartTime}] %% Ref = reference() -%% Prop = {name,Name} | sequence | parallel | +%% Prop = {name,Name} | sequence | parallel | %% shuffle | {shuffle,Seed} | -%% repeat | {repeat,N} | +%% repeat | {repeat,N} | %% repeat_until_all_ok | {repeat_until_all_ok,N} | -%% repeat_until_any_ok | {repeat_until_any_ok,N} | -%% repeat_until_any_fail | {repeat_until_any_fail,N} | -%% repeat_until_all_fail | {repeat_until_all_fail,N} +%% repeat_until_any_ok | {repeat_until_any_ok,N} | +%% repeat_until_any_fail | {repeat_until_any_fail,N} | +%% repeat_until_all_fail | {repeat_until_all_fail,N} %% Status = [{Ref,{{Ok,Skipped,Failed},CopiedCases}}] %% Ok = Skipped = Failed = [Case,...] %% %% Execute the TestCases under configuration Config. Config is a list %% of lists, where hd(Config) holds the config tuples for the current -%% conf case and tl(Config) is the data for the higher level conf cases. -%% Config data is "inherited" from top to nested conf cases, but +%% conf case and tl(Config) is the data for the higher level conf cases. +%% Config data is "inherited" from top to nested conf cases, but %% never the other way around. if length(Config) == 1, Config contains %% only the initial config data for the suite. %% %% Test may be one of the following: %% -%% {conf,Ref,Props,{Mod,Func}} Mod:Func is a configuration modification +%% {conf,Ref,Props,{Mod,Func}} Mod:Func is a configuration modification %% function, call it with the current configuration as argument. It will %% return a new configuration. %% -%% {make,Ref,{Mod,Func,Args}} Mod:Func is a make function, and it is called -%% with the given arguments. This function will *always* be called on the host +%% {make,Ref,{Mod,Func,Args}} Mod:Func is a make function, and it is called +%% with the given arguments. This function will *always* be called on the host %% - not on target. %% %% {Mod,Case} This is a normal test case. Determine the correct @@ -2114,16 +2158,16 @@ maybe_get_privdir() -> %% {skip_case,{conf,Ref,Case,Comment}} An init conf case gets skipped %% by the user. This will also cause the end conf case to be skipped. %% Note that it is not possible to skip an end conf case directly (it -%% can only be skipped indirectly by a skipped init conf case). The -%% comment (which gets printed in the log files) describes why the case +%% can only be skipped indirectly by a skipped init conf case). The +%% comment (which gets printed in the log files) describes why the case %% was skipped. %% -%% {skip_case,{Case,Comment}} A normal test case skipped by the user. -%% The comment (which gets printed in the log files) describes why the +%% {skip_case,{Case,Comment}} A normal test case skipped by the user. +%% The comment (which gets printed in the log files) describes why the %% case was skipped. %% %% {auto_skip_case,{conf,Ref,Case,Comment},Mode} This is the result of -%% an end conf case being automatically skipped due to a failing init +%% an end conf case being automatically skipped due to a failing init %% conf case. It could also be a nested conf case that gets skipped %% because of a failed or skipped top level conf. %% @@ -2151,25 +2195,25 @@ maybe_get_privdir() -> %% messages to the main process instead of writing the data to file %% (only true for printouts to common log files). %% -%% If a conf group nested under a parallel group in the test +%% If a conf group nested under a parallel group in the test %% specification should be started, the 'test_server_common_io_handler' %% value gets set also on the main process. This causes all printouts -%% to common files - both from parallel test cases and from cases +%% to common files - both from parallel test cases and from cases %% executed by the main process - to all end up as messages in the -%% inbox of the main process. +%% inbox of the main process. %% %% During execution of a parallel group (or of a group nested under a -%% parallel group), *any* new test case being started gets registered +%% parallel group), *any* new test case being started gets registered %% in a list saved in the dictionary with 'test_server_queued_io' as key. %% When the top level parallel group is finished (only then can we be %% sure all parallel test cases have finished and "reported in"), the -%% list of test cases is traversed in order and printout messages from -%% each process - including the main process - are handled in turn. See +%% list of test cases is traversed in order and printout messages from +%% each process - including the main process - are handled in turn. See %% handle_test_case_io_and_status/0 for details. %% %% To be able to handle nested conf groups with different properties, %% the Mode argument specifies a list of {Ref,Properties} tuples. -%% The head of the Mode list at any given time identifies the group +%% The head of the Mode list at any given time identifies the group %% currently being processed. The tail of the list identifies groups %% on higher level. %% @@ -2179,13 +2223,13 @@ maybe_get_privdir() -> %% %% A group nested under a parallel group will start executing in %% parallel with previous (parallel) test cases (no matter what -%% properties the nested group has). Test cases are however never +%% properties the nested group has). Test cases are however never %% executed in parallel with the start or end conf case of the same %% group! Because of this, the test_server_ctrl loop waits at %% the end conf of a group for all parallel cases to finish %% before the end conf case actually executes. This has the effect %% that it's only after a nested group has finished that any -%% remaining parallel cases in the previous group get spawned (*). +%% remaining parallel cases in the previous group get spawned (*). %% Example (all parallel cases): %% %% group1_init |----> @@ -2201,7 +2245,7 @@ maybe_get_privdir() -> %% run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], - Config, MultiplyTimetrap, Mode, Status) when Type==conf; + Config, TimetrapData, Mode, Status) when Type==conf; Type==make -> file:set_cwd(filename:dirname(get(test_server_dir))), @@ -2217,24 +2261,24 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], set_io_buffering(undefined), {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode), + run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode), delete_status(Ref, Status)); _ -> - %% this is a skipped end conf for a parallel group nested under a + %% this is a skipped end conf for a parallel group nested under a %% parallel group (io buffering is active) wait_for_cases(Ref), {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), case CurrIOHandler of - {Ref,_} -> + {Ref,_} -> %% current_io_handler was set by start conf of this - %% group, so we can unset it now (no more io from main + %% group, so we can unset it now (no more io from main %% process needs to be buffered) set_io_buffering(undefined); - _ -> + _ -> ok end, - run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode), + run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode), delete_status(Ref, Status)) end; {Ref,false} -> @@ -2242,7 +2286,7 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], %% nested under a parallel group {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode), + run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode), delete_status(Ref, Status)); {Ref,_} -> %% this is a skipped end conf for a non-parallel group nested under @@ -2250,22 +2294,22 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), case CurrIOHandler of - {Ref,_} -> + {Ref,_} -> %% current_io_handler was set by start conf of this - %% group, so we can unset it now (no more io from main + %% group, so we can unset it now (no more io from main %% process needs to be buffered) set_io_buffering(undefined); - _ -> + _ -> ok end, - run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode), + run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode), delete_status(Ref, Status)); {_,false} -> %% this is a skipped start conf for a group which is not nested %% under a parallel group {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, [conf(Ref,[])|Mode], Status); + run_test_cases_loop(Cases, Config, TimetrapData, [conf(Ref,[])|Mode], Status); {_,Ref0} when is_reference(Ref0) -> %% this is a skipped start conf for a group nested under a parallel group %% and if this is the first nested group, io buffering must be activated @@ -2276,19 +2320,19 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], end, {Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, [conf(Ref,[])|Mode], Status) - end; + run_test_cases_loop(Cases, Config, TimetrapData, [conf(Ref,[])|Mode], Status) + end; run_test_cases_loop([{auto_skip_case,{Case,Comment},SkipMode}|Cases], - Config, MultiplyTimetrap, Mode, Status) -> + Config, TimetrapData, Mode, Status) -> {Mod,Func} = skip_case(auto, undefined, get(test_server_case_num)+1, Case, Comment, (undefined /= get(test_server_common_io_handler)), SkipMode), test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases, Config, TimetrapData, Mode, update_status(skipped, Mod, Func, Status)); run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0], - Config, MultiplyTimetrap, Mode, Status) -> + Config, TimetrapData, Mode, Status) -> {Mod,Func} = skip_case(user, Ref, 0, Case, Comment, (undefined /= get(test_server_common_io_handler))), {Cases,Config1} = @@ -2301,24 +2345,24 @@ run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0], {skip_cases_upto(Ref, Cases0, Comment, conf, Mode),Config} end, test_server_sup:framework_call(report, [tc_user_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config1, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases, Config1, TimetrapData, Mode, update_status(skipped, Mod, Func, Status)); run_test_cases_loop([{skip_case,{Case,Comment}}|Cases], - Config, MultiplyTimetrap, Mode, Status) -> + Config, TimetrapData, Mode, Status) -> {Mod,Func} = skip_case(user, undefined, get(test_server_case_num)+1, Case, Comment, (undefined /= get(test_server_common_io_handler))), test_server_sup:framework_call(report, [tc_user_skip,{?pl2a(Mod),Func,Comment}]), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases, Config, TimetrapData, Mode, update_status(skipped, Mod, Func, Status)); %% a start *or* end conf case, wrapping test cases or other conf cases -run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, - Config, MultiplyTimetrap, Mode0, Status) -> - +run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, + Config, TimetrapData, Mode0, Status) -> + CurrIOHandler = get(test_server_common_io_handler), %% check and update the mode for test case execution and io msg handling - {StartConf,Mode,IOHandler,ConfTime,Status1} = + {StartConf,Mode,IOHandler,ConfTime,Status1} = case {curr_ref(Mode0),check_props(parallel, Mode0)} of {Ref,Ref} -> case check_props(parallel, tl(Mode0)) of @@ -2334,19 +2378,19 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, {false,tl(Mode0),undefined,Elapsed, update_status(Ref, OkSkipFail, Status)}; _ -> - %% this is an end conf for a parallel group nested under a + %% this is an end conf for a parallel group nested under a %% parallel group (io buffering is active) OkSkipFail = wait_for_cases(Ref), queue_test_case_io(Ref, self(), 0, Mod, Func), Elapsed = elapsed_time(conf_start(Ref, Mode0),?now)/1000000, case CurrIOHandler of - {Ref,_} -> + {Ref,_} -> %% current_io_handler was set by start conf of this %% group, so we can unset it after this case (no %% more io from main process needs to be buffered) {false,tl(Mode0),undefined,Elapsed, update_status(Ref, OkSkipFail, Status)}; - _ -> + _ -> {false,tl(Mode0),CurrIOHandler,Elapsed, update_status(Ref, OkSkipFail, Status)} end @@ -2362,16 +2406,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, queue_test_case_io(Ref, self(), 0, Mod, Func), Elapsed = elapsed_time(conf_start(Ref, Mode0),?now)/1000000, case CurrIOHandler of - {Ref,_} -> + {Ref,_} -> %% current_io_handler was set by start conf of this %% group, so we can unset it after this case (no %% more io from main process needs to be buffered) {false,tl(Mode0),undefined,Elapsed,Status}; - _ -> + _ -> {false,tl(Mode0),CurrIOHandler,Elapsed,Status} end; {_,false} -> - %% this is a start conf for a group which is not nested under a + %% this is a start conf for a group which is not nested under a %% parallel group, check if this case starts a new parallel group case lists:member(parallel, Props) of true -> @@ -2424,9 +2468,9 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, end; NumStr -> %% Ex: "123 456 789" or "123,456,789" -> {123,456,789} - list_to_tuple([list_to_integer(NS) || + list_to_tuple([list_to_integer(NS) || NS <- string:tokens(NumStr, [$ ,$:,$,])]) - end, + end, {shuffle_cases(Ref, Cs0, UseSeed),{shuffle,UseSeed}} end; not StartConf -> @@ -2440,17 +2484,17 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, if StartConf -> case get_repeat(Props) of undefined -> - %% we *must* have a status entry for every conf since we + %% we *must* have a status entry for every conf since we %% will continously update status with test case results %% without knowing the Ref (but update hd(Status)) {false,new_status(Ref, Status1),Cases1,?void_fun}; - _ -> + _ -> {Copied,_} = copy_cases(Ref, make_ref(), Cs1), {true,new_status(Ref, Copied, Status1),Cases1,?void_fun} end; not StartConf -> RepVal = get_repeat(get_props(Mode0)), - ReportStop = + ReportStop = fun() -> print(minor, "~n*** Stopping repeat operation ~w", [RepVal]), print(1, "Stopping repeat operation ~w", [RepVal]) @@ -2465,17 +2509,17 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, {true,EndStatus,CopiedCases++Cases1,?void_fun}; {repeat_until_all_ok,_} -> {RestCs,Fun} = case get_tc_results(Status1) of - {_,_,[]} -> + {_,_,[]} -> {Cases1,ReportStop}; - _ -> + _ -> {CopiedCases++Cases1,?void_fun} end, {true,EndStatus,RestCs,Fun}; {repeat_until_any_ok,_} -> {RestCs,Fun} = case get_tc_results(Status1) of - {Ok,_,_} when length(Ok) > 0 -> + {Ok,_,_} when length(Ok) > 0 -> {Cases1,ReportStop}; - _ -> + _ -> {CopiedCases++Cases1,?void_fun} end, {true,EndStatus,RestCs,Fun}; @@ -2483,15 +2527,15 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, {RestCs,Fun} = case get_tc_results(Status1) of {_,_,Fails} when length(Fails) > 0 -> {Cases1,ReportStop}; - _ -> + _ -> {CopiedCases++Cases1,?void_fun} end, {true,EndStatus,RestCs,Fun}; {repeat_until_all_fail,_} -> {RestCs,Fun} = case get_tc_results(Status1) of - {[],_,_} -> + {[],_,_} -> {Cases1,ReportStop}; - _ -> + _ -> {CopiedCases++Cases1,?void_fun} end, {true,EndStatus,RestCs,Fun} @@ -2517,13 +2561,13 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, [{tc_group_properties,get_props(Mode0)}, {tc_group_result,[{ok,TcOk},{skipped,TcSkip},{failed,TcFail}]}] end, - ActualCfg = + ActualCfg = update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)}, {data_dir,get_data_dir(Mod)}] ++ CfgProps), CurrMode = curr_mode(Ref, Mode0, Mode), - ConfCaseResult = run_test_case(Ref, 0, Mod, Func, [ActualCfg], skip_init, target, - MultiplyTimetrap, CurrMode), + ConfCaseResult = run_test_case(Ref, 0, Mod, Func, [ActualCfg], skip_init, target, + TimetrapData, CurrMode), case ConfCaseResult of {_,NewCfg,_} when Func == init_per_suite, is_list(NewCfg) -> @@ -2533,8 +2577,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, [] -> set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases, [NewCfg|Config], - MultiplyTimetrap, Mode, Status2); + run_test_cases_loop(Cases, [NewCfg|Config], + TimetrapData, Mode, Status2); Bad -> print(minor, "~n*** ~p returned bad elements in Config: ~p.~n", [Func,Bad]), @@ -2542,22 +2586,22 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases2, Config, TimetrapData, Mode, delete_status(Ref, Status2)) - end; + end; {_,NewCfg,_} when StartConf, is_list(NewCfg) -> print_conf_time(ConfTime), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases, [NewCfg|Config], MultiplyTimetrap, Mode, Status2); + run_test_cases_loop(Cases, [NewCfg|Config], TimetrapData, Mode, Status2); {_,{framework_error,{FwMod,FwFunc},Reason},_} -> print(minor, "~n*** ~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]), print(1, "~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]), exit(framework_error); - {_,Fail,_} when element(1,Fail) == 'EXIT'; + {_,Fail,_} when element(1,Fail) == 'EXIT'; element(1,Fail) == timetrap_timeout; element(1,Fail) == failed -> - {Cases2,Config1} = + {Cases2,Config1} = if StartConf -> ReportAbortRepeat(failed), print(minor, "~n*** ~p failed.~n" @@ -2571,7 +2615,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, end, set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config1, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases2, Config1, TimetrapData, Mode, delete_status(Ref, Status2)); {died,Why,_} when Func == init_per_suite -> print(minor, "~n*** Unexpected exit during init_per_suite.~n", []), @@ -2579,16 +2623,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode, - delete_status(Ref, Status2)); + run_test_cases_loop(Cases2, Config, TimetrapData, Mode, + delete_status(Ref, Status2)); {_,{Skip,Reason},_} when StartConf and ((Skip==skip) or (Skip==skipped)) -> ReportAbortRepeat(skipped), print(minor, "~n*** ~p skipped.~n" " Skipping all cases.", [Func]), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), - Config, MultiplyTimetrap, Mode, + run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), + Config, TimetrapData, Mode, delete_status(Ref, Status2)); {_,{skip_and_save,Reason,_SavedConfig},_} when StartConf -> ReportAbortRepeat(skipped), @@ -2596,8 +2640,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, " Skipping all cases.", [Func]), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), - Config, MultiplyTimetrap, Mode, + run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), + Config, TimetrapData, Mode, delete_status(Ref, Status2)); {_,_Other,_} when Func == init_per_suite -> print(minor, "~n*** init_per_suite failed to return a Config list.~n", []), @@ -2605,16 +2649,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode, + run_test_cases_loop(Cases2, Config, TimetrapData, Mode, delete_status(Ref, Status2)); {_,_Other,_} when StartConf -> print_conf_time(ConfTime), set_io_buffering(IOHandler), ReportRepeatStop(), stop_minor_log_file(), - run_test_cases_loop(Cases, [hd(Config)|Config], MultiplyTimetrap, + run_test_cases_loop(Cases, [hd(Config)|Config], TimetrapData, Mode, Status2); - + {_,_EndConfRetVal,Opts} -> %% check if return_group_result is set (ok, skipped or failed) and %% if so return the value to the group "above" so that result may be @@ -2631,35 +2675,35 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, ReportRepeatStop(), set_io_buffering(IOHandler), stop_minor_log_file(), - run_test_cases_loop(Cases, tl(Config), MultiplyTimetrap, Mode, Status3) + run_test_cases_loop(Cases, tl(Config), TimetrapData, Mode, Status3) end; -run_test_cases_loop([{make,Ref,{Mod,Func,Args}}|Cases0], Config, MultiplyTimetrap, Mode, Status) -> - case run_test_case(Ref, 0, Mod, Func, Args, skip_init, host, MultiplyTimetrap) of +run_test_cases_loop([{make,Ref,{Mod,Func,Args}}|Cases0], Config, TimetrapData, Mode, Status) -> + case run_test_case(Ref, 0, Mod, Func, Args, skip_init, host, TimetrapData) of {_,Why={'EXIT',_},_} -> print(minor, "~n*** ~p failed.~n" " Skipping all cases.", [Func]), Reason = {failed,{Mod,Func,Why}}, Cases = skip_cases_upto(Ref, Cases0, Reason, conf, Mode), stop_minor_log_file(), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status); + run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status); {_,_Whatever,_} -> stop_minor_log_file(), - run_test_cases_loop(Cases0, Config, MultiplyTimetrap, Mode, Status) + run_test_cases_loop(Cases0, Config, TimetrapData, Mode, Status) end; -run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0], - Config, _MultiplyTimetrap, _Mode, _Status) -> +run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0], + Config, _TimetrapData, _Mode, _Status) -> erlang:error(badarg, [Conf,Config]); -run_test_cases_loop([{Mod,Case}|Cases], Config, MultiplyTimetrap, Mode, Status) -> - ActualCfg = +run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) -> + ActualCfg = update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)}, {data_dir,get_data_dir(Mod)}]), run_test_cases_loop([{Mod,Case,[ActualCfg]}|Cases], Config, - MultiplyTimetrap, Mode, Status); + TimetrapData, Mode, Status); -run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Status) -> +run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) -> Num = put(test_server_case_num, get(test_server_case_num)+1), %% check the current execution mode and save info about the case if %% detected that printouts to common log files is handled later @@ -2669,15 +2713,15 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Sta undefined -> %% io printouts are written to straight to file ok; - _ -> + _ -> %% io messages are buffered, put test case in queue queue_test_case_io(undefined, self(), Num+1, Mod, Func) end; _ -> ok end, - case run_test_case(undefined, Num+1, Mod, Func, Args, - run_init, target, MultiplyTimetrap, Mode) of + case run_test_case(undefined, Num+1, Mod, Func, Args, + run_init, target, TimetrapData, Mode) of %% callback to framework module failed, exit immediately {_,{framework_error,{FwMod,FwFunc},Reason},_} -> print(minor, "~n*** ~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]), @@ -2688,50 +2732,50 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Sta {Time,RetVal,_} -> {Failed,Status1} = case Time of - died -> + died -> {true,update_status(failed, Mod, Func, Status)}; _ when is_tuple(RetVal) -> case element(1, RetVal) of - R when R=='EXIT'; R==failed -> + R when R=='EXIT'; R==failed -> {true,update_status(failed, Mod, Func, Status)}; - R when R==skip; R==skipped -> + R when R==skip; R==skipped -> {false,update_status(skipped, Mod, Func, Status)}; - _ -> + _ -> {false,update_status(ok, Mod, Func, Status)} end; - _ -> + _ -> {false,update_status(ok, Mod, Func, Status)} - end, + end, case check_prop(sequence, Mode) of false -> stop_minor_log_file(), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status1); - Ref -> - %% the case is in a sequence; we must check the result and + run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1); + Ref -> + %% the case is in a sequence; we must check the result and %% determine if the following cases should run or be skipped if not Failed -> % proceed with next case stop_minor_log_file(), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status1); + run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1); true -> % skip rest of cases in sequence print(minor, "~n*** ~p failed.~n" " Skipping all other cases in sequence.", [Func]), Reason = {failed,{Mod,Func}}, Cases2 = skip_cases_upto(Ref, Cases, Reason, tc, Mode), stop_minor_log_file(), - run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode, Status1) + run_test_cases_loop(Cases2, Config, TimetrapData, Mode, Status1) end end; %% the test case is being executed in parallel with the main process (and %% other test cases) and Pid is the dedicated process executing the case Pid -> - %% io from Pid will be buffered in the main process inbox and handled + %% io from Pid will be buffered in the main process inbox and handled %% later, so we have to save info about the case queue_test_case_io(undefined, Pid, Num+1, Mod, Func), - run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status) + run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status) end; %% TestSpec processing finished -run_test_cases_loop([], _Config, _MultiplyTimetrap, _, _) -> +run_test_cases_loop([], _Config, _TimetrapData, _, _) -> ok. %%-------------------------------------------------------------------- @@ -2798,12 +2842,12 @@ check_props(Attrib, Mode) -> case [R || {R,Ps,_} <- Mode, lists:member(Attrib, Ps)] of [] -> false; [Ref|_] -> Ref - end. + end. get_name([{_Ref,Props,_}|_]) -> proplists:get_value(name, Props); get_name([]) -> - undefined. + undefined. conf_start(Ref, Mode) -> case lists:keysearch(Ref, 1, Mode) of @@ -2826,10 +2870,10 @@ print_conf_time(0) -> print_conf_time(ConfTime) -> print(major, "=group_time ~.3fs", [ConfTime]), print(minor, "~n=== Total execution time of group: ~.3fs~n", [ConfTime]). - -print_props(_, []) -> + +print_props(_, []) -> ok; -print_props(true, Props) -> +print_props(true, Props) -> print(major, "=group_props ~p", [Props]), print(minor, "Group properties: ~p~n", [Props]); print_props(_, _) -> @@ -2858,7 +2902,7 @@ update_repeat(Props) -> N >= 2 -> [{RepType,N-1}|lists:keydelete(RepType, 1, Props)] end, - %% if shuffle is used in combination with repeat, a new + %% if shuffle is used in combination with repeat, a new %% seed shouldn't be set every new turn case get_shuffle(Props1) of undefined -> @@ -2874,13 +2918,13 @@ get_shuffle(Props) -> delete_shuffle(Props) -> delete_prop([shuffle], Props). -%% Return {Item,Value} if found, else if Item alone +%% Return {Item,Value} if found, else if Item alone %% is found, return {Item,Default} get_prop([Item|Items], Default, Props) -> case lists:keysearch(Item, 1, Props) of - {value,R} -> + {value,R} -> R; - false -> + false -> case lists:member(Item, Props) of true -> {Item,Default}; @@ -2940,8 +2984,8 @@ random_order(1, {_Pos,Seed}, [{_Ix,CaseOrGroup}], Shuffled) -> put(test_server_curr_random_seed, Seed), Shuffled++CaseOrGroup; random_order(N, {Pos,NewSeed}, IxCases, Shuffled) -> - {First,[{_Ix,CaseOrGroup}|Rest]} = lists:split(Pos-1, IxCases), - random_order(N-1, random:uniform_s(N-1, NewSeed), + {First,[{_Ix,CaseOrGroup}|Rest]} = lists:split(Pos-1, IxCases), + random_order(N-1, random:uniform_s(N-1, NewSeed), First++Rest, Shuffled++CaseOrGroup). @@ -2949,7 +2993,7 @@ random_order(N, {Pos,NewSeed}, IxCases, Shuffled) -> %% skip_case(Type, Ref, CaseNum, Case, Comment, SendSync) -> {Mod,Func} %% %% Prints info about a skipped case in the major and html log files. -%% SendSync determines if start and finished messages must be sent so +%% SendSync determines if start and finished messages must be sent so %% that the printouts can be buffered and handled in order with io from %% parallel processes. @@ -2969,13 +3013,13 @@ skip_case(Type, Ref, CaseNum, Case, Comment, SendSync, Mode) -> not SendSync -> skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) end, - MF. + MF. skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) -> {{Col0,Col1},_} = get_font_style((CaseNum > 0), Mode), ResultCol = if Type == auto -> "#ffcc99"; Type == user -> "#ff9933" - end, + end, Comment1 = reason_to_string(Comment), @@ -3084,7 +3128,7 @@ modify_cases_upto1(Ref, ModOp, [{skip_case,{_F,_Cmt}}=MF|T], Orig, Alt) -> %% next is a normal case (possibly in a sequence), mark as skipped, or copy, and proceed modify_cases_upto1(Ref, {skip,Reason,_,Mode}=Op, [{_M,_F}=MF|T], Orig, Alt) -> - modify_cases_upto1(Ref, Op, T, Orig, [{auto_skip_case,{MF,Reason},Mode}|Alt]); + modify_cases_upto1(Ref, Op, T, Orig, [{auto_skip_case,{MF,Reason},Mode}|Alt]); modify_cases_upto1(Ref, CopyOp, [{_M,_F}=MF|T], Orig, Alt) -> modify_cases_upto1(Ref, CopyOp, T, [MF|Orig], [MF|Alt]); @@ -3110,7 +3154,7 @@ set_io_buffering(IOHandler) -> %% queue_test_case_io(Pid, Num, Mod, Func) -> ok %% %% Save info about test case that gets its io buffered. This can -%% be a parallel test case or it can be a test case (conf or normal) +%% be a parallel test case or it can be a test case (conf or normal) %% that belongs to a group nested under a parallel group. The queue %% is processed after io buffering is disabled. See run_test_cases_loop/4 %% and handle_test_case_io_and_status/0 for more info. @@ -3124,10 +3168,10 @@ queue_test_case_io(Ref, Pid, Num, Mod, Func) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% wait_for_cases(Ref) -> {Ok,Skipped,Failed} %% -%% At the end of a nested parallel group, we have to wait for the test +%% At the end of a nested parallel group, we have to wait for the test %% cases to terminate before we can go on (since test cases never execute -%% in parallel with the end conf case of the group). When a top level -%% parallel group is finished, buffered io messages must be handled and +%% in parallel with the end conf case of the group). When a top level +%% parallel group is finished, buffered io messages must be handled and %% this is taken care of by handle_test_case_io_and_status/0. wait_for_cases(Ref) -> @@ -3135,15 +3179,15 @@ wait_for_cases(Ref) -> [] -> {[],[],[]}; Cases -> - [_Start|TCs] = + [_Start|TCs] = lists:dropwhile(fun({R,_,_,_,_}) when R == Ref -> false; (_) -> true end, Cases), wait_and_resend(Ref, TCs, [],[],[]) end. -wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps], - Ok,Skip,Fail) when is_reference(OtherRef), +wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps], + Ok,Skip,Fail) when is_reference(OtherRef), OtherRef /= Ref -> %% ignore cases that belong to nested group Ps1 = rm_cases_upto(OtherRef, Ps), @@ -3152,7 +3196,7 @@ wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps], wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> receive {finished,_Ref,CurrPid,CaseNum,Mod,Func,Result,_RetVal} = Msg -> - %% resend message to main process so that it can be used + %% resend message to main process so that it can be used %% to handle buffered io messages later self() ! Msg, MF = {Mod,Func}, @@ -3163,7 +3207,7 @@ wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> failed -> {Ok,Skip,[MF|Fail]} end, wait_and_resend(Ref, Ps, Ok1,Skip1,Fail1); - {'EXIT',CurrPid,Reason} when Reason /= normal -> + {'EXIT',CurrPid,Reason} when Reason /= normal -> %% unexpected termination of test case process {value,{_,_,CaseNum,Mod,Func}} = lists:keysearch(CurrPid, 2, Cases), print(1, "Error! Process for test case #~p (~p:~p) died! Reason: ~p", @@ -3186,17 +3230,17 @@ rm_cases_upto(Ref, [_|Ps]) -> %% execution. The common log files (major, html etc) must however be %% written to sequentially. The test case processes send print requests %% to the main (starting) process (the same process executing -%% run_test_cases_loop/4), which handles these requests in the same +%% run_test_cases_loop/4), which handles these requests in the same %% order that the test case processes were started. %% %% An io session is always started with a {started,Ref,Pid,Num,Mod,Func} %% message and terminated with {finished,Ref,Pid,Num,Mod,Func,Result,RetVal}. %% The result shipped with the finished message from a parallel process -%% is used to update status data of the current test run. An 'EXIT' -%% message from each parallel test case process (after finishing and +%% is used to update status data of the current test run. An 'EXIT' +%% message from each parallel test case process (after finishing and %% terminating) is also received and handled here. %% -%% During execution of a parallel group, any cases (conf or normal) +%% During execution of a parallel group, any cases (conf or normal) %% belonging to a nested group will also get its io printouts buffered. %% This is necessary to get the major and html log files written in %% correct sequence. This function handles also the print messages @@ -3207,7 +3251,7 @@ rm_cases_upto(Ref, [_|Ps]) -> %% See the header comment for run_test_cases_loop/4 for more %% info about IO handling. %% -%% Note: It is important that the type of messages handled here +%% Note: It is important that the type of messages handled here %% do not get consumated by test_server:run_test_case_msgloop/5 %% during the test case execution (e.g. in the catch clause of %% the receive)! @@ -3231,7 +3275,7 @@ handle_test_case_io_and_status() -> ok end, Cases), Result - end. + end. %% Handle cases (without Ref) that belong to the top parallel group (i.e. when Refs = []) handle_io_and_exit_loop([], [{undefined,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> @@ -3249,7 +3293,7 @@ handle_io_and_exit_loop([], [{undefined,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, O 1000 -> exit({testcase_failed_to_start,Mod,Func}) end; - + %% Handle cases that belong to groups nested under top parallel group handle_io_and_exit_loop(Refs, [{Ref,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) -> receive @@ -3269,7 +3313,7 @@ handle_io_and_exit_loop(Refs, [{Ref,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Sk 1000 -> exit({testcase_failed_to_start,Mod,Func}) end; - + handle_io_and_exit_loop(_, [], Ok,Skip,Fail) -> {lists:reverse(Ok),lists:reverse(Skip),lists:reverse(Fail)}. @@ -3286,7 +3330,7 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> failed -> put(test_server_failed, get(test_server_failed)+1); skipped -> - SkipCounters = + SkipCounters = update_skip_counters(RetVal, get(test_server_skipped)), put(test_server_skipped, SkipCounters) end, @@ -3298,7 +3342,7 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases); %% unexpected termination of test case process - {'EXIT',TCPid,Reason} when Reason /= normal -> + {'EXIT',TCPid,Reason} when Reason /= normal -> {value,{_,_,Num,M,F}} = lists:keysearch(TCPid, 2, Cases), print(1, "Error! Process for test case #~p (~p:~p) died! Reason: ~p", [Num, M, F, Reason]), @@ -3307,65 +3351,65 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_case(Ref, Num, Mod, Func, Args, RunInit, -%% Where, MultiplyTimetrap, Mode) -> RetVal +%% run_test_case(Ref, Num, Mod, Func, Args, RunInit, +%% Where, TimetrapData, Mode) -> RetVal %% %% Creates the minor log file and inserts some test case specific headers -%% and footers into the log files. If a remote target is used, the test +%% and footers into the log files. If a remote target is used, the test %% suite (binary) and the content of data_dir is sent. Then the test case -%% is executed and the result is printed to the log files (also info +%% is executed and the result is printed to the log files (also info %% about lingering processes & slave nodes in the system is presented). -%% +%% %% RunInit decides if the per test case init is to be run (true for all %% but conf cases). %% -%% Where specifies if the test case should run on target or on the host. +%% Where specifies if the test case should run on target or on the host. %% (Note that 'make' test cases always run on host). -%% +%% %% Mode specifies if the test case should be executed by a dedicated, %% parallel, process rather than sequentially by the main process. If %% the former, the new process is spawned and the dictionary of the main %% process is copied to the test case process. -%% -%% RetVal is the result of executing the test case. It contains info +%% +%% RetVal is the result of executing the test case. It contains info %% about the execution time and the return value of the test case function. -run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, MultiplyTimetrap) -> +run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData) -> file:set_cwd(filename:dirname(get(test_server_dir))), - run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - MultiplyTimetrap, [], [], self()). + run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, + TimetrapData, [], [], self()). -run_test_case(Ref, Num, Mod, Func, Args, skip_init, Where, MultiplyTimetrap, Mode) -> +run_test_case(Ref, Num, Mod, Func, Args, skip_init, Where, TimetrapData, Mode) -> %% a conf case is always executed by the main process - run_test_case1(Ref, Num, Mod, Func, Args, skip_init, Where, - MultiplyTimetrap, [], Mode, self()); + run_test_case1(Ref, Num, Mod, Func, Args, skip_init, Where, + TimetrapData, [], Mode, self()); -run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, MultiplyTimetrap, Mode) -> +run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData, Mode) -> file:set_cwd(filename:dirname(get(test_server_dir))), case check_prop(parallel, Mode) of false -> %% this is a sequential test case - run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - MultiplyTimetrap, [], Mode, self()); + run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, + TimetrapData, [], Mode, self()); _Ref -> %% this a parallel test case, spawn the new process Main = self(), - {dictionary,State} = process_info(self(), dictionary), + {dictionary,State} = process_info(self(), dictionary), spawn_link(fun() -> - run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - MultiplyTimetrap, State, Mode, Main) - end) + run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, + TimetrapData, State, Mode, Main) + end) end. -run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, - MultiplyTimetrap, State, Mode, Main) -> - %% if this runs on a parallel test case process, +run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, + TimetrapData, State, Mode, Main) -> + %% if this runs on a parallel test case process, %% copy the dictionary from the main process do_if_parallel(Main, fun() -> process_flag(trap_exit, true) end, ok), CopyDict = fun() -> lists:foreach(fun({Key,Val}) -> put(Key, Val) end, State) end, do_if_parallel(Main, CopyDict, ok), do_if_parallel(Main, fun() -> put(test_server_common_io_handler, {tc,Main}) end, ok), - %% if io is being buffered, send start io session message + %% if io is being buffered, send start io session message %% (no matter if case runs on parallel or main process) case get(test_server_common_io_handler) of undefined -> ok; @@ -3373,7 +3417,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, end, TSDir = get(test_server_dir), case Where of - target -> + target -> maybe_send_beam_and_datadir(Mod); host -> ok @@ -3396,8 +3440,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, do_if_parallel(Main, ok, fun erlang:yield/0), %% run the test case {Result,DetectedFail,ProcsBefore,ProcsAfter} = - run_test_case_apply(Num, Mod, Func, Args, get_name(Mode), - RunInit, Where, MultiplyTimetrap), + run_test_case_apply(Num, Mod, Func, Args, get_name(Mode), + RunInit, Where, TimetrapData), {Time,RetVal,Loc,Opts,Comment} = case Result of Normal={_Time,_RetVal,_Loc,_Opts,_Comment} -> Normal; @@ -3409,7 +3453,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]), do_if_parallel(Main, ok, fun() -> file:set_cwd(filename:dirname(TSDir)) end), - + %% call the appropriate progress function clause to print the results to log Status = case {Time,RetVal} of @@ -3423,16 +3467,16 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, progress(skip, Num, Mod, Func, Loc, Reason, Time, Comment, Style); {_,{'EXIT',_Pid,{Skip,Reason}}} when Skip==skip; Skip==skipped -> - progress(skip, Num, Mod, Func, Loc, Reason, + progress(skip, Num, Mod, Func, Loc, Reason, Time, Comment, Style); {_,{'EXIT',_Pid,Reason}} -> - progress(failed, Num, Mod, Func, Loc, Reason, + progress(failed, Num, Mod, Func, Loc, Reason, Time, Comment, Style); {_,{'EXIT',Reason}} -> - progress(failed, Num, Mod, Func, Loc, Reason, + progress(failed, Num, Mod, Func, Loc, Reason, Time, Comment, Style); {_, {failed, Reason}} -> - progress(failed, Num, Mod, Func, Loc, Reason, + progress(failed, Num, Mod, Func, Loc, Reason, Time, Comment, Style); {_, {Skip, Reason}} when Skip==skip; Skip==skipped -> progress(skip, Num, Mod, Func, Loc, Reason, @@ -3442,7 +3486,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, [] -> progress(ok, Num, Mod, Func, Loc, RetVal, Time, Comment, Style); - + Reason -> progress(failed, Num, Mod, Func, Loc, Reason, Time, Comment, Style) @@ -3465,18 +3509,18 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, {US,AS} = get(test_server_skipped), put(test_server_skipped, {US,AS+1}) end, - %% only if test case execution is sequential do we care about the + %% only if test case execution is sequential do we care about the %% remaining processes and slave nodes count case self() of Main -> case test_server_sup:framework_call(warn, [processes], true) of true -> if ProcsBefore < ProcsAfter -> - print(minor, + print(minor, "WARNING: ~w more processes in system after test case", [ProcsAfter-ProcsBefore]); ProcsBefore > ProcsAfter -> - print(minor, + print(minor, "WARNING: ~w less processes in system after test case", [ProcsBefore-ProcsAfter]); true -> ok @@ -3493,7 +3537,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, " system. I tried to kill them, but I failed: ~p\n", [Exit]); [] -> ok; - List -> + List -> print(minor, "WARNING: ~w slave nodes in system after test"++ "case. Tried to killed them.~n"++ " Names:~p", @@ -3505,8 +3549,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, _ -> ok end, - %% if the test case was executed sequentially, this updates the execution - %% time count on the main process (adding execution time of parallel test + %% if the test case was executed sequentially, this updates the execution + %% time count on the main process (adding execution time of parallel test %% case groups is done in run_test_cases_loop/4) if is_number(Time) -> put(test_server_total_time, get(test_server_total_time)+Time); @@ -3515,7 +3559,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, end, check_new_crash_dumps(Where), - %% if io is being buffered, send finished message + %% if io is being buffered, send finished message %% (no matter if case runs on parallel or main process) case get(test_server_common_io_handler) of undefined -> ok; @@ -3528,7 +3572,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where, %%-------------------------------------------------------------------- %% various help functions -%% Call If() if we're on parallel process, or +%% Call If() if we're on parallel process, or %% call Else() if we're on main process do_if_parallel(Pid, If, Else) -> case self() of @@ -3536,7 +3580,7 @@ do_if_parallel(Pid, If, Else) -> if is_function(Else) -> Else(); true -> Else end; - _ -> + _ -> if is_function(If) -> If(); true -> If end @@ -3549,13 +3593,13 @@ num2str(N) -> integer_to_list(N). %% and the content of datadir til target. maybe_send_beam_and_datadir(Mod) -> case get(test_server_ctrl_job_sock) of - undefined -> + undefined -> %% local target ok; JobSock -> %% remote target case get(test_server_downloaded_suites) of - undefined -> + undefined -> send_beam_and_datadir(Mod, JobSock), put(test_server_downloaded_suites, [Mod]); Suites -> @@ -3571,10 +3615,10 @@ maybe_send_beam_and_datadir(Mod) -> send_beam_and_datadir(Mod, JobSock) -> case code:which(Mod) of - non_existing -> + non_existing -> io:format("** WARNING: Suite ~w could not be found on host\n", [Mod]); - BeamFile -> + BeamFile -> send_beam(JobSock, Mod, BeamFile) end, DataDir = get_data_dir(Mod), @@ -3589,7 +3633,7 @@ send_beam_and_datadir(Mod, JobSock) -> ModsInDatadir = filelib:wildcard(Wc), SendBeamFun = fun(X) -> send_beam(JobSock, X) end, lists:foreach(SendBeamFun, ModsInDatadir), - %% No need to send C code or makefiles since + %% No need to send C code or makefiles since %% no compilation can be done on target anyway. %% Compiled C code must exist on target. %% Beam files are already sent as binaries. @@ -3597,7 +3641,7 @@ send_beam_and_datadir(Mod, JobSock) -> %% is to compile it. Filter = fun("Makefile") -> false; ("Makefile.src") -> false; - (Y) -> + (Y) -> case filename:extension(Y) of ".c" -> false; ObjExt -> false; @@ -3611,7 +3655,7 @@ send_beam_and_datadir(Mod, JobSock) -> Tarfile = "data_dir.tar.gz", {ok,Tar} = erl_tar:open(Tarfile, [write,compressed]), ShortDataDir = filename:basename(DataDir), - AddTarFun = + AddTarFun = fun(File) -> Long = filename:join(DataDir, File), Short = filename:join(ShortDataDir, File), @@ -3628,11 +3672,11 @@ send_beam_and_datadir(Mod, JobSock) -> send_beam(JobSock, BeamFile) -> Mod=filename:rootname(filename:basename(BeamFile), code:objfile_extension()), - send_beam(JobSock, list_to_atom(Mod), BeamFile). + send_beam(JobSock, list_to_atom(Mod), BeamFile). send_beam(JobSock, Mod, BeamFile) -> {ok,BeamBin} = file:read_file(BeamFile), request(JobSock, {{beam,Mod,BeamFile}, BeamBin}). - + check_new_crash_dumps(Where) -> case Where of target -> @@ -3649,25 +3693,25 @@ check_new_crash_dumps(Where) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% progress(Result, CaseNum, Mod, Func, Location, Reason, Time, +%% progress(Result, CaseNum, Mod, Func, Location, Reason, Time, %% Comment, TimeFormat) -> Result %% %% Prints the result of the test case to log file. %% Note: Strings that are to be written to the minor log must %% be prefixed with "=== " here, or the indentation will be wrong. -progress(skip, CaseNum, Mod, Func, Loc, Reason, Time, +progress(skip, CaseNum, Mod, Func, Loc, Reason, Time, Comment, {St0,St1}) -> - {Reason1,{Color,Ret}} = if_auto_skip(Reason, + {Reason1,{Color,Ret}} = if_auto_skip(Reason, fun() -> {"#ffcc99",auto_skip} end, fun() -> {"#ff9933",skip} end), print(major, "=result skipped", []), - print(1, "*** SKIPPED *** ~s", + print(1, "*** SKIPPED *** ~s", [get_info_str(Func, CaseNum, get(test_server_cases))]), test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func, {skipped,Reason1}}]), ReasonStr = reason_to_string(Reason1), - ReasonStr1 = lists:flatten([string:strip(S,left) || + ReasonStr1 = lists:flatten([string:strip(S,left) || S <- string:tokens(ReasonStr,[$\n])]), ReasonStr2 = if length(ReasonStr1) > 80 -> @@ -3686,10 +3730,10 @@ progress(skip, CaseNum, Mod, Func, Loc, Reason, Time, [Time,Color,ReasonStr2,Comment1]), FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== location ~s", [FormatLoc]), - print(minor, "=== reason = ~s", [ReasonStr1]), + print(minor, "=== reason = ~s", [ReasonStr1]), Ret; - -progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T, + +progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T, Comment0, {St0,St1}) -> print(major, "=result failed: timeout, ~p", [Loc]), print(1, "*** FAILED *** ~s", @@ -3699,23 +3743,23 @@ progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T, {failed,timetrap_timeout}}]), FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)), ErrorReason = io_lib:format("{timetrap_timeout,~s}", [FormatLastLoc]), - Comment = + Comment = case Comment0 of "" -> "" ++ ErrorReason ++ ""; - _ -> "" ++ ErrorReason ++ "
" ++ + _ -> "" ++ ErrorReason ++ "
" ++ to_string(Comment0) end, - print(html, + print(html, "" ++ St0 ++ "~.3fs" ++ St1 ++ "" "FAILED" - "~s\n", + "~s\n", [T/1000,Comment]), FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== location ~s", [FormatLoc]), print(minor, "=== reason = timetrap timeout", []), failed; -progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T, +progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T, Comment0, {St0,St1}) -> print(major, "=result failed: testcase_aborted, ~p", [Loc]), print(1, "*** FAILED *** ~s", @@ -3725,23 +3769,23 @@ progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T, {failed,testcase_aborted}}]), FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)), ErrorReason = io_lib:format("{testcase_aborted,~s}", [FormatLastLoc]), - Comment = + Comment = case Comment0 of "" -> "" ++ ErrorReason ++ ""; - _ -> "" ++ ErrorReason ++ "
" ++ + _ -> "" ++ ErrorReason ++ "
" ++ to_string(Comment0) end, - print(html, + print(html, "" ++ St0 ++ "died" ++ St1 ++ "" "FAILED" - "~s\n", + "~s\n", [Comment]), FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== location ~s", [FormatLoc]), print(minor, "=== reason = {testcase_aborted,~p}", [Reason]), failed; -progress(failed, CaseNum, Mod, Func, unknown, Reason, Time, +progress(failed, CaseNum, Mod, Func, unknown, Reason, Time, Comment0, {St0,St1}) -> print(major, "=result failed: ~p, ~p", [Reason,unknown]), print(1, "*** FAILED *** ~s", @@ -3749,10 +3793,10 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time, test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func, {failed,Reason}}]), TimeStr = io_lib:format(if is_float(Time) -> "~.3fs"; - true -> "~w" + true -> "~w" end, [Time]), ErrorReason = lists:flatten(io_lib:format("~p", [Reason])), - ErrorReason1 = lists:flatten([string:strip(S,left) || + ErrorReason1 = lists:flatten([string:strip(S,left) || S <- string:tokens(ErrorReason,[$\n])]), ErrorReason2 = if length(ErrorReason1) > 63 -> @@ -3760,13 +3804,13 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time, true -> ErrorReason1 end, - Comment = + Comment = case Comment0 of "" -> "" ++ ErrorReason2 ++ ""; - _ -> "" ++ ErrorReason2 ++ "
" ++ + _ -> "" ++ ErrorReason2 ++ "
" ++ to_string(Comment0) end, - print(html, + print(html, "" ++ St0 ++ "~s" ++ St1 ++ "" "FAILED" "~s\n", @@ -3776,7 +3820,7 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time, print(minor, "=== reason = "++FStr, [FormattedReason]), failed; -progress(failed, CaseNum, Mod, Func, Loc, Reason, Time, +progress(failed, CaseNum, Mod, Func, Loc, Reason, Time, Comment0, {St0,St1}) -> print(major, "=result failed: ~p, ~p", [Reason,Loc]), print(1, "*** FAILED *** ~s", @@ -3784,18 +3828,18 @@ progress(failed, CaseNum, Mod, Func, Loc, Reason, Time, test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func, {failed,Reason}}]), TimeStr = io_lib:format(if is_float(Time) -> "~.3fs"; - true -> "~w" + true -> "~w" end, [Time]), - Comment = + Comment = case Comment0 of "" -> ""; _ -> "
" ++ to_string(Comment0) end, FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)), - print(html, + print(html, "" ++ St0 ++ "~s" ++ St1 ++ "" "FAILED" - "~s~s\n", + "~s~s\n", [TimeStr,FormatLastLoc,Comment]), FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== location ~s", [FormatLoc]), @@ -3803,7 +3847,7 @@ progress(failed, CaseNum, Mod, Func, Loc, Reason, Time, print(minor, "=== reason = "++FStr, [FormattedReason]), failed; -progress(ok, _CaseNum, Mod, Func, _Loc, RetVal, Time, +progress(ok, _CaseNum, Mod, Func, _Loc, RetVal, Time, Comment0, {St0,St1}) -> print(minor, "successfully completed test case", []), test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func,ok}]), @@ -3852,9 +3896,9 @@ get_info_str(Func, 0, _Cases) -> get_info_str(_Func, CaseNum, unknown) -> "test case " ++ integer_to_list(CaseNum); get_info_str(_Func, CaseNum, Cases) -> - "test case " ++ integer_to_list(CaseNum) ++ + "test case " ++ integer_to_list(CaseNum) ++ " of " ++ integer_to_list(Cases). - + print_if_known(Known, {SK,AK}, {SU,AU}) -> {S,A} = if Known == unknown -> {SU,AU}; true -> {SK,AK} @@ -3880,7 +3924,7 @@ reason_to_string({failed,{_,FailFunc,bad_return}}) -> atom_to_list(FailFunc) ++ " bad return value"; reason_to_string({failed,{_,FailFunc,{timetrap_timeout,_}}}) -> atom_to_list(FailFunc) ++ " timed out"; -reason_to_string(FWInitFail = {failed,{_CB,init_tc,_Reason}}) -> +reason_to_string(FWInitFail = {failed,{_CB,init_tc,_Reason}}) -> to_string(FWInitFail); reason_to_string({failed,{_,FailFunc,_}}) -> atom_to_list(FailFunc) ++ " failed"; @@ -3889,29 +3933,29 @@ reason_to_string(Other) -> %get_font_style(Prop) -> % {Col,St0,St1} = get_font_style1(Prop), -% {{"",""}, +% {{"",""}, % {""++St0,St1++""}}. - + get_font_style(NormalCase, Mode) -> - Prop = if not NormalCase -> + Prop = if not NormalCase -> default; true -> case check_prop(parallel, Mode) of - false -> + false -> case check_prop(sequence, Mode) of - false -> + false -> default; - _ -> + _ -> sequence end; - _ -> + _ -> parallel end end, {Col,St0,St1} = get_font_style1(Prop), - {{"",""}, + {{"",""}, {""++St0,St1++""}}. - + get_font_style1(parallel) -> {"\"darkslategray\"","",""}; get_font_style1(sequence) -> @@ -3931,7 +3975,7 @@ get_font_style1(default) -> %% The framework application can switch this feature off by setting %% *its* application environment variable 'format_exception' to false. %% It is also possible to switch formatting off by starting the -%% test_server node with init argument 'test_server_format_exception' +%% test_server node with init argument 'test_server_format_exception' %% set to false. format_exception(Reason={_Error,Stack}) when is_list(Stack) -> @@ -3950,17 +3994,17 @@ format_exception(Reason={_Error,Stack}) when is_list(Stack) -> _ -> do_format_exception(Reason) end - end; + end; format_exception(Error) -> format_exception({Error,[]}). do_format_exception(Reason={Error,Stack}) -> StackFun = fun(_, _, _) -> false end, - PF = fun(Term, I) -> - io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term]) + PF = fun(Term, I) -> + io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term]) end, case catch lib:format_exception(1, error, Error, Stack, StackFun, PF) of - {'EXIT',_} -> + {'EXIT',_} -> {"~p",Reason}; Formatted -> Formatted1 = re:replace(Formatted, "exception error: ", "", [{return,list}]), @@ -3969,8 +4013,8 @@ do_format_exception(Reason={Error,Stack}) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, -%% Where, MultiplyTimetrap) -> +%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, +%% Where, TimetrapData) -> %% {{Time,RetVal,Loc,Opts,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} | %% {{died,Reason,unknown,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} %% Name = atom() @@ -3984,24 +4028,24 @@ do_format_exception(Reason={Error,Stack}) -> %% ProcessesBefore = ProcessesAfter = integer() %% %% Where indicates if the test should run on target or always on the host. -%% -%% If test is to be run on target, and target is remote the request is +%% +%% If test is to be run on target, and target is remote the request is %% sent over socket to target, and test_server runs the case and sends the %% result back over the socket. Else test_server runs the case directly on host. -run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, MultiplyTimetrap) -> +run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, TimetrapData) -> test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit, - MultiplyTimetrap}); -run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, MultiplyTimetrap) -> + TimetrapData}); +run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, TimetrapData) -> case get(test_server_ctrl_job_sock) of undefined -> %% local target test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit, - MultiplyTimetrap}); + TimetrapData}); JobSock -> %% remote target request(JobSock, {test_case,{CaseNum,Mod,Func,Args,Name,RunInit, - MultiplyTimetrap}}), + TimetrapData}}), read_job_sock_loop(JobSock) end. @@ -4012,15 +4056,15 @@ run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, MultiplyTim %% Args = [term()] %% %% Just like io:format, except that depending on the Detail value, the output -%% is directed to console, major and/or minor log files. +%% is directed to console, major and/or minor log files. %% %% To handle printouts to common (not minor) log files from parallel test %% case processes, the test_server_common_io_handler value is checked. If %% set, the data is sent to the main controlling process. Note that test %% cases that belong to a conf group nested under a parallel group will also %% get its io data sent to main rather than immediately printed out, even -%% if the test cases are executed by the same, main, process (ie the main -%% process sends messages to itself then). +%% if the test cases are executed by the same, main, process (ie the main +%% process sends messages to itself then). %% %% Buffered io is handled by the handle_test_case_io_and_status/0 function. @@ -4040,21 +4084,21 @@ print_or_buffer(Detail, Msg, Printer) -> output({Detail,Msg}, Printer); MinLevel when is_number(Detail), Detail >= MinLevel -> output({Detail,Msg}, Printer); - _ -> % Detail < Minor | major | html + _ -> % Detail < Minor | major | html case get(test_server_common_io_handler) of - undefined -> + undefined -> output({Detail,Msg}, Printer); {_,MainPid} -> MainPid ! {print,self(),Detail,Msg} end - end. + end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% print_timestamp(Detail, Leader) -> ok %% %% Prints Leader followed by a time stamp (date and time). Depending on %% the Detail value, the output is directed to console, major and/or minor -%% log files. +%% log files. print_timestamp(Detail, Leader) -> print(Detail, timestamp_get(Leader), []). @@ -4288,7 +4332,7 @@ update_config(Config, []) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% collect_cases(CurMod, TopCase, SkipList) -> +%% collect_cases(CurMod, TopCase, SkipList) -> %% BasicCaseList | {error,Reason} %% %% CurMod = atom() @@ -4319,18 +4363,18 @@ update_config(Config, []) -> %% are listed, and each Module:all(suite) is called %% {dir,Dir,Pattern} All modules _SUITE in the named dir %% are listed, and each Module:all(suite) is called -%% {conf,InitMF,Cases,FinMF} -%% {conf,Props,InitMF,Cases,FinMF} +%% {conf,InitMF,Cases,FinMF} +%% {conf,Props,InitMF,Cases,FinMF} %% InitMF is placed in the BasicCaseList, then %% Cases is treated according to this table, then %% FinMF is placed in the BasicCaseList. InitMF %% and FinMF are configuration manipulation %% functions. See below. -%% {make,InitMFA,Cases,FinMFA} +%% {make,InitMFA,Cases,FinMFA} %% InitMFA is placed in the BasicCaseList, then %% Cases is treated according to this table, then %% FinMFA is placed in the BasicCaseList. InitMFA -%% and FinMFA are make/unmake functions. If InitMFA +%% and FinMFA are make/unmake functions. If InitMFA %% fails, Cases are not run. InitMFA and FinMFA are %% always run on the host - not on target. %% @@ -4339,7 +4383,7 @@ update_config(Config, []) -> %% %% [] Leaf case %% {req,ReqList} Kept for backwards compatibility - same as [] -%% {req,ReqList,Cases} Kept for backwards compatibility - +%% {req,ReqList,Cases} Kept for backwards compatibility - %% Cases parsed recursively with collect_cases/3 %% Cases (list) Recursively parsed with collect_cases/3 %% @@ -4351,7 +4395,7 @@ update_config(Config, []) -> %% Configuration manipulation functions are called with the current %% configuration list as only argument, and are expected to return a new %% configuration list. Such a pair of function may, for example, start a -%% server and stop it after a serie of test cases. +%% server and stop it after a serie of test cases. %% %% SkipCases is expected to be in the format: %% @@ -4364,7 +4408,7 @@ update_config(Config, []) -> skip}). % skip list collect_all_cases(Top, Skip) when is_list(Skip) -> - Result = + Result = case collect_cases(Top, #cc{mod=[],skip=Skip}) of {ok,Cases,_St} -> Cases; Other -> Other @@ -4384,7 +4428,7 @@ collect_cases([Case|Cs0], St0) -> {error,_Reason}=Error -> Error end; - + collect_cases({module,Case}, St) when is_atom(Case), is_atom(St#cc.mod) -> collect_case({St#cc.mod,Case}, St); collect_cases({module,Mod,Case}, St) -> @@ -4409,19 +4453,19 @@ collect_cases({conf,Props,InitMF,CaseList,FinF}, St) when is_atom(FinF) -> collect_cases({conf,Props,InitMF,CaseList,{St#cc.mod,FinF}}, St); collect_cases({conf,Props,InitMF,CaseList,FinMF}, St0) -> case collect_cases(CaseList, St0) of - {ok,[],_St}=Empty -> + {ok,[],_St}=Empty -> Empty; {ok,FlatCases,St} -> Ref = make_ref(), case in_skip_list(InitMF, St#cc.skip) of - {true,Comment} -> - {ok,[{skip_case,{conf,Ref,InitMF,Comment}} | + {true,Comment} -> + {ok,[{skip_case,{conf,Ref,InitMF,Comment}} | FlatCases ++ [{conf,Ref,[],FinMF}]],St}; false -> - {ok,[{conf,Ref,Props,InitMF} | + {ok,[{conf,Ref,Props,InitMF} | FlatCases ++ [{conf,Ref,keep_name(Props),FinMF}]],St} end; - {error,_Reason}=Error -> + {error,_Reason}=Error -> Error end; @@ -4430,12 +4474,12 @@ collect_cases({make,InitMFA,CaseList,FinMFA}, St0) -> {ok,[],_St}=Empty -> Empty; {ok,FlatCases,St} -> Ref = make_ref(), - {ok,[{make,Ref,InitMFA}|FlatCases ++ + {ok,[{make,Ref,InitMFA}|FlatCases ++ [{make,Ref,FinMFA}]],St}; {error,_Reason}=Error -> Error end; -collect_cases({Module, Cases}, St) when is_list(Cases) -> +collect_cases({Module, Cases}, St) when is_list(Cases) -> case (catch collect_case(Cases, St#cc{mod=Module}, [])) of {ok, NewCases, NewSt} -> {ok, NewCases, NewSt}; @@ -4475,9 +4519,9 @@ collect_case_invoke(Mod, Case, MFA, St) -> case os:getenv("TEST_SERVER_FRAMEWORK") of false -> case catch apply(Mod, Case, [suite]) of - {'EXIT',_} -> + {'EXIT',_} -> {ok,[MFA],St}; - Suite -> + Suite -> collect_subcases(Mod, Case, MFA, St, Suite) end; _ -> @@ -4485,7 +4529,7 @@ collect_case_invoke(Mod, Case, MFA, St) -> collect_subcases(Mod, Case, MFA, St, Suite) end. -collect_subcases(Mod, Case, MFA, St, Suite) -> +collect_subcases(Mod, Case, MFA, St, Suite) -> case Suite of [] when Case == all -> {ok,[],St}; [] -> {ok,[MFA],St}; @@ -4536,7 +4580,7 @@ collect_case_deny(Mod, Case, MFA, ReqList, SubCases, St) -> {granted,SubCases} -> collect_case_subcases(Mod, Case, SubCases, St) end. - + check_deny([Req|Reqs], DenyList) -> case check_deny_req(Req, DenyList) of {denied,_Comment}=Denied -> Denied; @@ -4560,7 +4604,7 @@ check_deny_req(Req, DenyList) -> end. in_skip_list({Mod,Func,_Args}, SkipList) -> - in_skip_list({Mod,Func}, SkipList); + in_skip_list({Mod,Func}, SkipList); in_skip_list({Mod,Func}, [{Mod,Funcs,Comment}|SkipList]) when is_list(Funcs) -> case lists:member(Func, Funcs) of true -> @@ -4579,7 +4623,6 @@ in_skip_list(_, []) -> keep_name(Props) -> lists:filter(fun({name,_}) -> true; (_) -> false end, Props). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Target node handling functions %% @@ -4615,13 +4658,13 @@ start_node(Name, Type, Options) -> end, case Warning of [] -> ok; - _ -> + _ -> format(1, Warning), format(minor, Warning) end, {ok, Nodename}; {fail,{Ret, Host, Cmd}} -> - format(minor, + format(minor, "Failed to start node ~p on ~p with command: ~p~n" "Reason: ~p", [Name, Host, Cmd, Ret]), @@ -4630,7 +4673,7 @@ start_node(Name, Type, Options) -> format(minor, "Failed to start node ~p: ~p", [Name,Ret]), Ret; {Ret, Host, Cmd} -> - format(minor, + format(minor, "Failed to start node ~p on ~p with command: ~p~n" "Reason: ~p", [Name, Host, Cmd, Ret]), @@ -4685,7 +4728,7 @@ read_job_sock_loop(Sock) -> exit({controller,connection_lost,Reason}); {ok,<<1,Request/binary>>} -> case decode(binary_to_term(Request)) of - ok -> + ok -> read_job_sock_loop(Sock); {stop,Result} -> Result @@ -4695,14 +4738,14 @@ read_job_sock_loop(Sock) -> decode({apply,{M,F,A}}) -> apply(M,F,A), ok; -decode({sync_apply,{M,F,A}}) -> +decode({sync_apply,{M,F,A}}) -> R = apply(M,F,A), request(get(test_server_ctrl_job_sock),{sync_result,R}), ok; decode({sync_result,Result}) -> {stop,Result}; decode({test_case_result,Result}) -> - {stop,Result}; + {stop,Result}; decode({privdir,empty_priv_dir}) -> {stop,ok}; decode({{privdir,PrivDirTar},TarBin}) -> @@ -4742,7 +4785,7 @@ p({A,B,C}) -> p(X) -> pinfo(X). -t() -> +t() -> t(wall_clock). t(X) -> element(1, statistics(X)). @@ -4781,7 +4824,7 @@ display_info([Pid|T], R, M) -> Other -> Other end, - Reds = fetch(reductions, Info), + Reds = fetch(reductions, Info), LM = length(fetch(messages, Info)), pformat(io_lib:format("~w", [Pid]), io_lib:format("~w", [Call]), @@ -4822,12 +4865,12 @@ pinfo(P) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% A module is included in the cover analysis if -%% - it belongs to the tested application and is not listed in the +%% - it belongs to the tested application and is not listed in the %% {exclude,List} part of the App.cover file %% - it does not belong to the application, but is listed in the %% {include,List} part of the App.cover file -%% - it does not belong to the application, but is listed in the -%% cross.cover file (in the test_server application) under 'all' +%% - it does not belong to the application, but is listed in the +%% cross.cover file (in the test_server application) under 'all' %% or under the tested application. %% %% The modules listed in the cross.cover file are modules that are @@ -4893,7 +4936,7 @@ read_cover_file(CoverFile) -> io:fwrite("Faulty format of CoverFile ~p\n", [CoverFile]), {[],[]} end; - {error,Reason} -> + {error,Reason} -> io:fwrite("Can't read CoverFile ~p\nReason: ~p\n", [CoverFile,Reason]), {[],[]} @@ -4958,7 +5001,7 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) -> end, io:fwrite(CoverLog, "

Excluded module(s): ~p\n", [Excluded]), - + Coverage = cover_analyse(Analyse, AnalyseMods), case lists:filter(fun({_M,{_,_,_}}) -> false; @@ -4968,7 +5011,7 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) -> ok; Bad -> io:fwrite(CoverLog, "

Analysis failed for ~w module(s): " - "~w\n", + "~w\n", [length(Bad),[BadM || {BadM,{_,_Why}} <- Bad]]) end, @@ -5002,10 +5045,10 @@ cross_cover_analyse(Analyse, CrossModules) -> CoverdataFiles = get_coverdata_files(), lists:foreach(fun(CDF) -> cover:import(CDF) end, CoverdataFiles), io:fwrite("Cover analysing... ", []), - DetailsFun = + DetailsFun = case Analyse of details -> - fun(Dir,M) -> + fun(Dir,M) -> OutFile = filename:join(Dir, atom_to_list(M) ++ ".CROSS_COVER.html"), @@ -5018,7 +5061,7 @@ cross_cover_analyse(Analyse, CrossModules) -> SortedModules = case CrossModules of undefined -> - sort_modules([Mod || Mod <- get_all_cross_modules(), + sort_modules([Mod || Mod <- get_all_cross_modules(), lists:member(Mod, cover:imported_modules())], []); _ -> sort_modules(CrossModules, []) @@ -5031,7 +5074,7 @@ cross_cover_analyse(Analyse, CrossModules) -> %% cross.cover, write a cross cover log (cross_cover.html). write_cross_cover_logs([{App,Coverage}|T]) -> case last_test_for_app(App) of - false -> + false -> ok; Dir -> CoverLogName = filename:join(Dir,?cross_coverlog_name), @@ -5045,13 +5088,13 @@ write_cross_cover_logs([{App,Coverage}|T]) -> end, write_cross_cover_logs(T); write_cross_cover_logs([]) -> - io:fwrite("done\n", []). + io:fwrite("done\n", []). %% Find all exported coverdata files. First find all the latest %% run. directories, and the check if there is a file named %% all.coverdata. get_coverdata_files() -> - PossibleFiles = [last_coverdata_file(Dir) || + PossibleFiles = [last_coverdata_file(Dir) || Dir <- filelib:wildcard([$*|?logdir_ext]), filelib:is_dir(Dir)], [File || File <- PossibleFiles, filelib:is_file(File)]. @@ -5074,12 +5117,12 @@ last_test([_|Rest], Latest) -> last_test(Rest, Latest); last_test([], Latest) -> Latest. - + %% Sort modules according to the application they belong to. %% Return [{App,LastTestDir,ModuleList}] sort_modules([M|Modules], Acc) -> App = get_app(M), - Acc1 = + Acc1 = case lists:keysearch(App, 1, Acc) of {value,{App,LastTest,List}} -> lists:keyreplace(App, 1, Acc, {App,LastTest,[M|List]}); @@ -5120,9 +5163,9 @@ get_all_cross_modules() -> get_cross_modules(all). get_cross_modules(App) -> case file:consult(?cross_cover_file) of - {ok,List} -> + {ok,List} -> get_cross_modules(App, List, []); - _X -> + _X -> [] end. @@ -5134,11 +5177,11 @@ get_cross_modules(App, [_H|T], Acc) -> get_cross_modules(App, T, Acc); get_cross_modules(_App, [], Acc) -> Acc. - + %% Support functions for writing the cover logs (both cross and normal) write_coverlog_header(CoverLog) -> - case catch + case catch io:fwrite(CoverLog, "\n" "\n" @@ -5162,13 +5205,13 @@ format_analyse(M,Cov,NotCov,undefined) -> io_lib:fwrite("~w" "~w %" "~w" - "~w\n", + "~w\n", [M,pc(Cov,NotCov),Cov,NotCov]); format_analyse(M,Cov,NotCov,{file,File}) -> io_lib:fwrite("~w" "~w %" "~w" - "~w\n", + "~w\n", [filename:basename(File),M,pc(Cov,NotCov),Cov,NotCov]); format_analyse(M,Cov,NotCov,{lines,Lines}) -> CoverOutName = atom_to_list(M)++".COVER.html", @@ -5177,15 +5220,15 @@ format_analyse(M,Cov,NotCov,{lines,Lines}) -> io_lib:fwrite("~w" "~w %" "~w" - "~w\n", + "~w\n", [CoverOutName,M,pc(Cov,NotCov),Cov,NotCov]); format_analyse(M,Cov,NotCov,{error,_}) -> io_lib:fwrite("~w" "~w %" "~w" - "~w\n", + "~w\n", [M,pc(Cov,NotCov),Cov,NotCov]). - + pc(0,0) -> 0; @@ -5200,9 +5243,9 @@ write_not_covered(CoverOut,M,Lines) -> "\n" "\n", [M]), - lists:foreach(fun({{_M,Line},{0,1}}) -> + lists:foreach(fun({{_M,Line},{0,1}}) -> io:fwrite(CoverOut,"\n", [Line]); - (_) -> + (_) -> ok end, Lines), @@ -5216,7 +5259,7 @@ write_default_coverlog(TestDir) -> file:close(CoverLog). write_default_cross_coverlog(TestDir) -> - {ok,CrossCoverLog} = + {ok,CrossCoverLog} = file:open(filename:join(TestDir,?cross_coverlog_name), [write]), write_coverlog_header(CrossCoverLog), io:fwrite(CrossCoverLog, @@ -5232,7 +5275,7 @@ write_cover_result_table(CoverLog,Coverage) -> "\n", []), {TotCov,TotNotCov} = - lists:foldl(fun({M,{Cov,NotCov,Details}},{AccCov,AccNotCov}) -> + lists:foldl(fun({M,{Cov,NotCov,Details}},{AccCov,AccNotCov}) -> Str = format_analyse(M,Cov,NotCov,Details), io:fwrite(CoverLog,"~s", [Str]), {AccCov+Cov,AccNotCov+NotCov}; diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index 89edb0f881..2b4e1efb71 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -21,7 +21,7 @@ %%% Purpose: Test server support functions. %%%------------------------------------------------------------------- -module(test_server_sup). --export([timetrap/2, timetrap_cancel/1, capture_get/1, messages_get/1, +-export([timetrap/2, timetrap/3, timetrap_cancel/1, capture_get/1, messages_get/1, timecall/3, call_crash/5, app_test/2, check_new_crash_dumps/0, cleanup_crash_dumps/0, crash_dump_dir/0, tar_crash_dumps/0, get_username/0, get_os_family/0, @@ -34,16 +34,23 @@ -define(src_listing_ext, ".src.html"). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% timetrap(Timeout,Pid) -> Handle +%% timetrap(Timeout,Scale,Pid) -> Handle %% Handle = term() %% %% Creates a time trap, that will kill the given process if the %% trap is not cancelled with timetrap_cancel/1, within Timeout %% milliseconds. +%% Scale says if the time should be scaled up to compensate for +%% delays during the test (e.g. if cover is running). timetrap(Timeout0, Pid) -> + timetrap(Timeout0, true, Pid). + +timetrap(Timeout0, Scale, Pid) -> process_flag(priority, max), - Timeout = test_server:timetrap_scale_factor() * Timeout0, + Timeout = if not Scale -> Timeout0; + true -> test_server:timetrap_scale_factor() * Timeout0 + end, receive after trunc(Timeout) -> Line = test_server:get_loc(Pid), -- cgit v1.2.3 From 4da38a84f7540856fa590afdba2eb7958978788c Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 3 Jun 2010 14:27:21 +0200 Subject: Make it possible to run ts tests for Common Test via the ct_run:script_start() interface The possibility to pass start arguments to ct_run:start_script/0 by means of an application environment variable has been implemented. This will be used by ct_test_support for automatic testing of all common_test start interfaces. --- lib/common_test/src/ct_config.erl | 119 ++++++++-------- lib/common_test/src/ct_run.erl | 273 ++++++++++++++++++++++++------------ lib/common_test/src/ct_util.hrl | 2 + lib/test_server/src/test_server.erl | 4 +- 4 files changed, 246 insertions(+), 152 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index 6314361b35..ee84164ad7 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -164,10 +164,10 @@ delete_default_config(Scope) -> update_config(Name, Config) -> call({update_config, {Name, Config}}). -reload_config(KeyOrName)-> +reload_config(KeyOrName) -> call({reload_config, KeyOrName}). -process_default_configs(Opts)-> +process_default_configs(Opts) -> case lists:keysearch(config, 1, Opts) of {value,{_,Files=[File|_]}} when is_list(File) -> Files; @@ -179,21 +179,21 @@ process_default_configs(Opts)-> [] end. -process_user_configs(Opts, Acc)-> +process_user_configs(Opts, Acc) -> case lists:keytake(userconfig, 1, Opts) of false-> Acc; {value, {userconfig, {Callback, []}}, NewOpts}-> process_user_configs(NewOpts, [{Callback, []} | Acc]); {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when - is_list(File)-> + is_list(File) -> process_user_configs(NewOpts, [{Callback, Files} | Acc]); {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when - is_integer(C)-> + is_integer(C) -> process_user_configs(NewOpts, [{Callback, [File]} | Acc]) end. -get_config_file_list(Opts)-> +get_config_file_list(Opts) -> DefaultConfigs = process_default_configs(Opts), CfgFiles = if @@ -206,16 +206,16 @@ get_config_file_list(Opts)-> CfgFiles. read_config_files(Opts) -> - AddCallback = fun(CallBack, [])-> + AddCallback = fun(CallBack, []) -> [{CallBack, []}]; - (CallBack, [F|_]=Files) when is_integer(F)-> + (CallBack, [F|_]=Files) when is_integer(F) -> [{CallBack, Files}]; - (CallBack, [F|_]=Files) when is_list(F)-> - lists:map(fun(X)-> {CallBack, X} end, Files) + (CallBack, [F|_]=Files) when is_list(F) -> + lists:map(fun(X) -> {CallBack, X} end, Files) end, ConfigFiles = case lists:keyfind(config, 1, Opts) of {config, ConfigLists}-> - lists:foldr(fun({Callback,Files}, Acc)-> + lists:foldr(fun({Callback,Files}, Acc) -> AddCallback(Callback,Files) ++ Acc end, [], @@ -225,18 +225,18 @@ read_config_files(Opts) -> end, read_config_files_int(ConfigFiles, fun store_config/3). -read_config_files_int([{Callback, File}|Files], FunToSave)-> +read_config_files_int([{Callback, File}|Files], FunToSave) -> case Callback:read_config(File) of - {ok, Config}-> + {ok, Config} -> FunToSave(Config, Callback, File), read_config_files_int(Files, FunToSave); {error, ErrorName, ErrorDetail}-> {user_error, {ErrorName, File, ErrorDetail}} end; -read_config_files_int([], _FunToSave)-> +read_config_files_int([], _FunToSave) -> ok. -store_config(Config, Callback, File)-> +store_config(Config, Callback, File) -> [ets:insert(?attr_table, #ct_conf{key=Key, value=Val, @@ -246,35 +246,34 @@ store_config(Config, Callback, File)-> default=false}) || {Key,Val} <- Config]. -keyfindall(Key, Pos, List)-> +keyfindall(Key, Pos, List) -> [E || E <- List, element(Pos, E) =:= Key]. -rewrite_config(Config, Callback, File)-> +rewrite_config(Config, Callback, File) -> OldRows = ets:match_object(?attr_table, #ct_conf{handler=Callback, config=File,_='_'}), ets:match_delete(?attr_table, #ct_conf{handler=Callback, config=File,_='_'}), - Updater = fun({Key, Value})-> + Updater = fun({Key, Value}) -> case keyfindall(Key, #ct_conf.key, OldRows) of []-> ets:insert(?attr_table, #ct_conf{key=Key, - value=Value, - handler=Callback, - config=File, - ref=ct_util:ct_make_ref()}); + value=Value, + handler=Callback, + config=File, + ref=ct_util:ct_make_ref()}); RowsToUpdate -> - Inserter = fun(Row)-> - ets:insert(?attr_table, - Row#ct_conf{value=Value, - ref=ct_util:ct_make_ref()}) - end, + Inserter = fun(Row) -> + ets:insert(?attr_table, + Row#ct_conf{value=Value, + ref=ct_util:ct_make_ref()}) + end, lists:foreach(Inserter, RowsToUpdate) end - end, - + end, [Updater({Key, Value})||{Key, Value}<-Config]. set_config(Config,Default) -> @@ -397,9 +396,9 @@ lookup_key(Key) -> [], [{{'$1','$2'}}]}]). -lookup_handler_for_config({Key, _Subkey})-> +lookup_handler_for_config({Key, _Subkey}) -> lookup_handler_for_config(Key); -lookup_handler_for_config(KeyOrName)-> +lookup_handler_for_config(KeyOrName) -> case lookup_handler_for_name(KeyOrName) of [] -> lookup_handler_for_key(KeyOrName); @@ -407,12 +406,12 @@ lookup_handler_for_config(KeyOrName)-> Values end. -lookup_handler_for_name(Name)-> +lookup_handler_for_name(Name) -> ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',name=Name,_='_'}, [], [{{'$1','$2'}}]}]). -lookup_handler_for_key(Key)-> +lookup_handler_for_key(Key) -> ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',key=Key,_='_'}, [], [{{'$1','$2'}}]}]). @@ -685,7 +684,7 @@ random_bytes(N) -> random_bytes_1(0, Acc) -> Acc; random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). -check_callback_load(Callback)-> +check_callback_load(Callback) -> case code:is_loaded(Callback) of {file, _Filename}-> {ok, Callback}; @@ -698,16 +697,16 @@ check_callback_load(Callback)-> end end. -check_config_files(Configs)-> +check_config_files(Configs) -> ConfigChecker = fun - ({Callback, [F|_R]=Files})-> + ({Callback, [F|_R]=Files}) -> case check_callback_load(Callback) of {ok, Callback}-> if - is_integer(F)-> + is_integer(F) -> Callback:check_parameter(Files); - is_list(F)-> - lists:map(fun(File)-> + is_list(F) -> + lists:map(fun(File) -> Callback:check_parameter(File) end, Files) @@ -715,7 +714,7 @@ check_config_files(Configs)-> {error, _}-> {error, {callback, Callback}} end; - ({Callback, []})-> + ({Callback, []}) -> case check_callback_load(Callback) of {ok, Callback}-> Callback:check_parameter([]); @@ -725,46 +724,46 @@ check_config_files(Configs)-> end, lists:keysearch(error, 1, lists:flatten(lists:map(ConfigChecker, Configs))). -prepare_user_configs([ConfigString|UserConfigs], Acc, new)-> +prepare_user_configs([ConfigString|UserConfigs], Acc, new) -> prepare_user_configs(UserConfigs, [{list_to_atom(ConfigString), []}|Acc], cur); -prepare_user_configs(["and"|UserConfigs], Acc, _)-> +prepare_user_configs(["and"|UserConfigs], Acc, _) -> prepare_user_configs(UserConfigs, Acc, new); -prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur)-> +prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur) -> prepare_user_configs(UserConfigs, [{LastMod, [ConfigString|LastList]}|Acc], cur); -prepare_user_configs([], Acc, _)-> +prepare_user_configs([], Acc, _) -> Acc. -prepare_config_list(Args)-> +prepare_config_list(Args) -> ConfigFiles = case lists:keysearch(ct_config, 1, Args) of - {value,{ct_config,Files}}-> - [{?ct_config_txt, Files}]; - false-> - [] - end, + {value,{ct_config,Files}}-> + [{?ct_config_txt,[filename:absname(F) || F <- Files]}]; + false-> + [] + end, UserConfigs = case lists:keysearch(userconfig, 1, Args) of - {value,{userconfig,UserConfigFiles}}-> - prepare_user_configs(UserConfigFiles, [], new); - false-> - [] - end, + {value,{userconfig,UserConfigFiles}}-> + prepare_user_configs(UserConfigFiles, [], new); + false-> + [] + end, ConfigFiles ++ UserConfigs. % TODO: add logging of the loaded configuration file to the CT FW log!!! -add_config(Callback, [])-> +add_config(Callback, []) -> read_config_files_int([{Callback, []}], fun store_config/3); -add_config(Callback, [File|_Files]=Config) when is_list(File)-> - lists:foreach(fun(CfgStr)-> +add_config(Callback, [File|_Files]=Config) when is_list(File) -> + lists:foreach(fun(CfgStr) -> read_config_files_int([{Callback, CfgStr}], fun store_config/3) end, Config); -add_config(Callback, [C|_]=Config) when is_integer(C)-> +add_config(Callback, [C|_]=Config) when is_integer(C) -> read_config_files_int([{Callback, Config}], fun store_config/3), ok. -remove_config(Callback, Config)-> +remove_config(Callback, Config) -> ets:match_delete(?attr_table, #ct_conf{handler=Callback, config=Config,_='_'}), diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 96703031d2..2c7d72c812 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -73,7 +73,25 @@ %%% script_start() -> process_flag(trap_exit, true), - Args = merge_arguments(init:get_arguments()), + Init = init:get_arguments(), + CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false; + (_) -> true end, Init), + Args = case application:get_env(common_test, run_test_start_opts) of + {ok,EnvStartOpts} -> + merge_arguments(CtArgs ++ opts2args(EnvStartOpts)); + _ -> + merge_arguments(CtArgs) + end, + + %%! --- Mon May 31 12:32:48 2010 --- peppe was here! + io:format(user, "~nInit:~n~p~n", [Init]), + + %%! --- Mon May 31 12:32:48 2010 --- peppe was here! + io:format(user, "~nCtArgs:~n~p~n", [CtArgs]), + + %%! --- Mon May 31 12:32:48 2010 --- peppe was here! + io:format(user, "~nArgs:~n~p~n", [Args]), + case proplists:get_value(help, Args) of undefined -> script_start(Args); _ -> script_usage() @@ -126,8 +144,8 @@ script_start1(Parent, Args) -> Shell = get_start_opt(shell, true, Args), Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, Args), LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), - MultTT = get_start_opt(multiply_timetraps, fun(MT) -> MT end, Args), - ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, Args), + MultTT = get_start_opt(multiply_timetraps, fun(MT) -> MT end, 1, Args), + ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, false, Args), EvHandlers = get_start_opt(event_handler, fun(Handlers) -> lists:map(fun(H) -> @@ -186,11 +204,11 @@ script_start1(Parent, Args) -> application:set_env(common_test, basic_html, true) end, - StartOpts = #opts({vts = Vts, shell = Shell, cover = Cover, - logdir = LogDir, event_handlers = EvHandlers, - include = IncludeDirs, - multiply_timetraps = MultTT, - scale_timetraps = ScaleTT}), + StartOpts = #opts{vts = Vts, shell = Shell, cover = Cover, + logdir = LogDir, event_handlers = EvHandlers, + include = IncludeDirs, + multiply_timetraps = MultTT, + scale_timetraps = ScaleTT}, %% check if log files should be refreshed or go on to run tests... Result = run_or_refresh(StartOpts, Args), @@ -203,7 +221,7 @@ run_or_refresh(StartOpts = #opts{logdir = LogDir}, Args) -> script_start2(StartOpts, Args); Refresh -> LogDir1 = case Refresh of - [] -> which_logdir(LogDir); + [] -> which(logdir,LogDir); [RefreshDir] -> ?abs(RefreshDir) end, {ok,Cwd} = file:get_cwd(), @@ -232,14 +250,14 @@ run_or_refresh(StartOpts = #opts{logdir = LogDir}, Args) -> script_start2(StartOpts = #opts{vts = undefined, shell = undefined}, Args) -> TestSpec = proplists:get_value(spec, Args), - Opts = + {Terms,Opts} = case TestSpec of Specs when Specs =/= [], Specs =/= undefined -> %% using testspec as input for test Relaxed = get_start_opt(allow_user_terms, true, false, Args), case catch ct_testspec:collect_tests_from_file(Specs, Relaxed) of - {error,Reason} -> - {error,Reason}; + {E,Reason} when E == error ; E == 'EXIT' -> + {{error,Reason},StartOpts}; TS -> SpecStartOpts = get_data_for_node(TS, node()), @@ -258,40 +276,42 @@ script_start2(StartOpts = #opts{vts = undefined, SpecStartOpts#opts.include]), application:set_env(common_test, include, AllInclude), - StartOpts#opts{testspecs = Specs, - cover = Cover, - logdir = LogDir, - config = SpecStartOpts#opts.config, - event_handlers = AllEvHs, - include = AllInclude, - multiply_timetraps = MultTT, - scale_timetraps = ScaleTT} + {TS,StartOpts#opts{testspecs = Specs, + cover = Cover, + logdir = LogDir, + config = SpecStartOpts#opts.config, + event_handlers = AllEvHs, + include = AllInclude, + multiply_timetraps = MultTT, + scale_timetraps = ScaleTT}} end; _ -> - StartOpts + {undefined,StartOpts} end, %% read config/userconfig from start flags InitConfig = ct_config:prepare_config_list(Args), - case TestSpec of - [] -> + case {TestSpec,Terms} of + {_,{error,_}=Error} -> + Error; + {[],_} -> {error,no_testspec_specified}; - undefined -> % no testspec used + {undefined,_} -> % no testspec used case check_and_install_configfiles(InitConfig, which(logdir,Opts#opts.logdir), Opts#opts.event_handlers) of - ok -> % go on read tests from start flags + ok -> % go on read tests from start flags script_start3(Opts#opts{config=InitConfig}, Args); Error -> Error end; - _ -> % testspec used + {_,_} -> % testspec used %% merge config from start flags with config from testspec AllConfig = merge_vals([InitConfig, Opts#opts.config]), case check_and_install_configfiles(AllConfig, which(logdir,Opts#opts.logdir), Opts#opts.event_handlers) of - ok -> % read tests from spec - {Run,Skip} = ct_testspec:prepare_tests(TS, node()), + ok -> % read tests from spec + {Run,Skip} = ct_testspec:prepare_tests(Terms, node()), do_run(Run, Skip, Opts#opts{config=AllConfig}, Args); Error -> Error @@ -314,7 +334,7 @@ check_and_install_configfiles(Configs, LogDir, EvHandlers) -> {error,{cant_load_callback_module,File}} end. -script_start3(StartOpts = #opts{cover = Cover}, Args) -> +script_start3(StartOpts, Args) -> case proplists:get_value(dir, Args) of [] -> {error,no_dir_specified}; @@ -332,8 +352,8 @@ script_start3(StartOpts = #opts{cover = Cover}, Args) -> cover = undefined} end, StartOpts, Args), DirMods = [suite_to_test(S) || S <- Suites], - case groups_and_cases(proplist:get_value(group, Args), - proplist:get_value(testcase, Args)) of + case groups_and_cases(proplists:get_value(group, Args), + proplists:get_value(testcase, Args)) of Error = {error,_} -> Error; [] when DirMods =/= [] -> @@ -357,14 +377,14 @@ script_start3(StartOpts = #opts{cover = Cover}, Args) -> end end. -script_start4(#opts{vts = true, config = Config, event_handler = EvHandlers, - tests = Tests, logdir = LogDir}, Args) -> +script_start4(#opts{vts = true, config = Config, event_handlers = EvHandlers, + tests = Tests, logdir = LogDir}, _Args) -> vts:init_data(Config, EvHandlers, ?abs(LogDir), Tests); script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers, - logdir = LogDir, testspecs = Specs}, Args) -> + logdir = LogDir, testspecs = Specs}, _Args) -> InstallOpts = [{config,Config},{event_handler,EvHandlers}], - if ConfigFiles == [] -> + if Config == [] -> ok; true -> io:format("\nInstalling: ~p\n\n", [Config]) @@ -485,7 +505,7 @@ install(Opts, LogDir) -> case whereis(ct_util_server) of undefined -> VarFile = variables_file_name(LogDir), - io:format("Varfile=~p~n", [VarFile]), + io:format("Varfile = ~p~n", [VarFile]), case file:open(VarFile, [write]) of {ok,Fd} -> [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts], @@ -522,7 +542,7 @@ run_test(StartOpt) when is_tuple(StartOpt) -> run_test([StartOpt]); run_test(StartOpts) when is_list(StartOpts) -> - case proplist:get_value(refresh_logs, StartOpts) of + case proplists:get_value(refresh_logs, StartOpts) of undefined -> Tracing = start_trace(StartOpts), {ok,Cwd} = file:get_cwd(), @@ -621,13 +641,13 @@ run_test1(StartOpts) -> end, %% decrypt config file - case lists:keysearch(decrypt, 1, StartOpts) of - {value,{_,Key={key,_}}} -> + case proplists:get_value(decrypt, StartOpts) of + undefined -> + application:unset_env(common_test, decrypt); + Key={key,_} -> application:set_env(common_test, decrypt, Key); - {value,{_,{file,KeyFile}}} -> - application:set_env(common_test, decrypt, {file,filename:absname(KeyFile)}); - false -> - application:unset_env(common_test, decrypt) + {file,KeyFile} -> + application:set_env(common_test, decrypt, {file,filename:absname(KeyFile)}) end, %% basic html - used by ct_logs @@ -639,7 +659,7 @@ run_test1(StartOpts) -> end, %% stepped execution - Step = get_start_opt(step, value, StepOpts), + Step = get_start_opt(step, value, StartOpts), Opts = #opts{cover = Cover, step = Step, logdir = LogDir, config = CfgFiles, event_handlers = EvHandlers, include = Include, @@ -661,11 +681,6 @@ run_test1(StartOpts) -> run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts) end. -replace_opt([O={Key,_Val}|Os], Opts) -> - [O | replace_opt(Os, lists:keydelete(Key, 1, Opts))]; -replace_opt([], Opts) -> - Opts. - run_spec_file(Relaxed, Opts = #opts{testspecs = Specs, config = CfgFiles}, StartOpts) -> @@ -676,7 +691,7 @@ run_spec_file(Relaxed, AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1), log_ts_names(AbsSpecs), case catch ct_testspec:collect_tests_from_file(AbsSpecs, Relaxed) of - {error,CTReason} -> + {Error,CTReason} when Error == error ; Error == 'EXIT' -> exit(CTReason); TS -> SpecOpts = get_data_for_node(TS, node()), @@ -699,16 +714,14 @@ run_spec_file(Relaxed, which(logdir,LogDir), AllEvHs) of ok -> - Opts1 = Opts#opts{testspecs = Specs, - cover = Cover, + Opts1 = Opts#opts{cover = Cover, logdir = LogDir, config = AllConfig, event_handlers = AllEvHs, include = AllInclude, testspecs = AbsSpecs, multiply_timetraps = MultTT, - scale_timetraps = ScaleTT} - + scale_timetraps = ScaleTT}, {Run,Skip} = ct_testspec:prepare_tests(TS, node()), do_run(Run, Skip, Opts1, StartOpts); {error,GCFReason} -> @@ -768,7 +781,7 @@ run_dir(Opts = #opts{logdir = LogDir, {value,{_,Dirs=[Dir|_]}} when not is_integer(Dir), length(Dirs)>1 -> %% multiple dirs (no suite) - do_run(tests(Dirs), [], StepOrCover, Opts, LogDir); + do_run(tests(Dirs), [], Opts, StartOpts); false -> % no dir %% fun for converting suite name to {Dir,Mod} tuple S2M = fun(S) when is_list(S) -> @@ -783,12 +796,12 @@ run_dir(Opts = #opts{logdir = LogDir, case listify(proplists:get_value(group, Opts, [])) ++ listify(proplists:get_value(testcase, Opts, [])) of [] -> - do_run(tests(Dir, listify(Mod)), [], StepOrCover, Opts, LogDir); + do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts); GsAndCs -> - do_run(tests(Dir, Mod, GsAndCs), [], StepOrCover, Opts, LogDir) + do_run(tests(Dir, Mod, GsAndCs), [], Opts, StartOpts) end; {value,{_,Suites}} -> - do_run(tests(lists:map(S2M, Suites)), [], StepOrCover, Opts, LogDir); + do_run(tests(lists:map(S2M, Suites)), [], Opts, StartOpts); _ -> exit(no_tests_specified) end; @@ -801,17 +814,17 @@ run_dir(Opts = #opts{logdir = LogDir, case listify(proplists:get_value(group, Opts, [])) ++ listify(proplists:get_value(testcase, Opts, [])) of [] -> - do_run(tests(Dir, listify(Mod)), [], StepOrCover, Opts, LogDir); + do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts); GsAndCs -> - do_run(tests(Dir, Mod, GsAndCs), [], StepOrCover, Opts, LogDir) + do_run(tests(Dir, Mod, GsAndCs), [], Opts, StartOpts) end; {value,{_,Suites=[Suite|_]}} when is_list(Suite) -> Mods = lists:map(fun(Str) -> list_to_atom(Str) end, Suites), - do_run(tests(delistify(Dir), Mods), [], StepOrCover, Opts, LogDir); + do_run(tests(delistify(Dir), Mods), [], Opts, StartOpts); {value,{_,Suites}} -> - do_run(tests(delistify(Dir), Suites), [], StepOrCover, Opts, LogDir); + do_run(tests(delistify(Dir), Suites), [], Opts, StartOpts); false -> % no suite, only dir - do_run(tests(listify(Dir)), [], StepOrCover, Opts, LogDir) + do_run(tests(listify(Dir)), [], Opts, StartOpts) end end. @@ -836,8 +849,8 @@ run_testspec(TestSpec) -> end. run_testspec1(TestSpec) -> - case ct_testspec:collect_tests_from_list(TestSpec, false) of - {error,CTReason} -> + case catch ct_testspec:collect_tests_from_list(TestSpec, false) of + {E,CTReason} when E == error ; E == 'EXIT' -> exit(CTReason); TS -> Opts = get_data_for_node(TS, node()), @@ -848,7 +861,7 @@ run_testspec1(TestSpec) -> Opts#opts.include; CtInclPath -> EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]), - EnvInclude++Opts#opts.include, + EnvInclude++Opts#opts.include end, application:set_env(common_test, include, AllInclude), @@ -968,8 +981,8 @@ run(TestDirs) -> suite_to_test(Suite) -> {filename:dirname(Suite),list_to_atom(filename:rootname(filename:basename(Suite)))}. -groups_and_cases(Gs, Cs) when (Gs == undefined ; Gs == []), - (Cs == undefined ; Cs == []) -> +groups_and_cases(Gs, Cs) when ((Gs == undefined) or (Gs == [])) and + ((Cs == undefined) or (Cs == [])) -> []; groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] -> [list_to_atom(C) || C <- Cs]; @@ -979,7 +992,7 @@ groups_and_cases([G], Cs) -> [{list_to_atom(G),[list_to_atom(C) || C <- Cs]}]; groups_and_cases([_,_|_] , Cs) when Cs =/= [] -> {error,multiple_groups_and_cases}; -groups_and_cases(Gs, Cs) -> +groups_and_cases(_Gs, _Cs) -> {error,incorrect_group_or_case_option}. tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) -> @@ -997,11 +1010,28 @@ tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) -> tests(TestDirs) when is_list(TestDirs), is_list(hd(TestDirs)) -> [{?testdir(TestDir,all),all,all} || TestDir <- TestDirs]. -do_run(Tests, Opt, LogDir) when is_list(Opt) -> - do_run(Tests, [], #opts{tests = Tests, logdir = LogDir}, []). +do_run(Tests, Misc) when is_list(Misc) -> + do_run(Tests, Misc, "."). + +do_run(Tests, Misc, LogDir) when is_list(Misc) -> + Opts = + case proplists:get_value(step, Misc) of + undefined -> + #opts{}; + StepOpts -> + #opts{step = StepOpts} + end, + Opts1 = + case proplists:get_value(cover, Misc) of + undefined -> + Opts; + CoverFile -> + Opts#opts{cover = CoverFile} + end, + do_run(Tests, [], Opts1#opts{tests = Tests, logdir = LogDir}, []). do_run(Tests, Skip, Opts, Args) -> - #opts{cover = Cover} = Opts + #opts{cover = Cover} = Opts, case code:which(test_server) of non_existing -> exit({error,no_path_to_test_server}); @@ -1026,7 +1056,7 @@ do_run(Tests, Skip, Opts, Args) -> Other -> erlang:display(list_to_atom("Note: TEST_SERVER_FRAMEWORK = " ++ Other)) end, - case ct_util:start(LogDir) of + case ct_util:start(Opts#opts.logdir) of {error,interactive_mode} -> io:format("CT is started in interactive mode. " "To exit this mode, run ct:stop_interactive().\n" @@ -1421,7 +1451,7 @@ do_run_test(Tests, Skip, Opts) -> ok end, lists:foreach(fun(Suite) -> - maybe_cleanup_interpret(Suite, Opt) + maybe_cleanup_interpret(Suite, Opts#opts.step) end, CleanUp); Error -> Error @@ -1714,10 +1744,10 @@ set_break_on_config(Suite, StepOpts) -> ok end. -maybe_cleanup_interpret(Suite, [{step,_}]) -> - i:iq(Suite); -maybe_cleanup_interpret(_, _) -> - ok. +maybe_cleanup_interpret(_, undefined) -> + ok; +maybe_cleanup_interpret(Suite, _) -> + i:iq(Suite). log_ts_names([]) -> ok; @@ -1729,17 +1759,42 @@ log_ts_names(Specs) -> [lists:flatten(List)]). merge_arguments(Args) -> - merge_arguments(Args, []). - -merge_arguments([LogDir={logdir,_}|Args], Merged) -> - merge_arguments(Args, handle_arg(replace, LogDir, Merged)); -merge_arguments([CoverFile={cover,_}|Args], Merged) -> - merge_arguments(Args, handle_arg(replace, CoverFile, Merged)); -merge_arguments([{'case',TC}|Args], Merged) -> - merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged)); -merge_arguments([Arg={_,_}|Args], Merged) -> - merge_arguments(Args, handle_arg(merge, Arg, Merged)); -merge_arguments([], Merged) -> + case proplists:get_value(ct_ignore, Args) of + undefined -> + merge_arguments(Args, [], []); + Ignore -> + merge_arguments(Args, [], [list_to_atom(I) || I <- Ignore]) + end. + +merge_arguments([LogDir={logdir,_}|Args], Merged, Ignore) -> + case lists:member(logdir, Ignore) of + true -> + merge_arguments(Args, Merged, Ignore); + false -> + merge_arguments(Args, handle_arg(replace, LogDir, Merged), Ignore) + end; +merge_arguments([CoverFile={cover,_}|Args], Merged, Ignore) -> + case lists:member(cover, Ignore) of + true -> + merge_arguments(Args, Merged, Ignore); + false -> + merge_arguments(Args, handle_arg(replace, CoverFile, Merged), Ignore) + end; +merge_arguments([{'case',TC}|Args], Merged, Ignore) -> + case lists:member('case', Ignore) of + true -> + merge_arguments(Args, Merged, Ignore); + false -> + merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged), Ignore) + end; +merge_arguments([Arg={Opt,_}|Args], Merged, Ignore) -> + case lists:member(Opt, Ignore) of + true -> + merge_arguments(Args, Merged, Ignore); + false -> + merge_arguments(Args, handle_arg(merge, Arg, Merged), Ignore) + end; +merge_arguments([], Merged, _Ignore) -> Merged. handle_arg(replace, {Key,Elems}, [{Key,_}|Merged]) -> @@ -1752,7 +1807,7 @@ handle_arg(_,Arg,[]) -> [Arg]. get_start_opt(Key, IfExists, Args) -> - get_start_opt(Key, IfExists, undefined, Args); + get_start_opt(Key, IfExists, undefined, Args). get_start_opt(Key, IfExists, IfNotExists, Args) -> case lists:keysearch(Key, 1, Args) of @@ -1768,6 +1823,44 @@ get_start_opt(Key, IfExists, IfNotExists, Args) -> IfNotExists end. +%% this function translates ct:run_test/1 start options +%% to run_test start arguments (on the init arguments format) - +%% this is useful mainly for testing the ct_run start functions +opts2args(EnvStartOpts) -> + lists:flatmap(fun({config,CfgFiles}) -> + [{ct_config,CfgFiles}]; + ({testcase,Cases}) -> + [{'case',[atom_to_list(C) || C <- Cases]}]; + ({'case',Cases}) -> + [{'case',[atom_to_list(C) || C <- Cases]}]; + ({allow_user_terms,true}) -> + [{allow_user_terms,[]}]; + ({allow_user_terms,false}) -> + []; + ({auto_compile,false}) -> + [{no_auto_compile,[]}]; + ({auto_compile,true}) -> + []; + ({scale_timetraps,true}) -> + [{scale_timetraps,[]}]; + ({scale_timetraps,false}) -> + []; + ({force_stop,true}) -> + [{force_stop,[]}]; + ({force_stop,false}) -> + []; + ({decrypt,{key,Key}}) -> + [{ct_decrypt_key,Key}]; + ({decrypt,{file,File}}) -> + [{ct_decrypt_file,File}]; + ({basic_html,true}) -> + ({basic_html,[]}); + ({basic_html,false}) -> + []; + (Opt) -> + Opt + end, EnvStartOpts). + locate_test_dir(Dir, Suite) -> TestDir = case ct_util:is_test_dir(Dir) of true -> Dir; diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 7ddb7d8c2b..d9c5631f88 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -36,6 +36,8 @@ userconfig=[], event_handler=[], include=[], + multiply_timetraps, + scale_timetraps, alias=[], tests=[]}). diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 7fb708778c..e3d853f078 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -958,7 +958,6 @@ job_proxy_msgloop() -> run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit, TimetrapData, TCCallback) -> - put(test_server_multiply_timetraps,TimetrapData), {{Time,Value},Loc,Opts} = @@ -1606,12 +1605,13 @@ timetrap_scale_factor() -> %% %% Creates a time trap, that will kill the calling process if the %% trap is not cancelled with timetrap_cancel/1, within Timeout milliseconds. - timetrap(Timeout0) -> Timeout = time_ms(Timeout0), cancel_default_timetrap(), case get(test_server_multiply_timetraps) of undefined -> timetrap1(Timeout, true); + {undefined,false} -> timetrap1(Timeout, false); + {undefined,_} -> timetrap1(Timeout, true); {infinity,_} -> infinity; {Int,Scale} -> timetrap1(Timeout*Int, Scale) end. -- cgit v1.2.3 From 7c6504029f84c52de40a6b29b2fd1b17c053ccec Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 3 Jun 2010 14:34:09 +0200 Subject: Add event_handler_init start flag that can pass init arguments to event handlers Also changed: The userconfig option in ct:run_test/1 from 3-tuple to 2-tuple. --- lib/common_test/doc/src/run_test.xml | 10 +- lib/common_test/src/ct.erl | 5 +- lib/common_test/src/ct_config.erl | 21 ++-- lib/common_test/src/ct_run.erl | 154 ++++++++++++++++++--------- lib/common_test/test/ct_smoke_test_SUITE.erl | 18 ++-- lib/common_test/test/ct_test_support.erl | 19 +++- lib/common_test/test/ct_test_support_eh.erl | 17 ++- 7 files changed, 169 insertions(+), 75 deletions(-) diff --git a/lib/common_test/doc/src/run_test.xml b/lib/common_test/doc/src/run_test.xml index 49538a7483..f98cefd0ee 100644 --- a/lib/common_test/doc/src/run_test.xml +++ b/lib/common_test/doc/src/run_test.xml @@ -63,13 +63,15 @@ [-step [config | keep_inactive]] [-config ConfigFile1 ConfigFile2 .. ConfigFileN] [-userconfig CallbackModule1 ConfigString1 and CallbackModule2 - ConfigString2 and .. and CallbackModuleN ConfigStringN] + ConfigString2 and .. and CallbackModuleN ConfigStringN] [-decrypt_key Key] | [-decrypt_file KeyFile] [-logdir LogDir] [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]] [-stylesheet CSSFile] [-cover CoverCfgFile] - [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] + [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] | + [-event_handler_init EvHandler1 InitArg1 and + EvHandler2 InitArg2 and .. EvHandlerN InitArgN] [-include InclDir1 InclDir2 .. InclDirN] [-no_auto_compile] [-repeat N [-force_stop]] | @@ -90,7 +92,9 @@ [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]] [-stylesheet CSSFile] [-cover CoverCfgFile] - [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] + [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] | + [-event_handler_init EvHandler1 InitArg1 and + EvHandler2 InitArg2 and .. EvHandlerN InitArgN] [-include InclDir1 InclDir2 .. InclDirN] [-no_auto_compile] [-repeat N [-force_stop]] | diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 57035719e2..307d10428d 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -139,7 +139,7 @@ run(TestDirs) -> %%% @spec run_test(Opts) -> Result %%% Opts = [OptTuples] %%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} | -%%% {userconfig, Callback, CfgFiles} | +%%% {userconfig, UserConfig} | %%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} | %%% {allow_user_terms,Bool} | {logdir,LogDir} | %%% {silent_connections,Conns} | {cover,CoverSpecFile} | @@ -149,6 +149,9 @@ run(TestDirs) -> %%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} | %%% {refresh_logs,LogDir} | {basic_html,Bool} %%% CfgFiles = [string()] | string() +%%% UserConfig = [{CallbackMod,CfgStrings}] | {CallbackMod,CfgStrings} +%%% CallbackMod = atom() +%%% CfgStrings = [string()] | string() %%% TestDirs = [string()] | string() %%% Suites = [string()] | string() %%% Cases = [atom()] | atom() diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index ee84164ad7..a7b8a9e906 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -181,15 +181,22 @@ process_default_configs(Opts) -> process_user_configs(Opts, Acc) -> case lists:keytake(userconfig, 1, Opts) of - false-> - Acc; - {value, {userconfig, {Callback, []}}, NewOpts}-> + false -> + lists:reverse(Acc); + {value, {userconfig, Config=[{_,_}|_]}, NewOpts} -> + Acc1 = lists:map(fun({_Callback, []}=Cfg) -> + Cfg; + ({Callback, Files=[File|_]}) when is_list(File) -> + {Callback, Files}; + ({Callback, File=[C|_]}) when is_integer(C) -> + {Callback, [File]} + end, Config), + process_user_configs(NewOpts, lists:reverse(Acc1)++Acc); + {value, {userconfig, {Callback, []}}, NewOpts} -> process_user_configs(NewOpts, [{Callback, []} | Acc]); - {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when - is_list(File) -> + {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when is_list(File) -> process_user_configs(NewOpts, [{Callback, Files} | Acc]); - {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when - is_integer(C) -> + {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when is_integer(C) -> process_user_configs(NewOpts, [{Callback, [File]} | Acc]) end. diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 2c7d72c812..ec9351f346 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -78,6 +78,9 @@ script_start() -> (_) -> true end, Init), Args = case application:get_env(common_test, run_test_start_opts) of {ok,EnvStartOpts} -> + %%! --- Mon May 31 22:59:29 2010 --- peppe was here! + io:format(user, "~nEnv1:~n~p~n~n~p~n", [EnvStartOpts,opts2args(EnvStartOpts)]), + merge_arguments(CtArgs ++ opts2args(EnvStartOpts)); _ -> merge_arguments(CtArgs) @@ -146,12 +149,10 @@ script_start1(Parent, Args) -> LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), MultTT = get_start_opt(multiply_timetraps, fun(MT) -> MT end, 1, Args), ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, false, Args), - EvHandlers = get_start_opt(event_handler, - fun(Handlers) -> - lists:map(fun(H) -> - {list_to_atom(H),[]} - end, Handlers) end, - [], Args), + EvHandlers = event_handler_args2opts(Args), + + %%! --- Mon May 31 23:16:45 2010 --- peppe was here! + io:format(user, "~nEvHandlers = ~p~n~n", [EvHandlers]), %% check flags and set corresponding application env variables @@ -505,7 +506,10 @@ install(Opts, LogDir) -> case whereis(ct_util_server) of undefined -> VarFile = variables_file_name(LogDir), + + %%! --- Tue Jun 1 00:20:33 2010 --- peppe was here! io:format("Varfile = ~p~n", [VarFile]), + case file:open(VarFile, [write]) of {ok,Fd} -> [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts], @@ -599,6 +603,10 @@ run_test1(StartOpts) -> end, Hs)) end, + %%! --- Mon May 31 23:16:45 2010 --- peppe was here! + io:format("~nEvHandlers = ~p~n~n", [EvHandlers]), + io:format(user, "~nEvHandlers = ~p~n~n", [EvHandlers]), + %% silent connections SilentConns = get_start_opt(silent_connections, fun(all) -> []; @@ -777,7 +785,7 @@ run_dir(Opts = #opts{logdir = LogDir, ok -> ok; {error,IReason} -> exit(IReason) end, - case lists:keysearch(dir, 1, Opts) of + case lists:keysearch(dir, 1, StartOpts) of {value,{_,Dirs=[Dir|_]}} when not is_integer(Dir), length(Dirs)>1 -> %% multiple dirs (no suite) @@ -790,11 +798,11 @@ run_dir(Opts = #opts{logdir = LogDir, (A) -> {".",A} end, - case lists:keysearch(suite, 1, Opts) of + case lists:keysearch(suite, 1, StartOpts) of {value,{_,Suite}} when is_integer(hd(Suite)) ; is_atom(Suite) -> {Dir,Mod} = S2M(Suite), - case listify(proplists:get_value(group, Opts, [])) ++ - listify(proplists:get_value(testcase, Opts, [])) of + case listify(proplists:get_value(group, StartOpts, [])) ++ + listify(proplists:get_value(testcase, StartOpts, [])) of [] -> do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts); GsAndCs -> @@ -806,13 +814,13 @@ run_dir(Opts = #opts{logdir = LogDir, exit(no_tests_specified) end; {value,{_,Dir}} -> - case lists:keysearch(suite, 1, Opts) of + case lists:keysearch(suite, 1, StartOpts) of {value,{_,Suite}} when is_integer(hd(Suite)) ; is_atom(Suite) -> Mod = if is_atom(Suite) -> Suite; true -> list_to_atom(Suite) end, - case listify(proplists:get_value(group, Opts, [])) ++ - listify(proplists:get_value(testcase, Opts, [])) of + case listify(proplists:get_value(group, StartOpts, [])) ++ + listify(proplists:get_value(testcase, StartOpts, [])) of [] -> do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts); GsAndCs -> @@ -1028,7 +1036,7 @@ do_run(Tests, Misc, LogDir) when is_list(Misc) -> CoverFile -> Opts#opts{cover = CoverFile} end, - do_run(Tests, [], Opts1#opts{tests = Tests, logdir = LogDir}, []). + do_run(Tests, [], Opts1#opts{logdir = LogDir}, []). do_run(Tests, Skip, Opts, Args) -> #opts{cover = Cover} = Opts, @@ -1759,46 +1767,29 @@ log_ts_names(Specs) -> [lists:flatten(List)]). merge_arguments(Args) -> - case proplists:get_value(ct_ignore, Args) of - undefined -> - merge_arguments(Args, [], []); - Ignore -> - merge_arguments(Args, [], [list_to_atom(I) || I <- Ignore]) - end. + merge_arguments(Args, []). -merge_arguments([LogDir={logdir,_}|Args], Merged, Ignore) -> - case lists:member(logdir, Ignore) of - true -> - merge_arguments(Args, Merged, Ignore); - false -> - merge_arguments(Args, handle_arg(replace, LogDir, Merged), Ignore) - end; -merge_arguments([CoverFile={cover,_}|Args], Merged, Ignore) -> - case lists:member(cover, Ignore) of - true -> - merge_arguments(Args, Merged, Ignore); - false -> - merge_arguments(Args, handle_arg(replace, CoverFile, Merged), Ignore) - end; -merge_arguments([{'case',TC}|Args], Merged, Ignore) -> - case lists:member('case', Ignore) of - true -> - merge_arguments(Args, Merged, Ignore); - false -> - merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged), Ignore) - end; -merge_arguments([Arg={Opt,_}|Args], Merged, Ignore) -> - case lists:member(Opt, Ignore) of - true -> - merge_arguments(Args, Merged, Ignore); - false -> - merge_arguments(Args, handle_arg(merge, Arg, Merged), Ignore) - end; -merge_arguments([], Merged, _Ignore) -> +merge_arguments([LogDir={logdir,_}|Args], Merged) -> + merge_arguments(Args, handle_arg(replace, LogDir, Merged)); + +merge_arguments([CoverFile={cover,_}|Args], Merged) -> + merge_arguments(Args, handle_arg(replace, CoverFile, Merged)); + +merge_arguments([{'case',TC}|Args], Merged) -> + merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged)); + +merge_arguments([Arg|Args], Merged) -> + merge_arguments(Args, handle_arg(merge, Arg, Merged)); + +merge_arguments([], Merged) -> Merged. handle_arg(replace, {Key,Elems}, [{Key,_}|Merged]) -> [{Key,Elems}|Merged]; +handle_arg(merge, {event_handler_init,Elems}, [{event_handler_init,PrevElems}|Merged]) -> + [{event_handler_init,PrevElems++["add"|Elems]}|Merged]; +handle_arg(merge, {userconfig,Elems}, [{userconfig,PrevElems}|Merged]) -> + [{userconfig,PrevElems++["add"|Elems]}|Merged]; handle_arg(merge, {Key,Elems}, [{Key,PrevElems}|Merged]) -> [{Key,PrevElems++Elems}|Merged]; handle_arg(Op, Arg, [Other|Merged]) -> @@ -1823,12 +1814,47 @@ get_start_opt(Key, IfExists, IfNotExists, Args) -> IfNotExists end. +event_handler_args2opts(Args) -> + case proplists:get_value(event_handler, Args) of + undefined -> + event_handler_args2opts([], Args); + EHs -> + event_handler_args2opts([{list_to_atom(EH),[]} || EH <- EHs], Args) + end. +event_handler_args2opts(Default, Args) -> + case proplists:get_value(event_handler_init, Args) of + undefined -> + Default; + EHs -> + event_handler_init_args2opts(EHs) + end. +event_handler_init_args2opts([EH, Arg, "and" | EHs]) -> + [{list_to_atom(EH),lists:flatten(io_lib:format("~s",[Arg]))} | + event_handler_init_args2opts(EHs)]; +event_handler_init_args2opts([EH, Arg]) -> + [{list_to_atom(EH),lists:flatten(io_lib:format("~s",[Arg]))}]; +event_handler_init_args2opts([]) -> + []. + %% this function translates ct:run_test/1 start options %% to run_test start arguments (on the init arguments format) - %% this is useful mainly for testing the ct_run start functions opts2args(EnvStartOpts) -> lists:flatmap(fun({config,CfgFiles}) -> - [{ct_config,CfgFiles}]; + [{ct_config,[CfgFiles]}]; + ({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) -> + [{userconfig,[atom_to_list(CBM),CfgStr]}]; + ({userconfig,{CBM,CfgStrs}}) when is_list(CfgStrs) -> + [{userconfig,[atom_to_list(CBM) | CfgStrs]}]; + ({userconfig,UserCfg}) when is_list(UserCfg) -> + Strs = + lists:map(fun({CBM,CfgStr=[X|_]}) when is_integer(X) -> + [atom_to_list(CBM),CfgStr,"and"]; + ({CBM,CfgStrs}) when is_list(CfgStrs) -> + [atom_to_list(CBM) | CfgStrs] ++ ["and"] + end, UserCfg), + [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)), + [{userconfig,lists:reverse(StrsR)}]; ({testcase,Cases}) -> [{'case',[atom_to_list(C) || C <- Cases]}]; ({'case',Cases}) -> @@ -1850,13 +1876,37 @@ opts2args(EnvStartOpts) -> ({force_stop,false}) -> []; ({decrypt,{key,Key}}) -> - [{ct_decrypt_key,Key}]; + [{ct_decrypt_key,[Key]}]; ({decrypt,{file,File}}) -> - [{ct_decrypt_file,File}]; + [{ct_decrypt_file,[File]}]; ({basic_html,true}) -> ({basic_html,[]}); ({basic_html,false}) -> []; + ({event_handler,EH}) when is_atom(EH) -> + [{event_handler,[atom_to_list(EH)]}]; + ({event_handler,EHs}) when is_list(EHs) -> + [{event_handler,[atom_to_list(EH) || EH <- EHs]}]; + ({event_handler,{EH,Arg}}) when is_atom(EH) -> + ArgStr = lists:flatten(io_lib:format("~p", [Arg])), + [{event_handler_init,[atom_to_list(EH),ArgStr]}]; + ({event_handler,{EHs,Arg}}) when is_list(EHs) -> + ArgStr = lists:flatten(io_lib:format("~p", [Arg])), + Strs = lists:map(fun(EH) -> + [atom_to_list(EH),ArgStr,"and"] + end, EHs), + [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)), + [{event_handler_init,lists:reverse(StrsR)}]; + ({Opt,As=[A|_]}) when is_atom(A) -> + [{Opt,[atom_to_list(Atom) || Atom <- As]}]; + ({Opt,Strs=[S|_]}) when is_list(S) -> + [{Opt,[Strs]}]; + ({Opt,A}) when is_atom(A) -> + [{Opt,[atom_to_list(A)]}]; + ({Opt,I}) when is_integer(I) -> + [{Opt,[integer_to_list(I)]}]; + ({Opt,S}) when is_list(S) -> + [{Opt,[S]}]; (Opt) -> Opt end, EnvStartOpts). diff --git a/lib/common_test/test/ct_smoke_test_SUITE.erl b/lib/common_test/test/ct_smoke_test_SUITE.erl index f1c695f614..76136b1d69 100644 --- a/lib/common_test/test/ct_smoke_test_SUITE.erl +++ b/lib/common_test/test/ct_smoke_test_SUITE.erl @@ -162,7 +162,7 @@ dir1(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -191,7 +191,7 @@ dir2(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -221,7 +221,7 @@ dir1_2(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -251,7 +251,7 @@ suite11(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -280,7 +280,7 @@ suite21(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -311,7 +311,7 @@ suite11_21(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -342,7 +342,7 @@ tc111(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -372,7 +372,7 @@ tc211(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -403,7 +403,7 @@ tc111_112(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + {ok,ok} = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 0ce103e111..c3dc706d9b 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -28,7 +28,7 @@ -export([init_per_suite/1, init_per_suite/2, end_per_suite/1, init_per_testcase/2, end_per_testcase/2, write_testspec/3, - run/4, get_opts/1, wait_for_ct_stop/1]). + run/2, run/4, get_opts/1, wait_for_ct_stop/1]). -export([handle_event/2, start_event_receiver/1, get_events/2, verify_events/3, reformat/2, log_events/3]). @@ -172,6 +172,23 @@ get_opts(Config) -> %%%----------------------------------------------------------------- %%% +run(Opts, Config) -> + CTNode = ?config(ct_node, Config), + Level = ?config(trace_level, Config), + %% use ct interface + test_server:format(Level, "Calling ct:run_test(~p) on ~p~n", + [Opts, CTNode]), + Result1 = rpc:call(CTNode, ct, run_test, [Opts]), + + %% use run_test interface (simulated) + test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]), + rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]), + test_server:format(Level, "Calling ct_run:script_start() on ~p~n", [CTNode]), + Result2 = rpc:call(CTNode, ct_run, script_start, []), + + {Result1,Result2}. + + run(M, F, A, Config) -> CTNode = ?config(ct_node, Config), Level = ?config(trace_level, Config), diff --git a/lib/common_test/test/ct_test_support_eh.erl b/lib/common_test/test/ct_test_support_eh.erl index fd3ae18746..1454d20e47 100644 --- a/lib/common_test/test/ct_test_support_eh.erl +++ b/lib/common_test/test/ct_test_support_eh.erl @@ -44,8 +44,20 @@ %% Description: Whenever a new event handler is added to an event manager, %% this function is called to initialize the event handler. %%-------------------------------------------------------------------- +init(String = [X|_]) when is_integer(X) -> + case erl_scan:string(String++".") of + {ok,Ts,_} -> + case erl_parse:parse_term(Ts) of + {ok,Args} -> + init(Args); + _ -> + init(String) + end; + _ -> + init(String) + end; + init(Args) -> - S1 = case lists:keysearch(cbm, 1, Args) of {_,{cbm,CBM}} -> #state{cbm=CBM}; @@ -58,7 +70,8 @@ init(Args) -> _ -> S1 end, - print(S2#state.trace_level, "Event Handler ~w started!~n", [?MODULE]), + print(S2#state.trace_level, "Event Handler ~w started with ~p~n", + [?MODULE,Args]), {ok,S2}. %%-------------------------------------------------------------------- -- cgit v1.2.3 From 5d01bdc2c4c3cc18150711ebfab4c84abdfc0b45 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 3 Jun 2010 14:40:16 +0200 Subject: Improve and fix various test suites --- lib/common_test/src/ct_run.erl | 58 +++++++++-------- lib/common_test/src/ct_testspec.erl | 38 +++++++++-- lib/common_test/src/ct_util.hrl | 4 +- lib/common_test/test/ct_config_SUITE.erl | 67 ++++++++++++------- lib/common_test/test/ct_error_SUITE.erl | 21 ++++-- lib/common_test/test/ct_event_handler_SUITE.erl | 9 ++- .../test/ct_event_handler_SUITE_data/eh_A.erl | 13 ++++ lib/common_test/test/ct_groups_test_1_SUITE.erl | 68 ++++++++----------- .../groups_1/test/groups_12_SUITE.erl | 4 ++ lib/common_test/test/ct_groups_test_2_SUITE.erl | 32 ++++++++- lib/common_test/test/ct_master_SUITE.erl | 11 ++-- lib/common_test/test/ct_skip_SUITE.erl | 17 +++-- lib/common_test/test/ct_smoke_test_SUITE.erl | 52 ++++++++------- lib/common_test/test/ct_test_server_if_1_SUITE.erl | 13 +++- lib/common_test/test/ct_test_support.erl | 76 ++++++++++++++++++---- 15 files changed, 324 insertions(+), 159 deletions(-) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index ec9351f346..7037ac7c1e 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -76,25 +76,32 @@ script_start() -> Init = init:get_arguments(), CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false; (_) -> true end, Init), - Args = case application:get_env(common_test, run_test_start_opts) of - {ok,EnvStartOpts} -> - %%! --- Mon May 31 22:59:29 2010 --- peppe was here! - io:format(user, "~nEnv1:~n~p~n~n~p~n", [EnvStartOpts,opts2args(EnvStartOpts)]), - - merge_arguments(CtArgs ++ opts2args(EnvStartOpts)); - _ -> - merge_arguments(CtArgs) - end, - - %%! --- Mon May 31 12:32:48 2010 --- peppe was here! - io:format(user, "~nInit:~n~p~n", [Init]), - - %%! --- Mon May 31 12:32:48 2010 --- peppe was here! - io:format(user, "~nCtArgs:~n~p~n", [CtArgs]), - - %%! --- Mon May 31 12:32:48 2010 --- peppe was here! - io:format(user, "~nArgs:~n~p~n", [Args]), - + Args = + case application:get_env(common_test, run_test_start_opts) of + {ok,EnvStartOpts} -> + FlagFilter = fun(Flags) -> + lists:filter(fun({root,_}) -> false; + ({progname,_}) -> false; + ({home,_}) -> false; + ({noshell,_}) -> false; + ({noinput,_}) -> false; + (_) -> true + end, Flags) + end, + %% used for purpose of testing the run_test interface + io:format(user, "~n--------------- START ARGS ---------------~n", []), + io:format(user, "--- Init args:~n~p~n", [FlagFilter(Init)]), + io:format(user, "--- CT args:~n~p~n", [FlagFilter(CtArgs)]), + EnvArgs = opts2args(EnvStartOpts), + io:format(user, "--- Env opts -> args:~n~p~n =>~n~p~n", + [EnvStartOpts,EnvArgs]), + Merged = merge_arguments(CtArgs ++ EnvArgs), + io:format(user, "--- Merged args:~n~p~n", [FlagFilter(Merged)]), + io:format(user, "------------------------------------------~n~n", []), + Merged; + _ -> + merge_arguments(CtArgs) + end, case proplists:get_value(help, Args) of undefined -> script_start(Args); _ -> script_usage() @@ -151,9 +158,6 @@ script_start1(Parent, Args) -> ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, false, Args), EvHandlers = event_handler_args2opts(Args), - %%! --- Mon May 31 23:16:45 2010 --- peppe was here! - io:format(user, "~nEvHandlers = ~p~n~n", [EvHandlers]), - %% check flags and set corresponding application env variables %% ct_decrypt_key | ct_decrypt_file @@ -603,10 +607,6 @@ run_test1(StartOpts) -> end, Hs)) end, - %%! --- Mon May 31 23:16:45 2010 --- peppe was here! - io:format("~nEvHandlers = ~p~n~n", [EvHandlers]), - io:format(user, "~nEvHandlers = ~p~n~n", [EvHandlers]), - %% silent connections SilentConns = get_start_opt(silent_connections, fun(all) -> []; @@ -684,7 +684,7 @@ run_test1(StartOpts) -> run_prepared(Run, Skip, Opts#opts{testspecs = Specs}, StartOpts) end; Specs -> - Relaxed = get_start_opt(allow_user_term, value, false), + Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts), %% using testspec(s) as input for test run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts) end. @@ -1855,6 +1855,8 @@ opts2args(EnvStartOpts) -> end, UserCfg), [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)), [{userconfig,lists:reverse(StrsR)}]; + ({testcase,Case}) when is_atom(Case) -> + [{'case',[atom_to_list(Case)]}]; ({testcase,Cases}) -> [{'case',[atom_to_list(C) || C <- Cases]}]; ({'case',Cases}) -> @@ -1900,7 +1902,7 @@ opts2args(EnvStartOpts) -> ({Opt,As=[A|_]}) when is_atom(A) -> [{Opt,[atom_to_list(Atom) || Atom <- As]}]; ({Opt,Strs=[S|_]}) when is_list(S) -> - [{Opt,[Strs]}]; + [{Opt,Strs}]; ({Opt,A}) when is_atom(A) -> [{Opt,[atom_to_list(A)]}]; ({Opt,I}) when is_integer(I) -> diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 582ce5e49f..1ab9242e4b 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -17,7 +17,7 @@ %% %CopyrightEnd% %% -%%% @doc Common Test Framework functions handlig test specifications. +%%% @doc Common Test Framework functions handling test specifications. %%% %%%

This module exports functions that are used within CT to %%% scan and parse test specifikations.

@@ -384,7 +384,7 @@ filter_init_terms([{init, [], _}|Ts], NewTerms, Spec)-> filter_init_terms([Term|Ts], NewTerms, Spec)-> filter_init_terms(Ts, [Term|NewTerms], Spec); filter_init_terms([], NewTerms, Spec)-> - {NewTerms, Spec}. + {lists:reverse(NewTerms), Spec}. add_option([], _, List, _)-> List; @@ -472,6 +472,36 @@ add_tests([{cover,Node,File}|Ts],Spec) -> add_tests([{cover,File}|Ts],Spec) -> add_tests([{cover,all_nodes,File}|Ts],Spec); +%% --- multiply_timetraps --- +add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec) -> + Tests = lists:map(fun(N) -> {multiply_timetraps,N,MT} end, list_nodes(Spec)), + add_tests(Tests++Ts,Spec); +add_tests([{multiply_timetraps,Nodes,MT}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,multiply_timetraps,[MT],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{multiply_timetraps,Node,MT}|Ts],Spec) -> + MTs = Spec#testspec.multiply_timetraps, + MTs1 = [{ref2node(Node,Spec#testspec.nodes),MT} | + lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,MTs)], + add_tests(Ts,Spec#testspec{multiply_timetraps=MTs1}); +add_tests([{multiply_timetraps,MT}|Ts],Spec) -> + add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec); + +%% --- scale_timetraps --- +add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec) -> + Tests = lists:map(fun(N) -> {scale_timetraps,N,ST} end, list_nodes(Spec)), + add_tests(Tests++Ts,Spec); +add_tests([{scale_timetraps,Nodes,ST}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,scale_timetraps,[ST],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{scale_timetraps,Node,ST}|Ts],Spec) -> + STs = Spec#testspec.scale_timetraps, + STs1 = [{ref2node(Node,Spec#testspec.nodes),ST} | + lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,STs)], + add_tests(Ts,Spec#testspec{scale_timetraps=STs1}); +add_tests([{scale_timetraps,ST}|Ts],Spec) -> + add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec); + %% --- config --- add_tests([{config,all_nodes,Files}|Ts],Spec) -> Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)), @@ -895,7 +925,3 @@ common_letters([L|Ls],Term,Count) -> end; common_letters([],_,Count) -> Count. - - - - diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index d9c5631f88..54eed29415 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -36,8 +36,8 @@ userconfig=[], event_handler=[], include=[], - multiply_timetraps, - scale_timetraps, + multiply_timetraps=[], + scale_timetraps=[], alias=[], tests=[]}). diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 7c0c88e76b..0722d137eb 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -44,8 +44,13 @@ %% there will be clashes with logging processes etc). %%-------------------------------------------------------------------- init_per_suite(Config) -> - Config1 = ct_test_support:init_per_suite(Config), - Config1. + DataDir = ?config(data_dir, Config), + PathDir = filename:join(DataDir, "config/test"), + Config1 = ct_test_support:init_per_suite([{path_dirs,[PathDir]} | Config]), + PrivDir = ?config(priv_dir, Config1), + ConfigDir = filename:join(PrivDir, "config"), + ok = file:make_dir(ConfigDir), + [{config_dir,ConfigDir} | Config1]. end_per_suite(Config) -> ct_test_support:end_per_suite(Config). @@ -94,46 +99,49 @@ userconfig_dynamic(Config) when is_list(Config) -> testspec_legacy(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), - make_spec(DataDir, - "config/spec_legacy.spec", - [config_static_SUITE], - [{config, filename:join(DataDir, "config/config.txt")}]), + ConfigDir = ?config(config_dir, Config), + make_spec(DataDir, ConfigDir, + "spec_legacy.spec", + [config_static_SUITE], + [{config, filename:join(DataDir, "config/config.txt")}]), run_test(config_static_SUITE, Config, - {spec, filename:join(DataDir, "config/spec_legacy.spec")}, + {spec, filename:join(ConfigDir, "spec_legacy.spec")}, []), - file:delete(filename:join(DataDir, "config/spec_legacy.spec")). + file:delete(filename:join(ConfigDir, "spec_legacy.spec")). testspec_static(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), - make_spec(DataDir, - "config/spec_static.spec", - [config_static_SUITE], - [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]), + ConfigDir = ?config(config_dir, Config), + make_spec(DataDir, ConfigDir, + "spec_static.spec", + [config_static_SUITE], + [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]), run_test(config_static_SUITE, Config, - {spec, filename:join(DataDir, "config/spec_static.spec")}, + {spec, filename:join(ConfigDir, "spec_static.spec")}, []), - file:delete(filename:join(DataDir, "config/spec_static.spec")). + file:delete(filename:join(ConfigDir, "spec_static.spec")). testspec_dynamic(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), - make_spec(DataDir, "config/spec_dynamic.spec", - [config_dynamic_SUITE], - [{userconfig, {config_driver, "config_server"}}]), + ConfigDir = ?config(config_dir, Config), + make_spec(DataDir, ConfigDir, "spec_dynamic.spec", + [config_dynamic_SUITE], + [{userconfig, {config_driver, "config_server"}}]), run_test(config_dynamic_SUITE, Config, - {spec, filename:join(DataDir, "config/spec_dynamic.spec")}, + {spec, filename:join(ConfigDir, "spec_dynamic.spec")}, []), - file:delete(filename:join(DataDir, "config/spec_dynamic.spec")). + file:delete(filename:join(ConfigDir, "spec_dynamic.spec")). %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- -make_spec(DataDir, Filename, Suites, Config)-> - {ok, Fd} = file:open(filename:join(DataDir, Filename), [write]), +make_spec(DataDir, ConfigDir, Filename, Suites, Config)-> + {ok, Fd} = file:open(filename:join(ConfigDir, Filename), [write]), ok = file:write(Fd, - io_lib:format("{suites, \"~sconfig/test/\", ~p}.~n", [DataDir, Suites])), + io_lib:format("{suites, \"~sconfig/test/\", ~p}.~n", [DataDir, Suites])), lists:foreach(fun(C)-> ok=file:write(Fd, io_lib:format("~p.~n", [C])) end, Config), ok = file:close(Fd). @@ -142,12 +150,12 @@ run_test(Name, Config, CTConfig, SuiteNames)-> Joiner = fun(Suite) -> filename:join(DataDir, "config/test/"++Suite) end, Suites = lists:map(Joiner, SuiteNames), {Opts,ERPid} = setup_env({suite,Suites}, Config, CTConfig), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), TestEvents = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(Name, reformat_events(TestEvents, ?eh), - ?config(priv_dir, Config)), - ExpEvents = expected_events(Name), + ?config(config_dir, Config)), + ExpEvents = events_to_check(Name), ok = ct_test_support:verify_events(ExpEvents, TestEvents, Config). setup_env(Test, Config, CTConfig) -> @@ -164,6 +172,15 @@ reformat_events(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + expected_events(Test) ++ events_to_check(Test, N-1). + expected_events(config_static_SUITE)-> [ {?eh,start_logging,{'DEF','RUNDIR'}}, diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index be75d768fc..d5d91706d9 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -87,14 +87,14 @@ cfg_error(Config) when is_list(Config) -> Join(DataDir, "cfg_error_9_SUITE") ], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(cfg_error, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(cfg_error), + TestEvents = events_to_check(cfg_error), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -105,14 +105,14 @@ lib_error(Config) when is_list(Config) -> Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, Suites = [Join(DataDir, "lib_error_1_SUITE")], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(lib_error, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(lib_error), + TestEvents = events_to_check(lib_error), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -123,14 +123,14 @@ no_compile(Config) when is_list(Config) -> Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, Suites = [Join(DataDir, "no_compile_SUITE")], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(no_compile, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(no_compile), + TestEvents = events_to_check(no_compile), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -154,6 +154,15 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + test_events(cfg_error) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, diff --git a/lib/common_test/test/ct_event_handler_SUITE.erl b/lib/common_test/test/ct_event_handler_SUITE.erl index bafd32f937..00a4c4ded3 100644 --- a/lib/common_test/test/ct_event_handler_SUITE.erl +++ b/lib/common_test/test/ct_event_handler_SUITE.erl @@ -88,7 +88,7 @@ start_stop(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -110,8 +110,7 @@ start_stop(Config) when is_list(Config) -> {eh_A,test_done,{'DEF','STOP_TIME'}}, {eh_A,stop_logging,[]}], - ok = ct_test_support:verify_events(TestEvents, Events, Config), - {comment,"NOTE! Known problem with test_start event!"}. + ok = ct_test_support:verify_events(TestEvents++TestEvents, Events, Config). results(doc) -> @@ -135,7 +134,7 @@ results(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -163,7 +162,7 @@ results(Config) when is_list(Config) -> {eh_A,test_done,{'DEF','STOP_TIME'}}, {eh_A,stop_logging,[]}], - ok = ct_test_support:verify_events(TestEvents, Events, Config). + ok = ct_test_support:verify_events(TestEvents++TestEvents, Events, Config). %%%----------------------------------------------------------------- diff --git a/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl b/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl index 6e526f15a2..cb6d4c3aaa 100644 --- a/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl +++ b/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl @@ -44,6 +44,19 @@ %% Description: Whenever a new event handler is added to an event manager, %% this function is called to initialize the event handler. %%-------------------------------------------------------------------- +init(String = [X|_]) when is_integer(X) -> + case erl_scan:string(String++".") of + {ok,Ts,_} -> + case erl_parse:parse_term(Ts) of + {ok,Args} -> + init(Args); + _ -> + init(String) + end; + _ -> + init(String) + end; + init(Args) -> S1 = case lists:keysearch(cbm, 1, Args) of diff --git a/lib/common_test/test/ct_groups_test_1_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE.erl index 1761b773f5..18a00f7f2b 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE.erl @@ -76,14 +76,14 @@ groups_suite_1(Config) when is_list(Config) -> Suite = filename:join(DataDir, "groups_1/test/groups_11_SUITE"), {Opts,ERPid} = setup({suite,Suite}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suite_1, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(groups_suite_1), + TestEvents = events_to_check(groups_suite_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -96,14 +96,14 @@ groups_suite_2(Config) when is_list(Config) -> Suite = filename:join(DataDir, "groups_1/test/groups_12_SUITE"), {Opts,ERPid} = setup({suite,Suite}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suite_2, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(groups_suite_2), + TestEvents = events_to_check(groups_suite_2), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -117,14 +117,14 @@ groups_suites_1(Config) when is_list(Config) -> filename:join(DataDir, "groups_1/test/groups_12_SUITE")], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_suites_1, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(groups_suites_1), + TestEvents = events_to_check(groups_suites_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -137,14 +137,14 @@ groups_dir_1(Config) when is_list(Config) -> Dir = filename:join(DataDir, "groups_1"), {Opts,ERPid} = setup({dir,Dir}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_dir_1, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(groups_dir_1), + TestEvents = events_to_check(groups_dir_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -157,14 +157,14 @@ groups_dirs_1(Config) when is_list(Config) -> filename:join(DataDir, "groups_2")], {Opts,ERPid} = setup({dir,Dirs}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(groups_dirs_1, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(groups_dirs_1), + TestEvents = events_to_check(groups_dirs_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -188,6 +188,14 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). test_events(groups_suite_1) -> [{?eh,start_logging,{'DEF','RUNDIR'}}, @@ -361,12 +369,8 @@ test_events(groups_suite_2) -> {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}}, - - %% the done event could come in during the parallel subgroup - %% and we can't test that, yet... - %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, - %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, - + {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, + {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}}, @@ -555,12 +559,8 @@ test_events(groups_suites_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}}, - - %% the done event could come in during the parallel subgroup - %% and we can't test that, yet... - %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, - %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, - + {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, + {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}}, [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}}, @@ -745,12 +745,8 @@ test_events(groups_dir_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}}, - - %% the done event could come in during the parallel subgroup - %% and we can't test that, yet... - %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, - %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, - + {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, + {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}}, [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}}, @@ -936,12 +932,8 @@ test_events(groups_dirs_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}}, - - %% the done event could come in during the parallel subgroup - %% and we can't test that, yet... - %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, - %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, - + {?eh,tc_start,{groups_12_SUITE,testcase_5a}}, + {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}}, {parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}}, [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}}, @@ -1181,12 +1173,8 @@ test_events(groups_dirs_1) -> {groups_22_SUITE,{init_per_group,test_group_5,[parallel]}}}, {?eh,tc_done, {groups_22_SUITE,{init_per_group,test_group_5,[parallel]},ok}}, - - %% the done event could come in during the parallel subgroup - %% and we can't test that, yet... - %% {?eh,tc_start,{groups_22_SUITE,testcase_5a}}, - %% {?eh,tc_done,{groups_22_SUITE,testcase_5a,ok}}, - + {?eh,tc_start,{groups_22_SUITE,testcase_5a}}, + {?eh,tc_done,{groups_22_SUITE,testcase_5a,ok}}, {parallel, [{?eh,tc_start, {groups_22_SUITE,{init_per_group,test_group_6,[parallel]}}}, diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl index b261ef581f..22eacde1f3 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl @@ -275,6 +275,10 @@ testcase_5a(Config) -> test_group_5 = ?config(test_group_5,Config), undefined = ?config(testcase_3,Config), testcase_5a = ?config(testcase_5a,Config), + %% increase chance the done event will come + %% during execution of subgroup (could be + %% tricky to handle) + timer:sleep(3), ok. testcase_5b() -> []. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl index 5a60d855b7..75348e2f4a 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl @@ -75,14 +75,14 @@ missing_conf(Config) when is_list(Config) -> Suite = filename:join(DataDir, "groups_1/missing_conf_SUITE"), {Opts,ERPid} = setup({suite,Suite}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(missing_conf_SUITE, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(missing_conf), + TestEvents = events_to_check(missing_conf), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -105,6 +105,32 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). test_events(missing_conf) -> - exit(must_handle_this). + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,2}}, + {?eh,tc_start,{ct_framework,ct_init_per_group}}, + {?eh,tc_done,{ct_framework,ct_init_per_group,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{missing_conf_SUITE,tc1}}, + {?eh,tc_done,{missing_conf_SUITE,tc1,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + {?eh,tc_start,{missing_conf_SUITE,tc2}}, + {?eh,tc_done,{missing_conf_SUITE,tc2,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{ct_framework,ct_end_per_group}}, + {?eh,tc_done,{ct_framework,ct_end_per_group,ok}}, + {?eh,test_stats,{4,0,{0,0}}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]. diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl index 2d4a29cde1..8d3ed8c45c 100644 --- a/lib/common_test/test/ct_master_SUITE.erl +++ b/lib/common_test/test/ct_master_SUITE.erl @@ -69,13 +69,14 @@ all(suite) -> ct_master_test(Config) when is_list(Config)-> NodeCount = 5, DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) || N <- lists:seq(1, NodeCount)], - FileName = filename:join(DataDir, "ct_master_spec.spec"), + FileName = filename:join(PrivDir, "ct_master_spec.spec"), Suites = [master_SUITE], TSFile = make_spec(DataDir, FileName, NodeNames, Suites, Config), [{TSFile, ok}] = run_test(ct_master_test, FileName, Config), - file:delete(filename:join(DataDir, FileName)). + ok. %%%----------------------------------------------------------------- %%% HELP FUNCTIONS @@ -91,9 +92,9 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> C = lists:map(fun(NodeName)-> Rnd = random:uniform(2), if Rnd == 1-> - {config, NodeName, "master/config.txt"}; + {config, NodeName, filename:join(DataDir, "master/config.txt")}; true-> - {userconfig, NodeName, {ct_config_xml, "master/config.xml"}} + {userconfig, NodeName, {ct_config_xml, filename:join(DataDir, "master/config.xml")}} end end, NodeNames), @@ -115,7 +116,7 @@ make_spec(DataDir, FileName, NodeNames, Suites, Config)-> end, NodeNames) ++ [{logdir, master, PrivDir}], - ct_test_support:write_testspec(N++C++S++LD++NS, DataDir, FileName). + ct_test_support:write_testspec(N++C++S++LD++NS, FileName). get_log_dir(PrivDir, NodeName)-> LogDir = filename:join(PrivDir, io_lib:format("slave.~p", [NodeName])), diff --git a/lib/common_test/test/ct_skip_SUITE.erl b/lib/common_test/test/ct_skip_SUITE.erl index 9f428723f5..2e02061dec 100644 --- a/lib/common_test/test/ct_skip_SUITE.erl +++ b/lib/common_test/test/ct_skip_SUITE.erl @@ -89,14 +89,14 @@ auto_skip(Config) when is_list(Config) -> ], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(auto_skip, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(auto_skip), + TestEvents = events_to_check(auto_skip), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -112,14 +112,14 @@ user_skip(Config) when is_list(Config) -> Join(DataDir, "user_skip_5_SUITE")], {Opts,ERPid} = setup({suite,Suites}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(user_skip, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(user_skip), + TestEvents = events_to_check(user_skip), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -142,6 +142,15 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + test_events(auto_skip) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, diff --git a/lib/common_test/test/ct_smoke_test_SUITE.erl b/lib/common_test/test/ct_smoke_test_SUITE.erl index 76136b1d69..05a2c20695 100644 --- a/lib/common_test/test/ct_smoke_test_SUITE.erl +++ b/lib/common_test/test/ct_smoke_test_SUITE.erl @@ -162,7 +162,7 @@ dir1(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -170,7 +170,7 @@ dir1(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(dir1), + TestEvents = events_to_check(dir1), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -191,7 +191,7 @@ dir2(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -199,7 +199,7 @@ dir2(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(dir2), + TestEvents = events_to_check(dir2), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -221,7 +221,7 @@ dir1_2(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -229,7 +229,7 @@ dir1_2(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(dir1_2), + TestEvents = events_to_check(dir1_2), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -251,7 +251,7 @@ suite11(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -259,7 +259,7 @@ suite11(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(suite11), + TestEvents = events_to_check(suite11), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -280,7 +280,7 @@ suite21(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -288,7 +288,7 @@ suite21(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(suite21), + TestEvents = events_to_check(suite21), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -311,7 +311,7 @@ suite11_21(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -319,7 +319,7 @@ suite11_21(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(suite11_21), + TestEvents = events_to_check(suite11_21), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -342,7 +342,7 @@ tc111(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -350,7 +350,7 @@ tc111(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(tc111), + TestEvents = events_to_check(tc111), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -372,7 +372,7 @@ tc211(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -380,7 +380,7 @@ tc211(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(tc211), + TestEvents = events_to_check(tc211), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- @@ -403,7 +403,7 @@ tc111_112(Config) when is_list(Config) -> ERPid = ct_test_support:start_event_receiver(Config), - {ok,ok} = ct_test_support:run(Opts, Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -411,7 +411,7 @@ tc111_112(Config) when is_list(Config) -> ct_test_support:reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = test_events(tc111_112), + TestEvents = events_to_check(tc111_112), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -423,8 +423,16 @@ eh_opts(Config) -> Level = ?config(trace_level, Config), [{event_handler,{?eh,[{cbm,ct_test_support},{trace_level,Level}]}}]. +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). -test_events(Test) when Test == dir1 ; Test == dir2 ; +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + events(Test) ++ events_to_check(Test, N-1). + +events(Test) when Test == dir1 ; Test == dir2 ; Test == suite11 ; Test == suite21 -> Suite = if Test == dir1 ; Test == suite11 -> happy_11_SUITE; true -> happy_21_SUITE @@ -465,7 +473,7 @@ test_events(Test) when Test == dir1 ; Test == dir2 ; {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} ]; -test_events(Test) when Test == dir1_2 ; Test == suite11_21 -> +events(Test) when Test == dir1_2 ; Test == suite11_21 -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, @@ -532,7 +540,7 @@ test_events(Test) when Test == dir1_2 ; Test == suite11_21 -> {?eh,stop_logging,[]} ]; -test_events(Test) when Test == tc111 ; Test == tc211 -> +events(Test) when Test == tc111 ; Test == tc211 -> Suite = if Test == tc111 -> happy_11_SUITE; true -> happy_21_SUITE end, [ {?eh,start_logging,{'DEF','RUNDIR'}}, @@ -549,7 +557,7 @@ test_events(Test) when Test == tc111 ; Test == tc211 -> {?eh,stop_logging,[]} ]; -test_events(tc111_112) -> +events(tc111_112) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE.erl index 069f8c75fc..8b46a30cdc 100644 --- a/lib/common_test/test/ct_test_server_if_1_SUITE.erl +++ b/lib/common_test/test/ct_test_server_if_1_SUITE.erl @@ -87,14 +87,14 @@ ts_if_1(Config) when is_list(Config) -> TestSpecName = ct_test_support:write_testspec(TestSpec, PrivDir, "ts_if_1_spec"), {Opts,ERPid} = setup({spec,TestSpecName}, Config), - ok = ct_test_support:run(ct, run_test, [Opts], Config), + ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), ct_test_support:log_events(ts_if_1, reformat(Events, ?eh), PrivDir), - TestEvents = test_events(ts_if_1), + TestEvents = events_to_check(ts_if_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). @@ -119,6 +119,15 @@ reformat(Events, EH) -> %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + test_events(ts_if_1) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index c3dc706d9b..c7c7384847 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -27,7 +27,8 @@ -include_lib("common_test/include/ct_event.hrl"). -export([init_per_suite/1, init_per_suite/2, end_per_suite/1, - init_per_testcase/2, end_per_testcase/2, write_testspec/3, + init_per_testcase/2, end_per_testcase/2, + write_testspec/2, write_testspec/3, run/2, run/4, get_opts/1, wait_for_ct_stop/1]). -export([handle_event/2, start_event_receiver/1, get_events/2, @@ -66,12 +67,17 @@ init_per_suite(Config, Level) -> %% PrivDir as well as directory of Test Server suites %% have to be in code path on Common Test node. - true = rpc:call(CTNode, code, add_patha, [PrivDir]), [_ | Parts] = lists:reverse(filename:split(DataDir)), TSDir = filename:join(lists:reverse(Parts)), - true = rpc:call(CTNode, code, add_patha, [TSDir]), - test_server:format(Level, "Dirs added to code path (on ~w):~n" - "~s~n~s~n", [CTNode,TSDir,PrivDir]), + AddPathDirs = case ?config(path_dirs, Config) of + undefined -> []; + Ds -> Ds + end, + PathDirs = [PrivDir,TSDir | AddPathDirs], + [true = rpc:call(CTNode, code, add_patha, [D]) || D <- PathDirs], + test_server:format(Level, "Dirs added to code path (on ~w):~n", + [CTNode]), + [io:format("~s~n", [D]) || D <- PathDirs], TraceFile = filename:join(DataDir, "ct.trace"), case file:read_file_info(TraceFile) of @@ -125,9 +131,10 @@ end_per_testcase(_TestCase, Config) -> %%%----------------------------------------------------------------- %%% - write_testspec(TestSpec, Dir, Name) -> - TSFile = filename:join(Dir, Name), + write_testspec(TestSpec, filename:join(Dir, Name)). + +write_testspec(TestSpec, TSFile) -> {ok,Dev} = file:open(TSFile, [write]), [io:format(Dev, "~p.~n", [Entry]) || Entry <- TestSpec], file:close(Dev), @@ -185,9 +192,14 @@ run(Opts, Config) -> rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]), test_server:format(Level, "Calling ct_run:script_start() on ~p~n", [CTNode]), Result2 = rpc:call(CTNode, ct_run, script_start, []), - - {Result1,Result2}. - + case {Result1,Result2} of + {ok,ok} -> + ok; + {E,_} when E =/= ok -> + E; + {_,E} when E =/= ok -> + E + end. run(M, F, A, Config) -> CTNode = ?config(ct_node, Config), @@ -262,6 +274,11 @@ verify_events(TEvs, Evs, Config) -> ok end. +verify_events1([TestEv|_], [{TEH,#event{name=stop_logging,node=Node,data=_}}|_], Node, _) + when element(1,TestEv) == TEH, element(2,TestEv) =/= stop_logging -> + test_server:format("Failed to find ~p in the list of events!~n", [TestEv]), + exit({event_not_found,TestEv}); + verify_events1(TEvs = [TestEv | TestEvs], Evs = [_|Events], Node, Config) -> %% test_server:format("Next expected event: ~p~n", [TestEv]), case catch locate(TestEv, Node, Evs, Config) of @@ -363,6 +380,10 @@ locate({parallel,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, Func == F -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_start_not_found,TEv}); (_) -> true end, Evs1), @@ -376,6 +397,10 @@ locate({parallel,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, Func == F -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_done_not_found,TEv}); (_) -> true end, Evs2), @@ -419,6 +444,10 @@ locate({parallel,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, EvGName == GroupName, EvProps == Props -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_start_not_found,TEv}); (_) -> true end, RemEvs), @@ -441,6 +470,10 @@ locate({parallel,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, EvGName == GroupName, EvProps == Props, Res == R -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_done_not_found,TEv}); (_) -> true end, RemEvs), @@ -460,6 +493,10 @@ locate({parallel,TEvs}, Node, Evs, Config) -> data={Mod,end_per_group,Reason}}}) when EH == TEH, EvNode == Node, Mod == M, Reason == R -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_auto_skip_not_found,TEv}); (_) -> true end, RemEvs), @@ -564,6 +601,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, Func == F -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_start_not_found,TEv}); (_) -> true end, Evs1), @@ -607,6 +648,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, EvGName == GroupName -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_start_not_found,TEv}); (_) -> true end, RemEvs), @@ -642,6 +687,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> EH == TEH, EvNode == Node, Mod == M, EvGName == GroupName, Res == R -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_done_not_found,TEv}); (_) -> true end, RemEvs), @@ -674,6 +723,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) -> data={Mod,end_per_group,Reason}}}) when EH == TEH, EvNode == Node, Mod == M, Reason == R -> false; + ({EH,#event{name=stop_logging, + node=EvNode,data=_}}) when + EH == TEH, EvNode == Node -> + exit({tc_auto_skip_not_found,TEv}); (_) -> true end, RemEvs), @@ -816,7 +869,8 @@ log_events(TC, Events, PrivDir) -> io:format(Dev, "[~n", []), log_events1(Events, Dev, " "), file:close(Dev), - io:format("Events written to logfile: ~p~n", [LogFile]), + io:format("Events written to logfile: ~s~n", + [LogFile,LogFile]), io:format(user, "Events written to logfile: ~p~n", [LogFile]). log_events1(Evs, Dev, "") -> -- cgit v1.2.3 From 13fab1dd93fba14e55fea0a343650dbaa54e8725 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 3 Jun 2010 13:21:38 +0200 Subject: Add new tests for test case groups and test specifications --- lib/common_test/src/ct_testspec.erl | 34 +++ lib/common_test/test/ct_groups_test_2_SUITE.erl | 26 +- .../cfgs/groups_2.1.cfg | 1 + .../groups_2/groups_21_SUITE.erl | 281 ++++++++++++++++++ .../groups_2/groups_22_SUITE.erl | 314 +++++++++++++++++++++ .../specs/groups_2.1.spec | 26 ++ 6 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg create mode 100644 lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl create mode 100644 lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl create mode 100644 lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 1ab9242e4b..dc017921cc 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -624,6 +624,25 @@ add_tests([{suites,Node,Dir,Ss}|Ts],Spec) -> Ss,Tests), add_tests(Ts,Spec#testspec{tests=Tests1}); +%% --- groups --- +%% Later make it possible to specify group execution properties +%% that will override thse in the suite. Also make it possible +%% create dynamic groups in specification, i.e. to group test cases +%% by means of groups defined only in the test specification. +add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec) -> + add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs}|Ts],Spec); +add_tests([{groups,Dir,Suite,Gs}|Ts],Spec) -> + add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec); +add_tests([{groups,Nodes,Dir,Suite,Gs}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,groups,[Dir,Suite,Gs],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{groups,Node,Dir,Suite,Gs}|Ts],Spec) -> + Tests = Spec#testspec.tests, + Tests1 = insert_cases(ref2node(Node,Spec#testspec.nodes), + ref2dir(Dir,Spec#testspec.alias), + Suite,Gs,Tests), + add_tests(Ts,Spec#testspec{tests=Tests1}); + %% --- cases --- add_tests([{cases,all_nodes,Dir,Suite,Cs}|Ts],Spec) -> add_tests([{cases,list_nodes(Spec),Dir,Suite,Cs}|Ts],Spec); @@ -654,6 +673,21 @@ add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) -> Ss,Cmt,Tests), add_tests(Ts,Spec#testspec{tests=Tests1}); +%% --- skip_groups --- +add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) -> + add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,Cmt}|Ts],Spec); +add_tests([{skip_groups,Dir,Suite,Gs,Cmt}|Ts],Spec) -> + add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec); +add_tests([{skip_groups,Nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,Cmt],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); +add_tests([{skip_groups,Node,Dir,Suite,Gs,Cmt}|Ts],Spec) -> + Tests = Spec#testspec.tests, + Tests1 = skip_cases(ref2node(Node,Spec#testspec.nodes), + ref2dir(Dir,Spec#testspec.alias), + Suite,Gs,Cmt,Tests), + add_tests(Ts,Spec#testspec{tests=Tests1}); + %% --- skip_cases --- add_tests([{skip_cases,all_nodes,Dir,Suite,Cs,Cmt}|Ts],Spec) -> add_tests([{skip_cases,list_nodes(Spec),Dir,Suite,Cs,Cmt}|Ts],Spec); diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl index 75348e2f4a..1fbeff0126 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl @@ -60,7 +60,7 @@ all(doc) -> ["Run smoke tests of Common Test."]; all(suite) -> - [missing_conf]. + [missing_conf, testspec_1]. %%-------------------------------------------------------------------- %% TEST CASES @@ -84,6 +84,25 @@ missing_conf(Config) when is_list(Config) -> TestEvents = events_to_check(missing_conf), ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- +%%% + +testspec_1(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + + TestSpec = filename:join(DataDir, "specs/groups_2.1.spec"), + + {Opts,ERPid} = setup({spec,TestSpec}, Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(testspec_1, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(testspec_1), + ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- %%% HELP FUNCTIONS @@ -133,4 +152,7 @@ test_events(missing_conf) -> {?eh,test_stats,{4,0,{0,0}}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} - ]. + ]; + +test_events(testspec_1) -> + []. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg b/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg new file mode 100644 index 0000000000..4928505157 --- /dev/null +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg @@ -0,0 +1 @@ +{dummy_key, "dummy_data"}. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl new file mode 100644 index 0000000000..a63aa31a7f --- /dev/null +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl @@ -0,0 +1,281 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(groups_21_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%==================================================================== +%% COMMON TEST CALLBACK FUNCTIONS +%%==================================================================== + +suite() -> + [{timetrap,{minutes,1}}]. + +groups() -> + [ + {test_group_1a, [testcase_1a,testcase_1b]}, + + {test_group_1b, [], [testcase_1a,testcase_1b]}, + + {test_group_2, [], [testcase_2a, + + {test_group_3, [], [testcase_3a, + testcase_3b]}, + testcase_2b]}, + + {test_group_4, [{test_group_5, [], [testcase_5a, + + {group, test_group_6}, + + testcase_5b]}]}, + + {test_group_6, [{group, test_group_7}]}, + + {test_group_7, [testcase_7a,testcase_7b]} + ]. + +all() -> + [testcase_1, + {group, test_group_1a}, + {group, test_group_1b}, + testcase_2, + {group, test_group_2}, + testcase_3, + {group, test_group_4}]. + +%% this func only for internal test purposes +grs_and_tcs() -> + {[ + test_group_1a, test_group_1b, + test_group_2, test_group_3, + test_group_4, test_group_5, + test_group_6, test_group_7 + ], + [ + testcase_1, + testcase_1a, testcase_1b, + testcase_2, + testcase_2a, testcase_2b, + testcase_3a, testcase_3b, + testcase_3, + testcase_5a, testcase_5b, + testcase_7a, testcase_7b + ]}. + +%%-------------------------------------------------------------------- +%% Suite Configuration +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + [{suite,init}|Config]. + +end_per_suite(Config) -> + init = ?config(suite,Config). + +%%-------------------------------------------------------------------- +%% Group Configuration +%%-------------------------------------------------------------------- + +init_per_group(Group, Config) -> + [{name,Group}] = ?config(tc_group_properties,Config), + {Grs,_} = grs_and_tcs(), + case lists:member(Group, Grs) of + true -> + ct:comment(io_lib:format("~w", [Group])), + init = ?config(suite,Config), + [{Group,Group} | Config]; + false -> + ct:fail({bad_group,Group}) + end. + +end_per_group(Group, Config) -> + {Grs,_} = grs_and_tcs(), + case lists:member(Group, Grs) of + true -> + init = ?config(suite,Config), + Group = ?config(Group,Config), + ok; + false -> + ct:fail({bad_group,Group}) + end. + +%%-------------------------------------------------------------------- +%% Testcase Configuration +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + {_,TCs} = grs_and_tcs(), + case lists:member(TestCase, TCs) of + true -> + init = ?config(suite,Config), + [{TestCase,TestCase} | Config]; + false -> + ct:fail({unknown_testcase,TestCase}) + end. + +end_per_testcase(TestCase, Config) -> + {_,TCs} = grs_and_tcs(), + case lists:member(TestCase, TCs) of + true -> + init = ?config(suite,Config), + TestCase = ?config(TestCase,Config), + ok; + false -> + ct:fail({unknown_testcase,TestCase}) + end. + + +%%-------------------------------------------------------------------- +%% Testcases +%%-------------------------------------------------------------------- + +testcase_1() -> + []. +testcase_1(Config) -> + init = ?config(suite,Config), + testcase_1 = ?config(testcase_1,Config), + ok. + +testcase_1a() -> + []. +testcase_1a(Config) -> + init = ?config(suite,Config), + case ?config(test_group_1a,Config) of + test_group_1a -> ok; + _ -> + case ?config(test_group_1b,Config) of + test_group_1b -> ok; + _ -> ct:fail(no_group_data) + end + end, + testcase_1a = ?config(testcase_1a,Config), + ok. +testcase_1b() -> + []. +testcase_1b(Config) -> + init = ?config(suite,Config), + case ?config(test_group_1a,Config) of + test_group_1a -> ok; + _ -> + case ?config(test_group_1b,Config) of + test_group_1b -> ok; + _ -> ct:fail(no_group_data) + end + end, + undefined = ?config(testcase_1a,Config), + testcase_1b = ?config(testcase_1b,Config), + ok. + +testcase_2() -> + []. +testcase_2(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_1a,Config), + undefined = ?config(test_group_1b,Config), + testcase_2 = ?config(testcase_2,Config), + ok. + +testcase_2a() -> + []. +testcase_2a(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + testcase_2a = ?config(testcase_2a,Config), + ok. +testcase_2b() -> + []. +testcase_2b(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + undefined = ?config(testcase_2a,Config), + testcase_2b = ?config(testcase_2b,Config), + ok. + +testcase_3a() -> + []. +testcase_3a(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + test_group_3 = ?config(test_group_3,Config), + undefined = ?config(testcase_2b,Config), + testcase_3a = ?config(testcase_3a,Config), + ok. +testcase_3b() -> + []. +testcase_3b(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + test_group_3 = ?config(test_group_3,Config), + undefined = ?config(testcase_3a,Config), + testcase_3b = ?config(testcase_3b,Config), + ok. + +testcase_3() -> + []. +testcase_3(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_2,Config), + undefined = ?config(test_group_3,Config), + testcase_3 = ?config(testcase_3,Config), + ok. + +testcase_5a() -> + []. +testcase_5a(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_3,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + undefined = ?config(testcase_3,Config), + testcase_5a = ?config(testcase_5a,Config), + ok. +testcase_5b() -> + []. +testcase_5b(Config) -> + init = ?config(suite,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + undefined = ?config(testcase_5a,Config), + testcase_5b = ?config(testcase_5b,Config), + ok. + +testcase_7a() -> + []. +testcase_7a(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_3,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + test_group_6 = ?config(test_group_6,Config), + test_group_7 = ?config(test_group_7,Config), + testcase_7a = ?config(testcase_7a,Config), + ok. +testcase_7b() -> + []. +testcase_7b(Config) -> + init = ?config(suite,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + test_group_6 = ?config(test_group_6,Config), + test_group_7 = ?config(test_group_7,Config), + undefined = ?config(testcase_7a,Config), + testcase_7b = ?config(testcase_7b,Config), + ok. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl new file mode 100644 index 0000000000..308df616d5 --- /dev/null +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl @@ -0,0 +1,314 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(groups_22_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%==================================================================== +%% COMMON TEST CALLBACK FUNCTIONS +%%==================================================================== + +suite() -> + [{timetrap,{minutes,1}}]. + +groups() -> + [ + {test_group_1a, [shuffle], [testcase_1a,testcase_1b,testcase_1c]}, + + {test_group_1b, [parallel], [testcase_1a,testcase_1b]}, + + {test_group_2, [parallel], [testcase_2a, + + {test_group_3, [{repeat,1}], + [testcase_3a, testcase_3b]}, + + testcase_2b]}, + + {test_group_4, [{test_group_5, [parallel], [testcase_5a, + + {group, test_group_6}, + + testcase_5b]}]}, + + {test_group_6, [parallel], [{group, test_group_7}]}, + + {test_group_7, [sequence], [testcase_7a,testcase_7b]} + ]. + +all() -> + [{group, test_group_1a}, + {group, test_group_1b}, + testcase_1, + testcase_2, + {group, test_group_2}, + testcase_3, + {group, test_group_4}]. + +%% this func only for internal test purposes +grs_and_tcs() -> + {[ + test_group_1a, test_group_1b, + test_group_2, test_group_3, + test_group_4, test_group_5, + test_group_6, test_group_7 + ], + [ + testcase_1a, testcase_1b, testcase_1c, + testcase_1, + testcase_2, + testcase_2a, testcase_2b, + testcase_3a, testcase_3b, + testcase_3, + testcase_5a, testcase_5b, + testcase_7a, testcase_7b + ]}. + +%%-------------------------------------------------------------------- +%% Suite Configuration +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + [{suite,init}|Config]. + +end_per_suite(Config) -> + init = ?config(suite,Config). + +%%-------------------------------------------------------------------- +%% Group Configuration +%%-------------------------------------------------------------------- + +init_per_group(Group, Config) -> + Cmt = + case {Group,?config(tc_group_properties,Config)} of + {test_group_1a,[{shuffle,S},{name,test_group_1a}]} -> + io_lib:format("shuffled, ~w", [S]); + {test_group_1b,[{name,test_group_1b},parallel]} -> "parallel"; + {test_group_2,[{name,test_group_2},parallel]} -> "parallel"; + {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1"; + {test_group_3,[{name,test_group_3}]} -> "repeat 0"; + {test_group_4,[{name,test_group_4}]} -> ok; + {test_group_5,[{name,test_group_5},parallel]} -> "parallel"; + {test_group_6,[{name,test_group_6},parallel]} -> "parallel"; + {test_group_7,[{name,test_group_7},sequence]} -> "sequence" + end, + {Grs,_} = grs_and_tcs(), + case lists:member(Group, Grs) of + true -> + init = ?config(suite,Config), + ct:comment(io_lib:format("~w, ~s", [Group,Cmt])), + [{Group,Group} | Config]; + false -> + ct:fail({bad_group,Group}) + end. + +end_per_group(Group, Config) -> + {Grs,_} = grs_and_tcs(), + case lists:member(Group, Grs) of + true -> + init = ?config(suite,Config), + Group = ?config(Group,Config), + ok; + false -> + ct:fail({bad_group,Group}) + end. + +%%-------------------------------------------------------------------- +%% Testcase Configuration +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + {_,TCs} = grs_and_tcs(), + case lists:member(TestCase, TCs) of + true -> + init = ?config(suite,Config), + [{TestCase,TestCase} | Config]; + false -> + ct:fail({unknown_testcase,TestCase}) + end. + +end_per_testcase(TestCase, Config) -> + {_,TCs} = grs_and_tcs(), + case lists:member(TestCase, TCs) of + true -> + init = ?config(suite,Config), + TestCase = ?config(TestCase,Config), + ok; + false -> + ct:fail({unknown_testcase,TestCase}) + end. + + +%%-------------------------------------------------------------------- +%% Testcases +%%-------------------------------------------------------------------- + +testcase_1a() -> + []. +testcase_1a(Config) -> + init = ?config(suite,Config), + case ?config(test_group_1a,Config) of + test_group_1a -> ok; + _ -> + case ?config(test_group_1b,Config) of + test_group_1b -> ok; + _ -> ct:fail(no_group_data) + end + end, + testcase_1a = ?config(testcase_1a,Config), + ok. +testcase_1b() -> + []. +testcase_1b(Config) -> + init = ?config(suite,Config), + case ?config(test_group_1a,Config) of + test_group_1a -> ok; + _ -> + case ?config(test_group_1b,Config) of + test_group_1b -> ok; + _ -> ct:fail(no_group_data) + end + end, + undefined = ?config(testcase_1a,Config), + testcase_1b = ?config(testcase_1b,Config), + ok. + +testcase_1c() -> + []. +testcase_1c(Config) -> + init = ?config(suite,Config), + case ?config(test_group_1a,Config) of + test_group_1a -> ok; + _ -> + case ?config(test_group_1b,Config) of + test_group_1b -> ok; + _ -> ct:fail(no_group_data) + end + end, + undefined = ?config(testcase_1b,Config), + testcase_1c = ?config(testcase_1c,Config), + ok. + +testcase_1() -> + []. +testcase_1(Config) -> + init = ?config(suite,Config), + testcase_1 = ?config(testcase_1,Config), + ok. + +testcase_2() -> + []. +testcase_2(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_1a,Config), + undefined = ?config(test_group_1b,Config), + testcase_2 = ?config(testcase_2,Config), + ok. + +testcase_2a() -> + []. +testcase_2a(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + testcase_2a = ?config(testcase_2a,Config), + ok. +testcase_2b() -> + []. +testcase_2b(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + undefined = ?config(testcase_2a,Config), + testcase_2b = ?config(testcase_2b,Config), + ok. + +testcase_3a() -> + []. +testcase_3a(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + test_group_3 = ?config(test_group_3,Config), + undefined = ?config(testcase_2b,Config), + testcase_3a = ?config(testcase_3a,Config), + ok. +testcase_3b() -> + []. +testcase_3b(Config) -> + init = ?config(suite,Config), + test_group_2 = ?config(test_group_2,Config), + test_group_3 = ?config(test_group_3,Config), + undefined = ?config(testcase_3a,Config), + testcase_3b = ?config(testcase_3b,Config), + ok. + +testcase_3() -> + []. +testcase_3(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_2,Config), + undefined = ?config(test_group_3,Config), + testcase_3 = ?config(testcase_3,Config), + ok. + +testcase_5a() -> + []. +testcase_5a(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_3,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + undefined = ?config(testcase_3,Config), + testcase_5a = ?config(testcase_5a,Config), + %% increase chance the done event will come + %% during execution of subgroup (could be + %% tricky to handle) + timer:sleep(3), + ok. +testcase_5b() -> + []. +testcase_5b(Config) -> + init = ?config(suite,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + undefined = ?config(testcase_5a,Config), + testcase_5b = ?config(testcase_5b,Config), + ok. + +testcase_7a() -> + []. +testcase_7a(Config) -> + init = ?config(suite,Config), + undefined = ?config(test_group_3,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + test_group_6 = ?config(test_group_6,Config), + test_group_7 = ?config(test_group_7,Config), + testcase_7a = ?config(testcase_7a,Config), + ok. +testcase_7b() -> + []. +testcase_7b(Config) -> + init = ?config(suite,Config), + test_group_4 = ?config(test_group_4,Config), + test_group_5 = ?config(test_group_5,Config), + test_group_6 = ?config(test_group_6,Config), + test_group_7 = ?config(test_group_7,Config), + undefined = ?config(testcase_7a,Config), + testcase_7b = ?config(testcase_7b,Config), + ok. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec new file mode 100644 index 0000000000..396b8ac645 --- /dev/null +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec @@ -0,0 +1,26 @@ + +{config, "../cfgs/groups_2.1.cfg"}. +{alias, groups_2, "../groups_2"}. + +{suites, groups_2, groups_21_SUITE}. +{skip_groups, groups_2, groups_21_SUITE, + [test_group_1b, test_group_7], "Skip tg_1b & tg_7"}. +{skip_cases, groups_2, groups_21_SUITE, + [testcase_1b, testcase_3a], "Skip tc_1b & tc_3a"}. + +{groups, groups_2, groups_22_SUITE, + test_group_1a}. +{skip_cases, groups_2, groups_22_SUITE, + testcase_1a, "Skip tc_1a"}. + +{groups, groups_2, groups_22_SUITE, + test_group_1b}. +{skip_cases, groups_2, groups_22_SUITE, + testcase_1b, "Skip tc_1b"}. +{skip_groups, groups_2, groups_21_SUITE, + [test_group_3], "Skip tg_3"}. + +{groups, groups_2, groups_22_SUITE, + test_group_5}. +{skip_cases, groups_2, groups_22_SUITE, + [testcase_7a, testcase_7b], "Skip tc_7a & tc_7b"}. -- cgit v1.2.3 From 7ce6aa16313d159b1994bf1d2f18b52bd91e9075 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Fri, 4 Jun 2010 11:21:41 +0200 Subject: Add groups in test specifications Ongoing work... --- lib/common_test/src/ct_framework.erl | 10 +- lib/common_test/src/ct_run.erl | 22 ++-- lib/common_test/src/ct_testspec.erl | 120 +++++++++++++++++++-- .../specs/groups_2.1.spec | 8 +- 4 files changed, 141 insertions(+), 19 deletions(-) diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 89bbc2448f..60978209b3 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -631,7 +631,7 @@ group_or_func(Func, _Config) -> %%% and every test case. If the former, all test cases in the suite %%% should be returned. -get_suite(Mod, all) -> +get_suite(Mod, all) -> case catch apply(Mod, groups, []) of {'EXIT',_} -> get_all(Mod, []); @@ -667,12 +667,18 @@ get_suite(Mod, Name) -> %% (and only) test case so we can report Error properly [{?MODULE,error_in_suite,[[Error]]}]; ConfTests -> + + %%! --- Thu Jun 3 19:13:22 2010 --- peppe was here! + %%! HEERE! + %%! Must be able to search recursively for group Name, + %%! this only handles top level groups! + FindConf = fun({conf,Props,_,_,_}) -> case proplists:get_value(name, Props) of Name -> true; _ -> false end - end, + end, case lists:filter(FindConf, ConfTests) of [] -> % must be a test case get_seq(Mod, Name); diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 7037ac7c1e..0d4a5b31dc 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -510,10 +510,6 @@ install(Opts, LogDir) -> case whereis(ct_util_server) of undefined -> VarFile = variables_file_name(LogDir), - - %%! --- Tue Jun 1 00:20:33 2010 --- peppe was here! - io:format("Varfile = ~p~n", [VarFile]), - case file:open(VarFile, [write]) of {ok,Fd} -> [io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts], @@ -1535,13 +1531,27 @@ add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) -> Error -> Error end; + +%% group +add_jobs([{TestDir,Suite,[{GroupName,_Cases}]}|Tests], Skip, Opts, CleanUp) when + is_atom(GroupName) -> + add_jobs([{TestDir,Suite,GroupName}|Tests], Skip, Opts, CleanUp); +add_jobs([{TestDir,Suite,{GroupName,_Cases}}|Tests], Skip, Opts, CleanUp) when + is_atom(GroupName) -> + add_jobs([{TestDir,Suite,GroupName}|Tests], Skip, Opts, CleanUp); + +%% test case add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opts, CleanUp) when is_atom(Case) -> add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp); + add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opts, CleanUp) when is_list(Cases) -> - case maybe_interpret(Suite, Cases, Opts) of + Cases1 = lists:map(fun({GroupName,_}) when is_atom(GroupName) -> GroupName; + (Case) -> Case + end, Cases), + case maybe_interpret(Suite, Cases1, Opts) of ok -> Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases", - case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases, + case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases1, skiplist(TestDir,Skip)) of {'EXIT',_} -> CleanUp; diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index dc017921cc..0f68b062f6 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -631,16 +631,29 @@ add_tests([{suites,Node,Dir,Ss}|Ts],Spec) -> %% by means of groups defined only in the test specification. add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec) -> add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs}|Ts],Spec); +add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) -> + add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs}}|Ts],Spec); add_tests([{groups,Dir,Suite,Gs}|Ts],Spec) -> add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec); +add_tests([{groups,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) -> + add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec); add_tests([{groups,Nodes,Dir,Suite,Gs}|Ts],Spec) when is_list(Nodes) -> Ts1 = separate(Nodes,groups,[Dir,Suite,Gs],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); +add_tests([{groups,Nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,groups,[Dir,Suite,Gs,{cases,TCs}],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); add_tests([{groups,Node,Dir,Suite,Gs}|Ts],Spec) -> Tests = Spec#testspec.tests, - Tests1 = insert_cases(ref2node(Node,Spec#testspec.nodes), + Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes), + ref2dir(Dir,Spec#testspec.alias), + Suite,Gs,all,Tests), + add_tests(Ts,Spec#testspec{tests=Tests1}); +add_tests([{groups,Node,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) -> + Tests = Spec#testspec.tests, + Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes), ref2dir(Dir,Spec#testspec.alias), - Suite,Gs,Tests), + Suite,Gs,TCs,Tests), add_tests(Ts,Spec#testspec{tests=Tests1}); %% --- cases --- @@ -676,16 +689,29 @@ add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) -> %% --- skip_groups --- add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) -> add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,Cmt}|Ts],Spec); +add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) -> + add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec); add_tests([{skip_groups,Dir,Suite,Gs,Cmt}|Ts],Spec) -> add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec); +add_tests([{skip_groups,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) -> + add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec); add_tests([{skip_groups,Nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) when is_list(Nodes) -> Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,Cmt],Ts,Spec#testspec.nodes), add_tests(Ts1,Spec); +add_tests([{skip_groups,Nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) when is_list(Nodes) -> + Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,{cases,TCs},Cmt],Ts,Spec#testspec.nodes), + add_tests(Ts1,Spec); add_tests([{skip_groups,Node,Dir,Suite,Gs,Cmt}|Ts],Spec) -> Tests = Spec#testspec.tests, - Tests1 = skip_cases(ref2node(Node,Spec#testspec.nodes), - ref2dir(Dir,Spec#testspec.alias), - Suite,Gs,Cmt,Tests), + Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes), + ref2dir(Dir,Spec#testspec.alias), + Suite,Gs,all,Cmt,Tests), + add_tests(Ts,Spec#testspec{tests=Tests1}); +add_tests([{skip_groups,Node,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) -> + Tests = Spec#testspec.tests, + Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes), + ref2dir(Dir,Spec#testspec.alias), + Suite,Gs,TCs,Cmt,Tests), add_tests(Ts,Spec#testspec{tests=Tests1}); %% --- skip_cases --- @@ -756,8 +782,11 @@ separate([],_,_,_) -> %% Representation: -%% {{Node,Dir},[{Suite1,[case11,case12,...]},{Suite2,[case21,case22,...]},...]} -%% {{Node,Dir},[{Suite1,{skip,Cmt}},{Suite2,[{case21,{skip,Cmt}},case22,...]},...]} +%% {{Node,Dir},[{Suite1,[GrOrCase11,GrOrCase12,...]}, +%% {Suite2,[GrOrCase21,GrOrCase22,...]},...]} +%% {{Node,Dir},[{Suite1,{skip,Cmt}}, +%% {Suite2,[{GrOrCase21,{skip,Cmt}},GrOrCase22,...]},...]} +%% GrOrCase = {GroupName,[Case1,Case2,...]} | Case insert_suites(Node,Dir,[S|Ss],Tests) -> Tests1 = insert_cases(Node,Dir,S,all,Tests), @@ -767,6 +796,54 @@ insert_suites(_Node,_Dir,[],Tests) -> insert_suites(Node,Dir,S,Tests) -> insert_suites(Node,Dir,[S],Tests). +insert_groups(Node,Dir,Suite,Group,Cases,Tests) when is_atom(Group) -> + insert_groups(Node,Dir,Suite,[Group],Cases,Tests); +insert_groups(Node,Dir,Suite,Groups,Cases,Tests) when + ((Cases == all) or is_list(Cases)) and is_list(Groups) -> + case lists:keysearch({Node,Dir},1,Tests) of + {value,{{Node,Dir},[{all,_}]}} -> + Tests; + {value,{{Node,Dir},Suites0}} -> + Suites1 = insert_groups1(Suite, + [{Gr,Cases} || Gr <- Groups], + Suites0), + insert_in_order({{Node,Dir},Suites1},Tests); + false -> + Groups1 = [{Gr,Cases} || Gr <- Groups], + insert_in_order({{Node,Dir},[{Suite,Groups1}]},Tests) + end; +insert_groups(Node,Dir,Suite,Groups,Case,Tests) when is_atom(Case) -> + Cases = if Case == all -> all; true -> [Case] end, + insert_groups(Node,Dir,Suite,Groups,Cases,Tests). + +insert_groups1(_Suite,_Groups,all) -> + all; +insert_groups1(Suite,Groups,Suites0) -> + case lists:keysearch(Suite,1,Suites0) of + {value,{Suite,all}} -> + Suites0; + {value,{Suite,GrAndCases0}} -> + GrAndCases = insert_groups2(Groups,GrAndCases0), + insert_in_order({Suite,GrAndCases},Suites0); + false -> + insert_in_order({Suite,Groups},Suites0) + end. + +insert_groups2(_Groups,all) -> + all; +insert_groups2([Group={GrName,Cases}|Groups],GrAndCases) -> + case lists:keysearch(GrName,1,GrAndCases) of + {value,{GrName,all}} -> + GrAndCases; + {value,{GrName,Cases0}} -> + Cases1 = insert_in_order(Cases,Cases0), + insert_groups2(Groups,insert_in_order({GrName,Cases1},GrAndCases)); + false -> + insert_groups2(Groups,insert_in_order(Group,GrAndCases)) + end; +insert_groups2([],GrAndCases) -> + GrAndCases. + insert_cases(Node,Dir,Suite,Cases,Tests) when is_list(Cases) -> case lists:keysearch({Node,Dir},1,Tests) of {value,{{Node,Dir},[{all,_}]}} -> @@ -801,6 +878,35 @@ skip_suites(_Node,_Dir,[],_Cmt,Tests) -> skip_suites(Node,Dir,S,Cmt,Tests) -> skip_suites(Node,Dir,[S],Cmt,Tests). +skip_groups(Node,Dir,Suite,Group,Case,Cmt,Tests) when is_atom(Group) -> + skip_groups(Node,Dir,Suite,[Group],[Case],Cmt,Tests); +skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests) when + ((Cases == all) or is_list(Cases)) and is_list(Groups) -> + Suites = + case lists:keysearch({Node,Dir},1,Tests) of + {value,{{Node,Dir},Suites0}} -> + Suites0; + false -> + [] + end, + Suites1 = skip_groups1(Suite,[{Gr,Cases} || Gr <- Groups],Cmt,Suites), + insert_in_order({{Node,Dir},Suites1},Tests); +skip_groups(Node,Dir,Suite,Groups,Case,Cmt,Tests) when is_atom(Case) -> + Cases = if Case == all -> all; true -> [Case] end, + skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests). + +skip_groups1(Suite,Groups,Cmt,Suites0) -> + SkipGroups = lists:map(fun(Group) -> + {Group,{skip,Cmt}} + end,Groups), + case lists:keysearch(Suite,1,Suites0) of + {value,{Suite,GrAndCases0}} -> + GrAndCases1 = GrAndCases0 ++ SkipGroups, + insert_in_order({Suite,GrAndCases1},Suites0); + false -> + insert_in_order({Suite,SkipGroups},Suites0) + end. + skip_cases(Node,Dir,Suite,Cases,Cmt,Tests) when is_list(Cases) -> Suites = case lists:keysearch({Node,Dir},1,Tests) of diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec index 396b8ac645..24d778ac44 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec @@ -3,8 +3,8 @@ {alias, groups_2, "../groups_2"}. {suites, groups_2, groups_21_SUITE}. -{skip_groups, groups_2, groups_21_SUITE, - [test_group_1b, test_group_7], "Skip tg_1b & tg_7"}. +%{skip_groups, groups_2, groups_21_SUITE, +% [test_group_1b, test_group_7], "Skip tg_1b & tg_7"}. {skip_cases, groups_2, groups_21_SUITE, [testcase_1b, testcase_3a], "Skip tc_1b & tc_3a"}. @@ -17,8 +17,8 @@ test_group_1b}. {skip_cases, groups_2, groups_22_SUITE, testcase_1b, "Skip tc_1b"}. -{skip_groups, groups_2, groups_21_SUITE, - [test_group_3], "Skip tg_3"}. +%{skip_groups, groups_2, groups_21_SUITE, +% [test_group_3], "Skip tg_3"}. {groups, groups_2, groups_22_SUITE, test_group_5}. -- cgit v1.2.3 From 470063d25f6128ef83cba6864533fc05e45cca74 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Thu, 3 Jun 2010 22:48:10 +0200 Subject: Flush old DOWN messages in demonitor --- lib/common_test/src/ct_config.erl | 2 +- lib/common_test/src/ct_gen_conn.erl | 4 ++-- lib/common_test/src/ct_logs.erl | 4 ++-- lib/common_test/src/ct_master.erl | 2 +- lib/common_test/src/ct_master_logs.erl | 4 ++-- lib/common_test/src/ct_run.erl | 4 ++-- lib/common_test/src/ct_util.erl | 2 +- lib/common_test/src/vts.erl | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index a7b8a9e906..a6ade3f66b 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -111,7 +111,7 @@ call(Msg) -> ct_config_server ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> {error,{ct_util_server_down,Reason}} diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl index a31e57c7ea..f3c040c933 100644 --- a/lib/common_test/src/ct_gen_conn.erl +++ b/lib/common_test/src/ct_gen_conn.erl @@ -76,7 +76,7 @@ start(Name,Address,InitData,CallbackMod) -> MRef = erlang:monitor(process,Pid), receive {connected,Pid} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), ct_util:register_connection(Name,Address,CallbackMod,Pid), {ok,Pid}; {Error,Pid} -> @@ -182,7 +182,7 @@ call(Pid,Msg) -> Pid ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), case Result of {retry,_Data} -> call(Pid,Result); diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index bd1a89ae1f..bd6fdfcd4f 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -80,7 +80,7 @@ init(Mode) -> MRef = erlang:monitor(process,Pid), receive {started,Pid,Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> exit({could_not_start_process,?MODULE,Reason}) @@ -163,7 +163,7 @@ call(Msg) -> ?MODULE ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> {error,{process_down,?MODULE,Reason}} diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 6cabe4c7a2..5c4ef70271 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -697,7 +697,7 @@ call(Pid,Msg) -> {'DOWN', Ref, _, _, _} -> {error,master_died} end, - erlang:demonitor(Ref), + erlang:demonitor(Ref, [flush]), Return. reply(Result,To) -> diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl index 63f60b1182..18e9eb528b 100644 --- a/lib/common_test/src/ct_master_logs.erl +++ b/lib/common_test/src/ct_master_logs.erl @@ -44,7 +44,7 @@ start(LogDir,Nodes) -> MRef = erlang:monitor(process,Pid), receive {started,Pid,Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), {Pid,Result}; {'DOWN',MRef,process,_,Reason} -> exit({could_not_start_process,?MODULE,Reason}) @@ -435,7 +435,7 @@ call(Msg) -> ?MODULE ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> {error,{process_down,?MODULE,Reason}} diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 0d4a5b31dc..c2fe2ababd 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1473,7 +1473,7 @@ count_test_cases(Tests, Skip) -> Ref = erlang:monitor(process, TSPid), add_jobs(Tests, Skip, #opts{}, []), {Suites,NoOfCases} = count_test_cases1(length(Tests), 0, [], Ref), - erlang:demonitor(Ref), + erlang:demonitor(Ref, [flush]), test_server_ctrl:stop_get_totals(), {Suites,NoOfCases}. @@ -1594,7 +1594,7 @@ wait_for_idle() -> idle -> ok; {'DOWN', Ref, _, _, _} -> error end, - erlang:demonitor(Ref), + erlang:demonitor(Ref, [flush]), ct_util:update_last_run_index(), Result end. diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index 2b06338479..f1692caf24 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -679,7 +679,7 @@ call(Msg) -> ct_util_server ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> {error,{ct_util_server_down,Reason}} diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index c94a796ab8..22399aff2a 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -100,7 +100,7 @@ start_link() -> MRef = erlang:monitor(process,Pid), receive {Pid,started} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), {ok,Pid}; {'DOWN',MRef,process,_,Reason} -> {error,{vts,died,Reason}} @@ -259,7 +259,7 @@ call(Msg) -> Pid ! {Msg,{self(),Ref}}, receive {Ref, Result} -> - erlang:demonitor(MRef), + erlang:demonitor(MRef, [flush]), Result; {'DOWN',MRef,process,_,Reason} -> {error,{process_down,Pid,Reason}} -- cgit v1.2.3 From a3af252253c1fbc642cf6229ff1e23f095b75b59 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Fri, 4 Jun 2010 01:06:03 +0200 Subject: Have end_per_testcase run even after timetrap_timeout and abort_testcase --- lib/common_test/src/ct_run.erl | 4 +- lib/common_test/test/ct_error_SUITE.erl | 38 +++++++- lib/test_server/src/test_server.erl | 159 +++++++++++++++++++++++--------- 3 files changed, 155 insertions(+), 46 deletions(-) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index c2fe2ababd..f993985f52 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -89,7 +89,7 @@ script_start() -> end, Flags) end, %% used for purpose of testing the run_test interface - io:format(user, "~n--------------- START ARGS ---------------~n", []), + io:format(user, "~n-------------------- START ARGS --------------------~n", []), io:format(user, "--- Init args:~n~p~n", [FlagFilter(Init)]), io:format(user, "--- CT args:~n~p~n", [FlagFilter(CtArgs)]), EnvArgs = opts2args(EnvStartOpts), @@ -97,7 +97,7 @@ script_start() -> [EnvStartOpts,EnvArgs]), Merged = merge_arguments(CtArgs ++ EnvArgs), io:format(user, "--- Merged args:~n~p~n", [FlagFilter(Merged)]), - io:format(user, "------------------------------------------~n~n", []), + io:format(user, "----------------------------------------------------~n~n", []), Merged; _ -> merge_arguments(CtArgs) diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index d5d91706d9..edf28e0bca 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -63,7 +63,8 @@ all(suite) -> [ cfg_error, lib_error, - no_compile + no_compile, + timetrap ]. @@ -133,6 +134,23 @@ no_compile(Config) when is_list(Config) -> TestEvents = events_to_check(no_compile), ok = ct_test_support:verify_events(TestEvents, Events, Config). +%%%----------------------------------------------------------------- +%%% +timetrap(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, + Suites = [Join(DataDir, "timetrap_1_SUITE")], + {Opts,ERPid} = setup({suite,Suites}, Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(timetrap, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(timetrap), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + %%%----------------------------------------------------------------- %%% HELP FUNCTIONS @@ -564,4 +582,20 @@ test_events(lib_error) -> ]; test_events(no_compile) -> - []. + []; + +test_events(timetrap) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,1}}, + {?eh,tc_start,{timetrap_1_SUITE,init_per_suite}}, + {?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{timetrap_1_SUITE,tc1}}, + {?eh,tc_done,{timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, + {?eh,test_stats,{0,1,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,end_per_suite}}, + {?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index e3d853f078..e01d083a1d 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -612,7 +612,7 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> end), group_leader(OldGLeader, self()), put(test_server_detected_fail, []), - run_test_case_msgloop(Ref, Pid, false, false, ""). + run_test_case_msgloop(Ref, Pid, false, false, "", undefined). %% Ugly bug (pre R5A): %% If this process (group leader of the test case) terminates before @@ -623,7 +623,7 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) -> %% A test case is known to have failed if it returns {'EXIT', _} tuple, %% or sends a message {failed, File, Line} to it's group_leader %% -run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> +run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment, CurrConf) -> %% NOTE: Keep job_proxy_msgloop/0 up to date when changes %% are made in this function! {Timeout,ReturnValue} = @@ -658,67 +658,67 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> Error1 end end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,NewComment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,NewComment,CurrConf); {io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} when is_list(Format) -> Msg = (catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} when is_atom(Format) -> Msg = (catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,Bytes}} -> run_test_case_msgloop_io( ReplyAs,CaptureStdout,Bytes,From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}} when is_list(Format) -> Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}} when is_list(Format) -> Msg = (catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}} when is_atom(Format) -> Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}} when is_atom(Format) -> Msg = (catch io_lib:Func(Format,Args)), run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,unicode,Bytes}} -> run_test_case_msgloop_io( ReplyAs,CaptureStdout,unicode_to_latin1(Bytes),From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {io_request,From,ReplyAs,{put_chars,latin1,Bytes}} -> run_test_case_msgloop_io( ReplyAs,CaptureStdout,Bytes,From,put_chars), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); IoReq when element(1, IoReq) == io_request -> %% something else, just pass it on group_leader() ! IoReq, - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {structured_io,ClientPid,Msg} -> output(Msg, ClientPid), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {capture,NewCapture} -> - run_test_case_msgloop(Ref,Pid,NewCapture,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,NewCapture,Terminate,Comment,CurrConf); {sync_apply,From,MFA} -> sync_local_or_remote_apply(false,From,MFA), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {sync_apply_proxy,Proxy,From,MFA} -> sync_local_or_remote_apply(Proxy,From,MFA), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {printout,Detail,Format,Args} -> print(Detail,Format,Args), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {comment,NewComment} -> Terminate1 = case Terminate of @@ -727,56 +727,105 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> Other -> Other end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate1,NewComment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate1,NewComment,CurrConf); + {set_curr_conf,NewCurrConf} -> + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,NewCurrConf); {'EXIT',Pid,{Ref,Time,Value,Loc,Opts}} -> RetVal = {Time/1000000,Value,mod_loc(Loc),Opts,Comment}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined); {'EXIT',Pid,Reason} -> case Reason of {timetrap_timeout,TVal,Loc} -> %% convert Loc to form that can be formatted Loc1 = mod_loc(Loc), {Mod,Func} = get_mf(Loc1), - %% The framework functions mustn't execute on this - %% group leader process or io will cause deadlock, - %% so we spawn a dedicated process for the operation - %% and let the group leader go back to handle io. - spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal}, - Loc1,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + %% call end_per_testcase on a separate process, only so that the + %% user has a chance to clean up after init_per_testcase, even + %% after a timetrap timeout + NewCurrConf = + case CurrConf of + {{Mod,Func},Conf} -> + EndConfPid = + call_end_conf(Mod,Func,Pid, + {timetrap_timeout,TVal}, + Loc1,[{tc_status, + {failed, + timetrap_timeout}}|Conf], + TVal), + {EndConfPid,{Mod,Func},Conf}; + _ -> + %% The framework functions mustn't execute on this + %% group leader process or io will cause deadlock, + %% so we spawn a dedicated process for the operation + %% and let the group leader go back to handle io. + spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal}, + Loc1,self(),Comment), + undefined + end, + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, + Comment,NewCurrConf); {timetrap_timeout,TVal,Loc,InitOrEnd} -> Loc1 = mod_loc(Loc), {Mod,_Func} = get_mf(Loc1), spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal}, Loc1,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); - {testcase_aborted,Reason,Loc} -> - Loc1 = mod_loc(Loc), + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); + {testcase_aborted,AbortReason,AbortLoc} -> + Loc1 = mod_loc(AbortLoc), {Mod,Func} = get_mf(Loc1), - spawn_fw_call(Mod,Func,Pid,{testcase_aborted,Reason}, - Loc1,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + %% call end_per_testcase on a separate process, only so that the + %% user has a chance to clean up after init_per_testcase, even + %% after abortion + ErrorMsg = {testcase_aborted,AbortReason}, + NewCurrConf = + case CurrConf of + {{Mod,Func},Conf} -> + TVal = case lists:keysearch(default_timeout,1,Conf) of + {value,{default_timeout,Tmo}} -> Tmo; + _ -> ?DEFAULT_TIMETRAP_SECS*1000 + end, + ErrorMsg = {testcase_aborted,AbortReason}, + EndConfPid = + call_end_conf(Mod,Func,Pid,ErrorMsg, + Loc1,[{tc_status,{failed,ErrorMsg}}|Conf], + TVal), + {EndConfPid,{Mod,Func},Conf}; + _ -> + spawn_fw_call(Mod,Func,Pid,ErrorMsg, + Loc1,self(),Comment), + undefined + end, + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,NewCurrConf); killed -> %% result of an exit(TestCase,kill) call, which is the %% only way to abort a testcase process that traps exits %% (see abort_current_testcase) spawn_fw_call(undefined,undefined,Pid,testcase_aborted_or_killed, unknown,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {fw_error,{FwMod,FwFunc,FwError}} -> spawn_fw_call(FwMod,FwFunc,Pid,{framework_error,FwError}, unknown,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); - _ -> + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); + _Other -> %% the testcase has terminated because of Reason (e.g. an exit %% because a linked process failed) spawn_fw_call(undefined,undefined,Pid,Reason, unknown,self(),Comment), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment) + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf) + end; + {EndConfPid,{call_end_conf,Data,_Result}} -> + case CurrConf of + {EndConfPid,{Mod,Func},_Conf} -> + {_Mod,_Func,TCPid,TCExitReason,Loc} = Data, + spawn_fw_call(Mod,Func,TCPid,TCExitReason,Loc,self(),Comment), + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,undefined); + _ -> + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf) end; {_FwCallPid,fw_notify_done,RetVal} -> %% the framework has been notified, we're finished - run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined); {'EXIT',_FwCallPid,{fw_notify_done,Func,Error}} -> %% a framework function failed CB = os:getenv("TEST_SERVER_FRAMEWORK"), @@ -787,20 +836,20 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) -> {list_to_atom(CB),Func} end, RetVal = {died,{framework_error,Loc,Error},Loc,"Framework error"}, - run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined); {failed,File,Line} -> put(test_server_detected_fail, [{File, Line}| get(test_server_detected_fail)]), - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); _Other when not is_tuple(_Other) -> %% ignore anything not generated by test server - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment); + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); _Other when element(1, _Other) /= 'EXIT', element(1, _Other) /= started, element(1, _Other) /= finished, element(1, _Other) /= print -> %% ignore anything not generated by test server - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment) + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf) after Timeout -> ReturnValue end. @@ -822,6 +871,28 @@ run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func) -> output(Msg,Sender) -> local_or_remote_apply({test_server_ctrl,output,[Msg,Sender]}). +call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) -> + Starter = self(), + Data = {Mod,Func,TCPid,TCExitReason,Loc}, + EndConfProc = + fun() -> + Supervisor = self(), + EndConfApply = fun() -> + apply(Mod,end_per_testcase,[Func,Conf]), + Supervisor ! {self(),end_conf} + end, + Pid = spawn_link(EndConfApply), + receive + {Pid,end_conf} -> + Starter ! {self(),{call_end_conf,Data,ok}}; + {'EXIT',Pid,Reason} -> + Starter ! {self(),{call_end_conf,Data,{error,Reason}}} + after TVal -> + Starter ! {self(),{call_end_conf,Data,{error,timeout}}} + end + end, + spawn_link(EndConfProc). + spawn_fw_call(Mod,{init_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why, Loc,SendTo,Comment) -> FwCall = @@ -1008,6 +1079,8 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> put(test_server_init_or_end_conf,undefined), %% call user callback function if defined NewConf1 = user_callback(TCCallback, Mod, Func, init, NewConf), + %% save current state in controller loop + group_leader() ! {set_curr_conf,{{Mod,Func},NewConf1}}, put(test_server_loc, {Mod,Func}), %% execute the test case {{T,Return},Loc} = {ts_tc(Mod, Func, [NewConf1]),get_loc()}, @@ -1029,6 +1102,8 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> _ -> {[{tc_status,ok}|NewConf1],Return,ok} end, + %% clear current state in controller loop + group_leader() ! {set_curr_conf,undefined}, %% call user callback function if defined EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf), {FWReturn1,TSReturn1,EndConf2} = -- cgit v1.2.3 From 34cf18550ff792ed9da884b00a49d6accd1bd5f5 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Fri, 4 Jun 2010 15:34:42 +0200 Subject: Add support for dynamic timetrap handling --- lib/common_test/src/ct.erl | 14 +- lib/common_test/src/ct_config.erl | 35 +++-- lib/common_test/src/ct_run.erl | 25 +++- lib/common_test/test/Makefile | 17 +-- lib/common_test/test/ct_error_SUITE.erl | 146 +++++++++++++++++---- .../error/test/timetrap_1_SUITE.erl | 135 +++++++++++++++++++ .../error/test/timetrap_2_SUITE.erl | 138 +++++++++++++++++++ lib/common_test/test/ct_test_server_if_1_SUITE.erl | 4 +- .../test_server_if/test/ts_if_1_SUITE.erl | 8 +- lib/common_test/test/ct_test_support.erl | 8 +- lib/common_test/test/ct_userconfig_callback.erl | 32 +++++ lib/test_server/src/test_server.erl | 1 - 12 files changed, 503 insertions(+), 60 deletions(-) create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl create mode 100644 lib/common_test/test/ct_userconfig_callback.erl diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 307d10428d..77bcf34981 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -65,7 +65,7 @@ pal/1, pal/2, pal/3, fail/1, comment/1, testcases/2, userdata/2, userdata/3, - sleep/1]). + timetrap/1, sleep/1]). %% New API for manipulating with config handlers -export([add_config/2, remove_config/2]). @@ -843,6 +843,18 @@ add_config(Callback, Config)-> remove_config(Callback, Config) -> ct_config:remove_config(Callback, Config). +%%%----------------------------------------------------------------- +%%% @spec timetrap(Time) -> ok +%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity +%%% Hours = integer() +%%% Mins = integer() +%%% Secs = integer() +%%% Millisecs = integer() | float() +%%% +%%% @doc

Use this function to set a new timetrap for the running test case.

+timetrap(Time) -> + test_server:timetrap(Time). + %%%----------------------------------------------------------------- %%% @spec sleep(Time) -> ok %%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index a6ade3f66b..dc6fcc66e5 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -694,39 +694,48 @@ random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]). check_callback_load(Callback) -> case code:is_loaded(Callback) of {file, _Filename}-> - {ok, Callback}; + check_exports(Callback); false-> case code:load_file(Callback) of {module, Callback}-> - {ok, Callback}; + check_exports(Callback); {error, Error}-> {error, Error} end end. +check_exports(Callback) -> + Fs = Callback:module_info(exports), + case {lists:member({check_parameter,1},Fs), + lists:member({read_config,1},Fs)} of + {true, true} -> + {ok, Callback}; + _ -> + {error, missing_callback_functions} + end. + check_config_files(Configs) -> ConfigChecker = fun ({Callback, [F|_R]=Files}) -> case check_callback_load(Callback) of - {ok, Callback}-> - if - is_integer(F) -> + {ok, Callback} -> + if is_integer(F) -> Callback:check_parameter(Files); - is_list(F) -> + is_list(F) -> lists:map(fun(File) -> - Callback:check_parameter(File) - end, - Files) + Callback:check_parameter(File) + end, + Files) end; - {error, _}-> - {error, {callback, Callback}} + {error, Why}-> + {error, {callback, {Callback,Why}}} end; ({Callback, []}) -> case check_callback_load(Callback) of {ok, Callback}-> Callback:check_parameter([]); - {error, _}-> - {error, {callback, Callback}} + {error, Why}-> + {error, {callback, {Callback,Why}}} end end, lists:keysearch(error, 1, lists:flatten(lists:map(ConfigChecker, Configs))). diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index f993985f52..ab900734f2 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -152,10 +152,12 @@ script_start1(Parent, Args) -> %% read general start flags Vts = get_start_opt(vts, true, Args), Shell = get_start_opt(shell, true, Args), - Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, Args), + Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args), LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), - MultTT = get_start_opt(multiply_timetraps, fun(MT) -> MT end, 1, Args), - ScaleTT = get_start_opt(scale_timetraps, fun(CT) -> CT end, false, Args), + MultTT = get_start_opt(multiply_timetraps, + fun([MT]) -> list_to_integer(MT) end, 1, Args), + ScaleTT = get_start_opt(scale_timetraps, + fun([CT]) -> list_to_atom(CT) end, false, Args), EvHandlers = event_handler_args2opts(Args), %% check flags and set corresponding application env variables @@ -335,8 +337,8 @@ check_and_install_configfiles(Configs, LogDir, EvHandlers) -> {error,{cant_read_config_file,File}}; {value,{error,{wrong_config,Message}}}-> {error,{wrong_config,Message}}; - {value,{error,{callback,File}}} -> - {error,{cant_load_callback_module,File}} + {value,{error,{callback,Info}}} -> + {error,{cant_load_callback_module,Info}} end. script_start3(StartOpts, Args) -> @@ -745,14 +747,23 @@ run_prepared(Run, Skip, Opts = #opts{logdir = LogDir, end. check_config_file(Callback, File)-> + case code:is_loaded(Callback) of + false -> + case code:load_file(Callback) of + {module,_} -> ok; + {error,Why} -> exit({cant_load_callback_module,Why}) + end; + _ -> + ok + end, case Callback:check_parameter(File) of {ok,{file,File}}-> ?abs(File); {ok,{config,_}}-> File; - {nok,{wrong_config,Message}}-> + {error,{wrong_config,Message}}-> exit({wrong_config,{Callback,Message}}); - {nok,{nofile,File}}-> + {error,{nofile,File}}-> exit({no_such_file,?abs(File)}) end. diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index eca6817682..594993cfec 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -27,6 +27,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk MODULES= \ ct_test_support \ ct_test_support_eh \ + ct_userconfig_callback \ ct_smoke_test_SUITE \ ct_event_handler_SUITE \ ct_groups_test_1_SUITE \ @@ -63,17 +64,17 @@ EBIN = . # Targets # ---------------------------------------------------- -#.PHONY: make_emakefile +.PHONY: make_emakefile -#make_emakefile: -# $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) \ -# '*_SUITE_make' > $(EMAKEFILE) -# $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\ -# >> $(EMAKEFILE) +make_emakefile: + $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\ + > $(EMAKEFILE) -tests debug opt: +tests debug opt: make_emakefile + erl $(ERL_MAKE_FLAGS) -make clean: + rm -f $(EMAKEFILE) rm -f $(TARGET_FILES) rm -f core @@ -86,7 +87,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt -release_tests_spec: +release_tests_spec: make_emakefile $(INSTALL_DIR) $(RELSYSDIR) $(INSTALL_DATA) $(ERL_FILES) $(COVERFILE) $(RELSYSDIR) $(INSTALL_PROGRAM) common_test.spec $(RELSYSDIR) diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index edf28e0bca..11482f1508 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -64,7 +64,9 @@ all(suite) -> cfg_error, lib_error, no_compile, - timetrap + timetrap_end_conf, + timetrap_normal, + timetrap_extended ]. @@ -87,7 +89,7 @@ cfg_error(Config) when is_list(Config) -> Join(DataDir, "cfg_error_8_SUITE"), Join(DataDir, "cfg_error_9_SUITE") ], - {Opts,ERPid} = setup({suite,Suites}, Config), + {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -105,7 +107,7 @@ lib_error(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, Suites = [Join(DataDir, "lib_error_1_SUITE")], - {Opts,ERPid} = setup({suite,Suites}, Config), + {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -123,7 +125,7 @@ no_compile(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, Suites = [Join(DataDir, "no_compile_SUITE")], - {Opts,ERPid} = setup({suite,Suites}, Config), + {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), @@ -136,21 +138,62 @@ no_compile(Config) when is_list(Config) -> %%%----------------------------------------------------------------- %%% -timetrap(Config) when is_list(Config) -> +timetrap_end_conf(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, Suites = [Join(DataDir, "timetrap_1_SUITE")], - {Opts,ERPid} = setup({suite,Suites}, Config), + {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), Events = ct_test_support:get_events(ERPid, Config), - ct_test_support:log_events(timetrap, + ct_test_support:log_events(timetrap_end_conf, reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = events_to_check(timetrap), + TestEvents = events_to_check(timetrap_end_conf), ok = ct_test_support:verify_events(TestEvents, Events, Config). +%%%----------------------------------------------------------------- +%%% +timetrap_normal(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, + Suite = Join(DataDir, "timetrap_2_SUITE"), + {Opts,ERPid} = setup([{suite,Suite}, + {userconfig,{ct_userconfig_callback, + "multiply 1 scale false"}}], + Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(timetrap_normal, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(timetrap_normal), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- +%%% +timetrap_extended(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + Join = fun(D, S) -> filename:join(D, "error/test/"++S) end, + Suite = Join(DataDir, "timetrap_2_SUITE"), + {Opts,ERPid} = setup([{suite,Suite}, + {multiply_timetraps,2}, + {scale_timetraps,false}, + {userconfig,{ct_userconfig_callback, + "multiply 2 scale false"}}], + Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(timetrap_extended, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(timetrap_extended), + ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- %%% HELP FUNCTIONS @@ -160,7 +203,7 @@ setup(Test, Config) -> Opts0 = ct_test_support:get_opts(Config), Level = ?config(trace_level, Config), EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], - Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], ERPid = ct_test_support:start_event_receiver(Config), {Opts,ERPid}. @@ -584,18 +627,77 @@ test_events(lib_error) -> test_events(no_compile) -> []; -test_events(timetrap) -> +test_events(timetrap_end_conf) -> [ - {?eh,start_logging,{'DEF','RUNDIR'}}, - {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{1,1,1}}, - {?eh,tc_start,{timetrap_1_SUITE,init_per_suite}}, - {?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}}, - {?eh,tc_start,{timetrap_1_SUITE,tc1}}, - {?eh,tc_done,{timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, - {?eh,test_stats,{0,1,{0,0}}}, - {?eh,tc_start,{timetrap_1_SUITE,end_per_suite}}, - {?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}}, - {?eh,test_done,{'DEF','STOP_TIME'}}, - {?eh,stop_logging,[]} + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,3}}, + {?eh,tc_start,{timetrap_1_SUITE,init_per_suite}}, + {?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{timetrap_1_SUITE,tc1}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, + {?eh,test_stats,{0,1,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,tc2}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc2,{failed,{testcase_aborted,testing_end_conf}}}}, + {?eh,test_stats,{0,2,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,tc3}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc3,{failed,{testcase_aborted,testing_end_conf}}}}, + {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,end_per_suite}}, + {?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(timetrap_normal) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,3}}, + {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}}, + {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{timetrap_2_SUITE,tc0}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc0,{failed,{timetrap_timeout,3000}}}}, + {?eh,test_stats,{0,1,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc1}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, + {?eh,test_stats,{0,2,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc2}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,500}}}}, + {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}}, + {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]; + +test_events(timetrap_extended) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,3}}, + {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}}, + {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{timetrap_2_SUITE,tc0}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc0,{failed,{timetrap_timeout,6000}}}}, + {?eh,test_stats,{0,1,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc1}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc1,{failed,{timetrap_timeout,2000}}}}, + {?eh,test_stats,{0,2,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,tc2}}, + {?eh,tc_done, + {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,1000}}}}, + {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}}, + {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} ]. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl new file mode 100644 index 0000000000..2e6432d05d --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl @@ -0,0 +1,135 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(timetrap_1_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% Info = [tuple()] +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,1}}]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_testcase(tc3, Config) -> + [{default_timeout,5000}|Config]; +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_testcase(tc1, Config) -> + ct:pal("tc1: ~p", [Config]), + ok; + +end_per_testcase(tc2, Config) -> + ct:pal("tc2: ~p", [Config]), + ok; + +end_per_testcase(tc3, Config) -> + ct:pal("tc3: ~p", [Config]), + ct:sleep(10000), + ok. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%%-------------------------------------------------------------------- +all() -> + [tc1,tc2,tc3]. + +tc1(_) -> + ct:sleep(3000), + ok. + +tc2(_) -> + spawn(ct, abort_current_testcase, [testing_end_conf]), + timer:sleep(3000), + ok. + +tc3(_) -> + spawn(ct, abort_current_testcase, [testing_end_conf]), + timer:sleep(3000), + ok. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl new file mode 100644 index 0000000000..b785d330aa --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl @@ -0,0 +1,138 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(timetrap_2_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% Info = [tuple()] +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,3}}, + {require,multiply}, + {require,scale}]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_testcase(tc1, Config) -> + ct:timetrap({seconds,1}), + Config; + +init_per_testcase(tc3, Config) -> + ct:timetrap({seconds,1}), + Config; + +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_testcase(_, Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%%-------------------------------------------------------------------- +all() -> + [tc0,tc1,tc2]. + +tc0(_) -> + N = list_to_integer(ct:get_config(multiply)), + ct:comment(io_lib:format("TO after ~w sec", [3*N])), + ct:sleep({seconds,5}), + ok. + +tc1(_) -> + N =list_to_integer( ct:get_config(multiply)), + ct:comment(io_lib:format("TO after ~w sec", [1*N])), + ct:sleep({seconds,5}), + ok. + +tc2(_) -> + N = list_to_integer(ct:get_config(multiply)), + ct:comment(io_lib:format("TO after ~w sec", [0.5*N])), + ct:timetrap(500), + ct:sleep(2000), + ok. diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE.erl index 8b46a30cdc..eb85409073 100644 --- a/lib/common_test/test/ct_test_server_if_1_SUITE.erl +++ b/lib/common_test/test/ct_test_server_if_1_SUITE.erl @@ -202,14 +202,14 @@ test_events(ts_if_1) -> {?eh,tc_done,{ts_if_1_SUITE,{end_per_group,g2,[parallel]},ok}}]}, {?eh,tc_start,{ts_if_1_SUITE,tc12}}, - {?eh,tc_done,{undefined,undefined,{testcase_aborted,{abort_current_testcase,tc12},'_'}}}, + {?eh,tc_done,{ts_if_1_SUITE,tc12,{failed,{testcase_aborted,'stopping tc12'}}}}, {?eh,test_stats,{2,5,{3,6}}}, {?eh,tc_start,{ts_if_1_SUITE,tc13}}, {?eh,tc_done,{ts_if_1_SUITE,tc13,ok}}, {?eh,test_stats,{3,5,{3,6}}}, {?eh,tc_start,{ts_if_1_SUITE,end_per_suite}}, {?eh,tc_done,{ts_if_1_SUITE,end_per_suite,ok}}, -%%! + {?eh,tc_start,{ts_if_2_SUITE,init_per_suite}}, {?eh,tc_done,{ts_if_2_SUITE,init_per_suite, {failed,{error,{suite0_failed,{exited,suite0_goes_boom}}}}}}, diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl index 8e90df21ce..47cea190dd 100644 --- a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl +++ b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl @@ -93,6 +93,10 @@ init_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- end_per_testcase(tc2, Config) -> timer:sleep(5000); +end_per_testcase(tc12, Config) -> + ct:comment("end_per_testcase(tc12) called!"), + ct:pal("end_per_testcase(tc12) called!", []), + ok; end_per_testcase(_TestCase, _Config) -> ok. @@ -180,9 +184,9 @@ gtc2(_) -> exit(should_have_been_skipped). tc12(_) -> - F = fun() -> ct:abort_current_testcase({abort_current_testcase,tc12}) end, + F = fun() -> ct:abort_current_testcase('stopping tc12') end, spawn(F), - timer:sleep(500), + timer:sleep(1000), exit(should_have_been_aborted). tc13(_) -> diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index c7c7384847..9ce8529bdb 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -110,11 +110,11 @@ init_per_testcase(_TestCase, Config) -> {_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)), case lists:keysearch(master, 1, Config) of false-> - test_server:format("See Common Test logs here:\n" + test_server:format("See Common Test logs here:\n\n" "~s/all_runs.html", [LogDir,LogDir]); {value, _}-> - test_server:format("See CT Master Test logs here:\n" + test_server:format("See CT Master Test logs here:\n\n" "~s/master_runs.html", [LogDir,LogDir]) end, @@ -183,14 +183,14 @@ run(Opts, Config) -> CTNode = ?config(ct_node, Config), Level = ?config(trace_level, Config), %% use ct interface - test_server:format(Level, "Calling ct:run_test(~p) on ~p~n", + test_server:format(Level, "[RUN #1] Calling ct:run_test(~p) on ~p~n", [Opts, CTNode]), Result1 = rpc:call(CTNode, ct, run_test, [Opts]), %% use run_test interface (simulated) test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]), rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]), - test_server:format(Level, "Calling ct_run:script_start() on ~p~n", [CTNode]), + test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n", [CTNode]), Result2 = rpc:call(CTNode, ct_run, script_start, []), case {Result1,Result2} of {ok,ok} -> diff --git a/lib/common_test/test/ct_userconfig_callback.erl b/lib/common_test/test/ct_userconfig_callback.erl new file mode 100644 index 0000000000..ca51bf240b --- /dev/null +++ b/lib/common_test/test/ct_userconfig_callback.erl @@ -0,0 +1,32 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(ct_userconfig_callback). + +-export([check_parameter/1, read_config/1]). + +read_config(Str) -> + KeyVals = string:tokens(Str, " "), + {ok,read_config1(KeyVals)}. + +read_config1([Key,Val | KeyVals]) -> + [{list_to_atom(Key),Val} | read_config1(KeyVals)]; +read_config1([]) -> + []. + +check_parameter(Str) -> + {ok,{config,Str}}. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index e01d083a1d..444af0c0d7 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -784,7 +784,6 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment, CurrConf) -> {value,{default_timeout,Tmo}} -> Tmo; _ -> ?DEFAULT_TIMETRAP_SECS*1000 end, - ErrorMsg = {testcase_aborted,AbortReason}, EndConfPid = call_end_conf(Mod,Func,Pid,ErrorMsg, Loc1,[{tc_status,{failed,ErrorMsg}}|Conf], -- cgit v1.2.3 From 1358ccecd1169d0c534137fb2c65759c7089cd0a Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Fri, 4 Jun 2010 18:21:13 +0200 Subject: Add support for config info functions (e.g. init_per_suite/0) Also fixed bug: return value {fail,Reason} from end_tc(init_per_suite) was ignored. --- lib/common_test/src/ct_framework.erl | 23 +++- lib/common_test/test/ct_error_SUITE.erl | 28 ++++- .../error/test/cfg_error_10_SUITE.erl | 123 +++++++++++++++++++ .../error/test/cfg_error_11_SUITE.erl | 134 +++++++++++++++++++++ lib/test_server/src/test_server.erl | 20 ++- 5 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 60978209b3..3dd1026f13 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -161,6 +161,7 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) -> _ -> MergeResult end, + %% timetrap must be handled before require MergedInfo1 = timetrap_first(MergedInfo, [], []), %% tell logger to use specified style sheet @@ -244,8 +245,8 @@ add_defaults(Mod,Func,FuncInfo,DoInit) -> _ -> {suite0_failed,bad_return_value} end. - -add_defaults1(_Mod,init_per_suite,[],SuiteInfo,_) -> + +add_defaults1(_Mod,init_per_suite,[],SuiteInfo,_DoInit) -> SuiteInfo; add_defaults1(Mod,Func,FuncInfo,SuiteInfo,DoInit) -> @@ -253,15 +254,27 @@ add_defaults1(Mod,Func,FuncInfo,SuiteInfo,DoInit) -> %% can result in weird behaviour (suite values get overwritten) SuiteReqs = [SDDef || SDDef <- SuiteInfo, - require == element(1,SDDef)], - case [element(2,Clash) || Clash <- SuiteReqs, - true == lists:keymember(element(2,Clash),2,FuncInfo)] of + ((require == element(1,SDDef)) or + (default_config == element(1,SDDef)))], + FuncReqs = + [FIDef || FIDef <- FuncInfo, + require == element(1,FIDef)], + case [element(2,Clash) || Clash <- SuiteReqs, + require == element(1, Clash), + true == lists:keymember(element(2,Clash),2, + FuncReqs)] of [] -> add_defaults2(Mod,Func,FuncInfo,SuiteInfo,SuiteReqs,DoInit); Clashes -> {error,{config_name_already_in_use,Clashes}} end. +add_defaults2(Mod,init_per_suite,IPSInfo,SuiteInfo,SuiteReqs,false) -> + %% not common practise to use a test case info function for + %% init_per_suite (usually handled by suite/0), but let's support + %% it just in case... + add_defaults2(Mod,init_per_suite,IPSInfo,SuiteInfo,SuiteReqs,true); + add_defaults2(_Mod,_Func,FuncInfo,SuiteInfo,_,false) -> %% include require elements from test case info, but not from suite/0 %% (since we've already required those vars) diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index 11482f1508..199a1ad97a 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -87,7 +87,9 @@ cfg_error(Config) when is_list(Config) -> Join(DataDir, "cfg_error_6_SUITE"), Join(DataDir, "cfg_error_7_SUITE"), Join(DataDir, "cfg_error_8_SUITE"), - Join(DataDir, "cfg_error_9_SUITE") + Join(DataDir, "cfg_error_9_SUITE"), + Join(DataDir, "cfg_error_10_SUITE"), + Join(DataDir, "cfg_error_11_SUITE") ], {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), @@ -228,7 +230,7 @@ test_events(cfg_error) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{9,9,33}}, + {?eh,start_info,{11,11,36}}, {?eh,tc_start,{cfg_error_1_SUITE,init_per_suite}}, {?eh,tc_done, @@ -540,6 +542,28 @@ test_events(cfg_error) -> {?eh,tc_start,{cfg_error_9_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_9_SUITE,end_per_suite,ok}}, + {?eh,tc_start,{cfg_error_10_SUITE,init_per_suite}}, + {?eh,tc_done,{cfg_error_10_SUITE,init_per_suite, + {failed,{error,fail_init_per_suite}}}}, + {?eh,tc_auto_skip,{cfg_error_10_SUITE,tc1, + {failed,{cfg_error_10_SUITE,init_per_suite, + {failed,fail_init_per_suite}}}}}, + {?eh,test_stats,{12,3,{0,19}}}, + {?eh,tc_auto_skip,{cfg_error_10_SUITE,end_per_suite, + {failed,{cfg_error_10_SUITE,init_per_suite, + {failed,fail_init_per_suite}}}}}, + {?eh,tc_start,{cfg_error_11_SUITE,init_per_suite}}, + {?eh,tc_done,{cfg_error_11_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{cfg_error_11_SUITE,tc1}}, + {?eh,tc_done,{cfg_error_11_SUITE,tc1, + {skipped,{config_name_already_in_use,[dummy0]}}}}, + {?eh,test_stats,{12,3,{1,19}}}, + {?eh,tc_start,{cfg_error_11_SUITE,tc2}}, + {?eh,tc_done,{cfg_error_11_SUITE,tc2,ok}}, + {?eh,test_stats,{13,3,{1,19}}}, + {?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}}, + {?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} ]; diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl new file mode 100644 index 0000000000..3bb2f63d34 --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl @@ -0,0 +1,123 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cfg_error_10_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% Info = [tuple()] +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,2}}, + {require, dummy0}, {default_config, dummy0, "suite/0"}, + {require, dummy1}, {default_config, dummy1, "suite/0"}, + {require, dummy2}, {default_config, dummy2, "suite/0"}]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_suite() -> + put('$test_server_framework_test', + fun(init_tc, _Default) -> {fail,fail_init_per_suite}; + (_, Default) -> Default + end), + [{require, dummy3}, {default_config, dummy3, "init_per_suite/0"}, + {timetrap,3000}]. + +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_suite(Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + done. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%%-------------------------------------------------------------------- +all() -> + [tc1]. + +tc1(_) -> + exit(should_never_run). diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl new file mode 100644 index 0000000000..224e83620e --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl @@ -0,0 +1,134 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cfg_error_11_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% Info = [tuple()] +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,2}}, + {require, dummy0}, {default_config, dummy0, "suite/0"}, + {require, dummy1}, {default_config, dummy1, "suite/0"}, + {require, dummy2}, {default_config, dummy2, "suite/0"}]. + + + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_suite() -> + put('$test_server_framework_test', + fun(end_tc, _Default) -> {fail,fail_end_per_suite}; + (_, Default) -> Default + end), + [{require, dummy3}, {default_config, dummy3, "end_per_suite/0"}, + {timetrap,3000}]. + +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%%-------------------------------------------------------------------- +init_per_testcase(_, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + done. + +%%-------------------------------------------------------------------- +%% Function: groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% Function: all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%%-------------------------------------------------------------------- +all() -> + [tc1, tc2]. + +tc1() -> + [{require, dummy0}, {default_config, dummy0, "tc1"}]. + +tc1(_) -> + dummy. + +tc2() -> + [{timetrap,1}]. + +tc2(_) -> + dummy. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 444af0c0d7..b70ac08622 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -1171,10 +1171,14 @@ process_return_val(Return, M,F,A, Loc, Final) -> process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts) when E=='EXIT'; E==failed -> fw_error_notify(M,F,A, TCError, mod_loc(Loc)), - test_server_sup:framework_call(end_tc, - [?pl2a(M),F,{{error,TCError}, - [[{tc_status,{failed,TCError}}|Args]]}]), - {Failed,SaveOpts}; + case test_server_sup:framework_call(end_tc, + [?pl2a(M),F,{{error,TCError}, + [[{tc_status,{failed,TCError}}|Args]]}]) of + {fail,FWReason} -> + {{failed,FWReason},SaveOpts}; + _ -> + {Failed,SaveOpts} + end; process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) -> process_return_val1(Opts, M,F,[[SaveCfg|Args]], Loc, Final, SaveOpts); process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) -> @@ -1187,8 +1191,12 @@ process_return_val1([RetVal={Tag,_}|Opts], M,F,A, Loc, _, SaveOpts) when Tag==sk process_return_val1([_|Opts], M,F,A, Loc, Final, SaveOpts) -> process_return_val1(Opts, M,F,A, Loc, Final, SaveOpts); process_return_val1([], M,F,A, _Loc, Final, SaveOpts) -> - test_server_sup:framework_call(end_tc, [?pl2a(M),F,{Final,A}]), - {Final,lists:reverse(SaveOpts)}. + case test_server_sup:framework_call(end_tc, [?pl2a(M),F,{Final,A}]) of + {fail,FWReason} -> + {{failed,FWReason},SaveOpts}; + _ -> + {Final,lists:reverse(SaveOpts)} + end. user_callback(undefined, _, _, _, Args) -> Args; -- cgit v1.2.3 From cc372a2ba2aaa1de19935ff178d5dce88ec16854 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Sat, 5 Jun 2010 01:40:06 +0200 Subject: Fix error with {repeat,0} property in groups causing double iterations --- lib/common_test/test/ct_groups_test_2_SUITE.erl | 118 ++++++++++++++++++++- .../groups_1/repeat_1_SUITE.erl | 105 ++++++++++++++++++ lib/test_server/src/test_server_ctrl.erl | 6 ++ 3 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl index 1fbeff0126..bdf7303a59 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl @@ -60,7 +60,7 @@ all(doc) -> ["Run smoke tests of Common Test."]; all(suite) -> - [missing_conf, testspec_1]. + [missing_conf, testspec_1, repeat_1]. %%-------------------------------------------------------------------- %% TEST CASES @@ -104,6 +104,25 @@ testspec_1(Config) when is_list(Config) -> TestEvents = events_to_check(testspec_1), ok = ct_test_support:verify_events(TestEvents, Events, Config). +%%%----------------------------------------------------------------- +%%% + +repeat_1(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + + Suite = filename:join(DataDir, "groups_1/repeat_1_SUITE"), + + {Opts,ERPid} = setup({suite,Suite}, Config), + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(repeat_1, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(repeat_1), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + %%%----------------------------------------------------------------- %%% HELP FUNCTIONS %%%----------------------------------------------------------------- @@ -155,4 +174,99 @@ test_events(missing_conf) -> ]; test_events(testspec_1) -> - []. + []; + +test_events(repeat_1) -> + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,unknown}}, + {?eh,tc_start,{repeat_1_SUITE,init_per_suite}}, + {?eh,tc_done,{repeat_1_SUITE,init_per_suite,ok}}, + [{?eh,tc_start, + {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,1}]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,1}]},ok}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_1a}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_1a,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_1b}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_1b,ok}}, + {?eh,test_stats,{2,0,{0,0}}}, + {?eh,tc_start, + {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,1}]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,1}]},ok}}], + [{?eh,tc_start, + {repeat_1_SUITE,{init_per_group,test_group_1,[]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{init_per_group,test_group_1,[]},ok}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_1a}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_1a,ok}}, + {?eh,test_stats,{3,0,{0,0}}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_1b}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_1b,ok}}, + {?eh,test_stats,{4,0,{0,0}}}, + {?eh,tc_start, + {repeat_1_SUITE,{end_per_group,test_group_1,[]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{end_per_group,test_group_1,[]},ok}}], + [{?eh,tc_start, + {repeat_1_SUITE,{init_per_group,test_group_2,[{repeat,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{init_per_group,test_group_2,[{repeat,0}]},ok}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_2a}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_2a,ok}}, + {?eh,test_stats,{5,0,{0,0}}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_2b}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_2b,ok}}, + {?eh,test_stats,{6,0,{0,0}}}, + {?eh,tc_start, + {repeat_1_SUITE,{end_per_group,test_group_2,[{repeat,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE,{end_per_group,test_group_2,[{repeat,0}]},ok}}], + [{?eh,tc_start, + {repeat_1_SUITE, + {init_per_group,test_group_3,[{repeat_until_all_fail,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE, + {init_per_group,test_group_3,[{repeat_until_all_fail,0}]}, + ok}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_3a}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_3a,ok}}, + {?eh,test_stats,{7,0,{0,0}}}, + [{?eh,tc_start, + {repeat_1_SUITE, + {init_per_group,test_group_4,[{repeat_until_any_fail,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE, + {init_per_group,test_group_4,[{repeat_until_any_fail,0}]}, + ok}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_4a}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_4a,ok}}, + {?eh,test_stats,{8,0,{0,0}}}, + {?eh,tc_start,{repeat_1_SUITE,testcase_4b}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_4b,ok}}, + {?eh,test_stats,{9,0,{0,0}}}, + {?eh,tc_start, + {repeat_1_SUITE, + {end_per_group,test_group_4,[{repeat_until_any_fail,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE, + {end_per_group,test_group_4,[{repeat_until_any_fail,0}]}, + ok}}], + {?eh,tc_start,{repeat_1_SUITE,testcase_3b}}, + {?eh,tc_done,{repeat_1_SUITE,testcase_3b,ok}}, + {?eh,test_stats,{10,0,{0,0}}}, + {?eh,tc_start, + {repeat_1_SUITE, + {end_per_group,test_group_3,[{repeat_until_all_fail,0}]}}}, + {?eh,tc_done, + {repeat_1_SUITE, + {end_per_group,test_group_3,[{repeat_until_all_fail,0}]}, + ok}}], + {?eh,tc_start,{repeat_1_SUITE,end_per_suite}}, + {?eh,tc_done,{repeat_1_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]. diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl new file mode 100644 index 0000000000..4edbc3e384 --- /dev/null +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl @@ -0,0 +1,105 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(repeat_1_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%==================================================================== +%% COMMON TEST CALLBACK FUNCTIONS +%%==================================================================== + +suite() -> + [{timetrap,{minutes,1}}]. + +groups() -> + [ + {test_group_1, [{repeat,1}], [testcase_1a,testcase_1b]}, + {test_group_2, [{repeat,0}], [testcase_2a,testcase_2b]}, + + {test_group_3, [{repeat_until_all_fail,0}], + [testcase_3a, + {test_group_4, [{repeat_until_any_fail,0}], + [testcase_4a, testcase_4b]}, + testcase_3b]} + ]. + +all() -> + [ + {group, test_group_1}, + {group, test_group_2}, + {group, test_group_3} + ]. + +%%-------------------------------------------------------------------- +%% Suite Configuration +%%-------------------------------------------------------------------- + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Group Configuration +%%-------------------------------------------------------------------- + +init_per_group(Group, Config) -> + Group = proplists:get_value(name,?config(tc_group_properties,Config)), + ct:comment(Group), + Config. + +end_per_group(_Group, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Testcase Configuration +%%-------------------------------------------------------------------- + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Testcases +%%-------------------------------------------------------------------- + +testcase_1a(_) -> + ok. +testcase_1b(_) -> + ok. + +testcase_2a(_) -> + ok. +testcase_2b(_) -> + ok. + +testcase_3a(_) -> + ok. +testcase_3b(_) -> + ok. + +testcase_4a(_) -> + ok. +testcase_4b(_) -> + ok. diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 49c8bfa2c9..9e6f1cedbb 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -1572,6 +1572,8 @@ remove_conf([{conf, _Ref, Props, _MF}|Cases], NoConf, Repeats) -> case get_repeat(Props) of undefined -> remove_conf(Cases, NoConf, Repeats); + {_RepType,0} -> + remove_conf(Cases, NoConf, Repeats); _ -> remove_conf(Cases, NoConf, true) end; @@ -2488,6 +2490,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, %% will continously update status with test case results %% without knowing the Ref (but update hd(Status)) {false,new_status(Ref, Status1),Cases1,?void_fun}; + {_RepType,0} -> + {false,new_status(Ref, Status1),Cases1,?void_fun}; _ -> {Copied,_} = copy_cases(Ref, make_ref(), Cs1), {true,new_status(Ref, Copied, Status1),Cases1,?void_fun} @@ -2505,6 +2509,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, case RepVal of undefined -> {false,EndStatus,Cases1,?void_fun}; + {_RepType,0} -> + {false,EndStatus,Cases1,?void_fun}; {repeat,_} -> {true,EndStatus,CopiedCases++Cases1,?void_fun}; {repeat_until_all_ok,_} -> -- cgit v1.2.3 From b9e20d9e1f3b47ab5806cd79c16c5609f12453f9 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Sat, 5 Jun 2010 03:19:35 +0200 Subject: Add test suite for remote loading of binary suites --- lib/common_test/test/Makefile | 3 +- lib/common_test/test/ct_misc_1_SUITE.erl | 138 +++++++++++++++++++++ .../test/ct_misc_1_SUITE_data/beam_1_SUITE.erl | 134 ++++++++++++++++++++ .../test/ct_misc_1_SUITE_data/beam_2_SUITE.erl | 134 ++++++++++++++++++++ lib/common_test/test/ct_test_support.erl | 4 +- 5 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 lib/common_test/test/ct_misc_1_SUITE.erl create mode 100644 lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl create mode 100644 lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 594993cfec..2f1ff9a835 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -36,7 +36,8 @@ MODULES= \ ct_error_SUITE \ ct_test_server_if_1_SUITE \ ct_config_SUITE \ - ct_master_SUITE + ct_master_SUITE \ + ct_misc_1_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl new file mode 100644 index 0000000000..f42081ee7e --- /dev/null +++ b/lib/common_test/test/ct_misc_1_SUITE.erl @@ -0,0 +1,138 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File: ct_misc_1_SUITE +%%% +%%% Description: +%%% Test misc things in Common Test suites. +%%% +%%% The suites used for the test are located in the data directory. +%%%------------------------------------------------------------------- +-module(ct_misc_1_SUITE). + +-compile(export_all). + +-include_lib("test_server/include/test_server.hrl"). +-include_lib("test_server/include/test_server_line.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config1 = ct_test_support:init_per_suite(Config), + Config1. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + ct_test_support:end_per_testcase(TestCase, Config). + +all(doc) -> + [""]; + +all(suite) -> + [ + beam_me_up + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% +beam_me_up(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + CTNode = ?config(ct_node, Config), + + %% Path = rpc:call(CTNode, code, get_path, []), + [_ | Parts] = lists:reverse(filename:split(DataDir)), + TSDir = filename:join(lists:reverse(Parts)), + true = rpc:call(CTNode, code, del_path, [TSDir]), + + Mods = [beam_1_SUITE, beam_2_SUITE], + Suites = [atom_to_list(M) || M <- Mods], + [{error,_} = rpc:call(CTNode, code, load_file, [M]) || M <- Mods], + + code:add_path(TSDir), + CRes = + [compile:file(filename:join(DataDir,F), + [verbose,report_errors, + report_warnings,binary]) || F <- Suites], + + [{module,_} = rpc:call(CTNode, code, load_binary, + [Mod, atom_to_list(Mod), Bin]) || + {ok,Mod,Bin} <- CRes], + + {Opts,ERPid} = setup([{suite,Suites},{auto_compile,false}], Config), + ok = ct_test_support:run(ct, run_test, [Opts], Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(beam_me_up, + reformat(Events, ?eh), + ?config(priv_dir, Config)), + + TestEvents = events_to_check(beam_me_up), + ok = ct_test_support:verify_events(TestEvents, Events, Config). + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). +%reformat(Events, _EH) -> +% Events. + +%%%----------------------------------------------------------------- +%%% TEST EVENTS +%%%----------------------------------------------------------------- +events_to_check(Test) -> + %% 2 tests (ct:run_test + script_start) is default + events_to_check(Test, 2). + +events_to_check(_, 0) -> + []; +events_to_check(Test, N) -> + test_events(Test) ++ events_to_check(Test, N-1). + +test_events(beam_me_up) -> + []. diff --git a/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl new file mode 100644 index 0000000000..e371b13ccb --- /dev/null +++ b/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl @@ -0,0 +1,134 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(beam_1_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% COMMON TEST CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> + [ + {timetrap,{seconds,10}} + ]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: all() -> TestCases | {skip,Reason} +%% +%% TestCases = [TestCase | {sequence,SeqName}] +%% TestCase = atom() +%% Name of a test case. +%% SeqName = atom() +%% Name of a test case sequence. +%% Reason = term() +%% The reason for skipping all test cases. +%% +%% Description: Returns the list of test cases that are to be executed. +%%-------------------------------------------------------------------- +all() -> + [tc1, tc2]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +tc1(_Config) -> + ct:comment("tc1 executed"), + ok. + +tc2(_Config) -> + exit('tc2 failed'). diff --git a/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl new file mode 100644 index 0000000000..d0d442bd0b --- /dev/null +++ b/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl @@ -0,0 +1,134 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(beam_2_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% COMMON TEST CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Function: suite() -> Info +%% +%% Info = [tuple()] +%% List of key/value pairs. +%% +%% Description: Returns list of tuples to set default properties +%% for the suite. +%% +%% Note: The suite/0 function is only meant to be used to return +%% default data values, not perform any other operations. +%%-------------------------------------------------------------------- +suite() -> + [ + {timetrap,{seconds,10}} + ]. + +%%-------------------------------------------------------------------- +%% Function: init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the suite. +%% +%% Description: Initialization before the suite. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_suite(Config0) -> void() | {save_config,Config1} +%% +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after the suite. +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is about to run. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% Reason = term() +%% The reason for skipping the test case. +%% +%% Description: Initialization before each test case. +%% +%% Note: This function is free to add any key/value pairs to the Config +%% variable, but should NOT alter/remove any existing entries. +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Function: end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} +%% +%% TestCase = atom() +%% Name of the test case that is finished. +%% Config0 = Config1 = [tuple()] +%% A list of key/value pairs, holding the test case configuration. +%% +%% Description: Cleanup after each test case. +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% Function: all() -> TestCases | {skip,Reason} +%% +%% TestCases = [TestCase | {sequence,SeqName}] +%% TestCase = atom() +%% Name of a test case. +%% SeqName = atom() +%% Name of a test case sequence. +%% Reason = term() +%% The reason for skipping all test cases. +%% +%% Description: Returns the list of test cases that are to be executed. +%%-------------------------------------------------------------------- +all() -> + [tc1, tc2]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +tc1(_Config) -> + ct:comment("tc1 executed"), + ok. + +tc2(_Config) -> + exit('tc2 failed'). diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 9ce8529bdb..eaf3e23859 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -183,7 +183,7 @@ run(Opts, Config) -> CTNode = ?config(ct_node, Config), Level = ?config(trace_level, Config), %% use ct interface - test_server:format(Level, "[RUN #1] Calling ct:run_test(~p) on ~p~n", + test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n", [Opts, CTNode]), Result1 = rpc:call(CTNode, ct, run_test, [Opts]), @@ -204,7 +204,7 @@ run(Opts, Config) -> run(M, F, A, Config) -> CTNode = ?config(ct_node, Config), Level = ?config(trace_level, Config), - test_server:format(Level, "Calling ~w:~w(~p) on ~p~n", + test_server:format(Level, "~nCalling ~w:~w(~p) on ~p~n", [M, F, A, CTNode]), rpc:call(CTNode, M, F, A). -- cgit v1.2.3 From d3a6ecb105706b66f1c0c6b8a515370df5e29bd3 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Sat, 5 Jun 2010 18:35:37 +0200 Subject: Add support for executing pre-loaded suites (e.g. modules loaded as binaries) Also fixed bug in test_server that calls end_per_testcase after test case timeout or abortion. --- lib/common_test/src/ct_run.erl | 58 ++++++++---- lib/common_test/test/ct_error_SUITE.erl | 20 +++- .../error/test/timetrap_1_SUITE.erl | 103 ++++++++++++++++----- lib/common_test/test/ct_misc_1_SUITE.erl | 37 +++++++- lib/test_server/src/test_server.erl | 17 +++- 5 files changed, 184 insertions(+), 51 deletions(-) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index ab900734f2..3d9b579a0c 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -1106,14 +1106,15 @@ do_run(Tests, Skip, Opts, Args) -> log_ts_names(Opts1#opts.testspecs), TestSuites = suite_tuples(Tests), - {SuiteMakeErrors,AllMakeErrors} = + {_TestSuites1,SuiteMakeErrors,AllMakeErrors} = case application:get_env(common_test, auto_compile) of {ok,false} -> - SuitesNotFound = verify_suites(TestSuites), - {SuitesNotFound,SuitesNotFound}; + {TestSuites1,SuitesNotFound} = + verify_suites(TestSuites), + {TestSuites1,SuitesNotFound,SuitesNotFound}; _ -> {SuiteErrs,HelpErrs} = auto_compile(TestSuites), - {SuiteErrs,SuiteErrs++HelpErrs} + {TestSuites,SuiteErrs,SuiteErrs++HelpErrs} end, case continue(AllMakeErrors) of @@ -1190,30 +1191,55 @@ auto_compile(TestSuites) -> verify_suites(TestSuites) -> io:nl(), Verify = - fun({Dir,Suite},NotFound) -> + fun({Dir,Suite}=DS,{Found,NotFound}) -> case locate_test_dir(Dir, Suite) of {ok,TestDir} -> if Suite == all -> - NotFound; + {[DS|Found],NotFound}; true -> - Beam = filename:join(TestDir, atom_to_list(Suite)++".beam"), + Beam = filename:join(TestDir, + atom_to_list(Suite)++".beam"), case filelib:is_regular(Beam) of true -> - NotFound; + {[DS|Found],NotFound}; false -> - Name = filename:join(TestDir, atom_to_list(Suite)), - io:format("Suite ~w not found in directory ~s~n", - [Suite,TestDir]), - [{{Dir,Suite},[Name]} | NotFound] + case code:is_loaded(Suite) of + {file,SuiteFile} -> + %% test suite is already loaded and + %% since auto_compile == false, + %% let's assume the user has + %% loaded the beam file explicitly + ActualDir = filename:dirname(SuiteFile), + {[{ActualDir,Suite}|Found],NotFound}; + false -> + Name = + filename:join(TestDir, + atom_to_list(Suite)), + io:format(user, + "Suite ~w not found" + "in directory ~s~n", + [Suite,TestDir]), + {Found,[{DS,[Name]}|NotFound]} + end end end; {error,_Reason} -> - io:format("Directory ~s is invalid~n", [Dir]), - Name = filename:join(Dir, atom_to_list(Suite)), - [{{Dir,Suite},[Name]} | NotFound] + case code:is_loaded(Suite) of + {file,SuiteFile} -> + %% test suite is already loaded and since + %% auto_compile == false, let's assume the + %% user has loaded the beam file explicitly + ActualDir = filename:dirname(SuiteFile), + {[{ActualDir,Suite}|Found],NotFound}; + false -> + io:format(user, "Directory ~s is invalid~n", [Dir]), + Name = filename:join(Dir, atom_to_list(Suite)), + {Found,[{DS,[Name]}|NotFound]} + end end end, - lists:reverse(lists:foldl(Verify, [], TestSuites)). + {ActualFound,Missing} = lists:foldl(Verify, {[],[]}, TestSuites), + {lists:reverse(ActualFound),lists:reverse(Missing)}. save_make_errors([]) -> []; diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index 199a1ad97a..b479d4133c 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -655,21 +655,33 @@ test_events(timetrap_end_conf) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{1,1,3}}, + {?eh,start_info,{1,1,6}}, {?eh,tc_start,{timetrap_1_SUITE,init_per_suite}}, {?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}}, {?eh,tc_start,{timetrap_1_SUITE,tc1}}, {?eh,tc_done, - {timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, + {timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}}, {?eh,test_stats,{0,1,{0,0}}}, {?eh,tc_start,{timetrap_1_SUITE,tc2}}, {?eh,tc_done, - {timetrap_1_SUITE,tc2,{failed,{testcase_aborted,testing_end_conf}}}}, + {timetrap_1_SUITE,tc2,{failed,{timetrap_timeout,1000}}}}, {?eh,test_stats,{0,2,{0,0}}}, {?eh,tc_start,{timetrap_1_SUITE,tc3}}, {?eh,tc_done, - {timetrap_1_SUITE,tc3,{failed,{testcase_aborted,testing_end_conf}}}}, + {timetrap_1_SUITE,tc3,{failed,{testcase_aborted,testing_end_conf}}}}, {?eh,test_stats,{0,3,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,tc4}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc4,{failed,{testcase_aborted,testing_end_conf}}}}, + {?eh,test_stats,{0,4,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,tc5}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc5,{failed,{timetrap_timeout,1000}}}}, + {?eh,test_stats,{0,5,{0,0}}}, + {?eh,tc_start,{timetrap_1_SUITE,tc6}}, + {?eh,tc_done, + {timetrap_1_SUITE,tc6,{failed,{testcase_aborted,testing_end_conf}}}}, + {?eh,test_stats,{0,6,{0,0}}}, {?eh,tc_start,{timetrap_1_SUITE,end_per_suite}}, {?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}}, {?eh,test_done,{'DEF','STOP_TIME'}}, diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl index 2e6432d05d..7a13a7c8a5 100644 --- a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl @@ -36,13 +36,19 @@ suite() -> %% Reason = term() %%-------------------------------------------------------------------- init_per_suite(Config) -> - Config. + TabPid = spawn(fun() -> + ets:new(?MODULE, [named_table, set, public]), + ets:insert(?MODULE, {last_case,ok}), + receive _ -> ok end + end), + [{tab,TabPid} | Config]. %%-------------------------------------------------------------------- %% Function: end_per_suite(Config0) -> void() | {save_config,Config1} %% Config0 = Config1 = [tuple()] %%-------------------------------------------------------------------- -end_per_suite(_Config) -> +end_per_suite(Config) -> + exit(?config(tab, Config), kill), ok. %%-------------------------------------------------------------------- @@ -71,10 +77,29 @@ end_per_group(_GroupName, _Config) -> %% Config0 = Config1 = [tuple()] %% Reason = term() %%-------------------------------------------------------------------- -init_per_testcase(tc3, Config) -> - [{default_timeout,5000}|Config]; -init_per_testcase(_TestCase, Config) -> - Config. +init_per_testcase(TC, Config) -> + {_,_} = process_info(?config(tab, Config), priority), + [{_,ok}] = ets:lookup(?MODULE, last_case), + ets:insert(?MODULE, {last_case,fail}), + init_per_testcase1(TC, Config). + +init_per_testcase1(tc1, Config) -> + [{tc,tc1}|Config]; + +init_per_testcase1(tc2, Config) -> + [{tc,tc2}|Config]; + +init_per_testcase1(tc3, Config) -> + [{tc,tc3}|Config]; + +init_per_testcase1(tc4, Config) -> + [{tc,tc4},{default_timeout,5000}|Config]; + +init_per_testcase1(tc5, Config) -> + [{tc,tc5}|Config]; + +init_per_testcase1(tc6, Config) -> + [{tc,tc6}|Config]. %%-------------------------------------------------------------------- %% Function: end_per_testcase(TestCase, Config0) -> @@ -82,18 +107,45 @@ init_per_testcase(_TestCase, Config) -> %% TestCase = atom() %% Config0 = Config1 = [tuple()] %%-------------------------------------------------------------------- -end_per_testcase(tc1, Config) -> - ct:pal("tc1: ~p", [Config]), +end_per_testcase(TC, Config) -> + {_,_} = process_info(?config(tab, Config), priority), + [{_,fail}] = ets:lookup(?MODULE, last_case), + ets:insert(?MODULE, {last_case,ok}), + end_per_testcase1(TC, Config). + +end_per_testcase1(tc1, Config) -> + ct:pal("end_per_testcase(tc1): ~p", [Config]), + tc1 = ?config(tc, Config), + {failed,timetrap_timeout} = ?config(tc_status, Config), ok; -end_per_testcase(tc2, Config) -> - ct:pal("tc2: ~p", [Config]), +end_per_testcase1(tc2, Config) -> + ct:pal("end_per_testcase(tc2): ~p", [Config]), + tc2 = ?config(tc, Config), + {failed,timetrap_timeout} = ?config(tc_status, Config), + timer:sleep(2000); + +end_per_testcase1(tc3, Config) -> + ct:pal("end_per_testcase(tc3): ~p", [Config]), + tc3 = ?config(tc, Config), + {failed,{testcase_aborted,testing_end_conf}} = ?config(tc_status, Config), ok; -end_per_testcase(tc3, Config) -> - ct:pal("tc3: ~p", [Config]), - ct:sleep(10000), - ok. +end_per_testcase1(tc4, Config) -> + ct:pal("end_per_testcase(tc4): ~p", [Config]), + tc4 = ?config(tc, Config), + {failed,{testcase_aborted,testing_end_conf}} = ?config(tc_status, Config), + timer:sleep(2000); + +end_per_testcase1(tc5, Config) -> + ct:pal("end_per_testcase(tc5): ~p", [Config]), + tc5 = ?config(tc, Config), + exit(end_per_tc_fail_after_timeout); + +end_per_testcase1(tc6, Config) -> + ct:pal("end_per_testcase(tc6): ~p", [Config]), + tc6 = ?config(tc, Config), + exit(end_per_tc_fail_after_abort). %%-------------------------------------------------------------------- %% Function: groups() -> [Group] @@ -118,18 +170,25 @@ groups() -> %% Reason = term() %%-------------------------------------------------------------------- all() -> - [tc1,tc2,tc3]. + [tc1, tc2, tc3, tc4, tc5, tc6]. tc1(_) -> - ct:sleep(3000), - ok. + timer:sleep(2000). tc2(_) -> - spawn(ct, abort_current_testcase, [testing_end_conf]), - timer:sleep(3000), - ok. + timer:sleep(2000). tc3(_) -> spawn(ct, abort_current_testcase, [testing_end_conf]), - timer:sleep(3000), - ok. + timer:sleep(2000). + +tc4(_) -> + spawn(ct, abort_current_testcase, [testing_end_conf]), + timer:sleep(2000). + +tc5(_) -> + timer:sleep(2000). + +tc6(_) -> + spawn(ct, abort_current_testcase, [testing_end_conf]), + timer:sleep(2000). diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl index f42081ee7e..e311876fb1 100644 --- a/lib/common_test/test/ct_misc_1_SUITE.erl +++ b/lib/common_test/test/ct_misc_1_SUITE.erl @@ -76,15 +76,15 @@ beam_me_up(Config) when is_list(Config) -> CTNode = ?config(ct_node, Config), %% Path = rpc:call(CTNode, code, get_path, []), - [_ | Parts] = lists:reverse(filename:split(DataDir)), - TSDir = filename:join(lists:reverse(Parts)), - true = rpc:call(CTNode, code, del_path, [TSDir]), + %% [_ | Parts] = lists:reverse(filename:split(DataDir)), + %% TSDir = filename:join(lists:reverse(Parts)), + %% true = rpc:call(CTNode, code, del_path, [TSDir]), Mods = [beam_1_SUITE, beam_2_SUITE], Suites = [atom_to_list(M) || M <- Mods], [{error,_} = rpc:call(CTNode, code, load_file, [M]) || M <- Mods], - code:add_path(TSDir), + code:add_path(DataDir), CRes = [compile:file(filename:join(DataDir,F), [verbose,report_errors, @@ -95,6 +95,7 @@ beam_me_up(Config) when is_list(Config) -> {ok,Mod,Bin} <- CRes], {Opts,ERPid} = setup([{suite,Suites},{auto_compile,false}], Config), + ok = ct_test_support:run(ct, run_test, [Opts], Config), Events = ct_test_support:get_events(ERPid, Config), @@ -135,4 +136,30 @@ events_to_check(Test, N) -> test_events(Test) ++ events_to_check(Test, N-1). test_events(beam_me_up) -> - []. + [ + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{2,2,4}}, + {?eh,tc_start,{beam_1_SUITE,init_per_suite}}, + {?eh,tc_done,{beam_1_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{beam_1_SUITE,tc1}}, + {?eh,tc_done,{beam_1_SUITE,tc1,ok}}, + {?eh,test_stats,{1,0,{0,0}}}, + {?eh,tc_start,{beam_1_SUITE,tc2}}, + {?eh,tc_done,{beam_1_SUITE,tc2,{failed,{error,'tc2 failed'}}}}, + {?eh,test_stats,{1,1,{0,0}}}, + {?eh,tc_start,{beam_1_SUITE,end_per_suite}}, + {?eh,tc_done,{beam_1_SUITE,end_per_suite,ok}}, + {?eh,tc_start,{beam_2_SUITE,init_per_suite}}, + {?eh,tc_done,{beam_2_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{beam_2_SUITE,tc1}}, + {?eh,tc_done,{beam_2_SUITE,tc1,ok}}, + {?eh,test_stats,{2,1,{0,0}}}, + {?eh,tc_start,{beam_2_SUITE,tc2}}, + {?eh,tc_done,{beam_2_SUITE,tc2,{failed,{error,'tc2 failed'}}}}, + {?eh,test_stats,{2,2,{0,0}}}, + {?eh,tc_start,{beam_2_SUITE,end_per_suite}}, + {?eh,tc_done,{beam_2_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} + ]. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index b70ac08622..d6de907a1b 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -876,10 +876,19 @@ call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) -> EndConfProc = fun() -> Supervisor = self(), - EndConfApply = fun() -> - apply(Mod,end_per_testcase,[Func,Conf]), - Supervisor ! {self(),end_conf} - end, + EndConfApply = + fun() -> + case catch apply(Mod,end_per_testcase,[Func,Conf]) of + {'EXIT',Why} -> + group_leader() ! {printout,12, + "ERROR! ~p:end_per_testcase(~p, ~p)" + " crashed!\n\tReason: ~p\n", + [Mod,Func,Conf,Why]}; + _ -> + ok + end, + Supervisor ! {self(),end_conf} + end, Pid = spawn_link(EndConfApply), receive {Pid,end_conf} -> -- cgit v1.2.3 From 331bb449c72078c315eff558158478445c4cb888 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Sat, 5 Jun 2010 20:57:10 +0200 Subject: Minor fixes in code and test suites --- lib/common_test/src/ct.erl | 6 ++- lib/common_test/src/ct_logs.erl | 2 +- lib/common_test/src/ct_run.erl | 83 ++++++++++++++++++++++---------- lib/common_test/src/ct_util.erl | 3 +- lib/common_test/src/vts.erl | 6 +-- lib/common_test/test/ct_misc_1_SUITE.erl | 2 +- lib/common_test/test/ct_test_support.erl | 1 - 7 files changed, 68 insertions(+), 35 deletions(-) diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 77bcf34981..eb16a04c7b 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -142,8 +142,9 @@ run(TestDirs) -> %%% {userconfig, UserConfig} | %%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} | %%% {allow_user_terms,Bool} | {logdir,LogDir} | -%%% {silent_connections,Conns} | {cover,CoverSpecFile} | -%%% {step,StepOpts} | {event_handler,EventHandlers} | {include,InclDirs} | +%%% {silent_connections,Conns} | {stylesheet,CSSFile} | +%%% {cover,CoverSpecFile} | {step,StepOpts} | +%%% {event_handler,EventHandlers} | {include,InclDirs} | %%% {auto_compile,Bool} | {multiply_timetraps,M} | {scale_timetraps,Bool} | %%% {repeat,N} | {duration,DurTime} | {until,StopTime} | %%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} | @@ -159,6 +160,7 @@ run(TestDirs) -> %%% TestSpecs = [string()] | string() %%% LogDir = string() %%% Conns = all | [atom()] +%%% CSSFile = string() %%% CoverSpecFile = string() %%% StepOpts = [StepOpt] | [] %%% StepOpt = config | keep_inactive diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index bd6fdfcd4f..8a4432ef08 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -505,7 +505,7 @@ logger_loop(State) -> logger_loop(State); {set_stylesheet,TC,SSFile} -> Fd = State#logger_state.ct_log_fd, - io:format(Fd, "~p uses external style sheet: ~s~n", [TC,SSFile]), + io:format(Fd, "~p loading external style sheet: ~s~n", [TC,SSFile]), logger_loop(State#logger_state{stylesheet=SSFile}); {clear_stylesheet,_} when State#logger_state.stylesheet == undefined -> logger_loop(State); diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 3d9b579a0c..4b9c9b6981 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -55,6 +55,7 @@ event_handlers = [], include = [], silent_connections, + stylesheet, multiply_timetraps = 1, scale_timetraps = false, testspecs = [], @@ -170,7 +171,7 @@ script_start1(Parent, Args) -> case proplists:get_value(ct_decrypt_file, Args) of [DecryptFile] -> application:set_env(common_test, decrypt, - {file,filename:absname(DecryptFile)}); + {file,?abs(DecryptFile)}); undefined -> application:unset_env(common_test, decrypt) end @@ -182,9 +183,9 @@ script_start1(Parent, Args) -> application:set_env(common_test, auto_compile, true), InclDirs = case proplists:get_value(include, Args) of - {include,Incl} when is_list(hd(Incl)) -> + Incl when is_list(hd(Incl)) -> Incl; - {include,Incl} when is_list(Incl) -> + Incl when is_list(Incl) -> [Incl]; undefined -> [] @@ -203,6 +204,15 @@ script_start1(Parent, Args) -> application:set_env(common_test, auto_compile, false), [] end, + %% silent connections + SilentConns = + get_start_opt(silent_connections, + fun(["all"]) -> []; + (Conns) -> [list_to_atom(Conn) || Conn <- Conns] + end, Args), + %% stylesheet + Stylesheet = get_start_opt(stylesheet, + fun([SS]) -> ?abs(SS) end, Args), %% basic_html - used by ct_logs case proplists:get_value(basic_html, Args) of undefined -> @@ -214,6 +224,8 @@ script_start1(Parent, Args) -> StartOpts = #opts{vts = Vts, shell = Shell, cover = Cover, logdir = LogDir, event_handlers = EvHandlers, include = IncludeDirs, + silent_connections = SilentConns, + stylesheet = Stylesheet, multiply_timetraps = MultTT, scale_timetraps = ScaleTT}, @@ -326,7 +338,16 @@ script_start2(StartOpts = #opts{vts = undefined, end; script_start2(StartOpts, Args) -> - script_start3(StartOpts, Args). + %% read config/userconfig from start flags + InitConfig = ct_config:prepare_config_list(Args), + case check_and_install_configfiles(InitConfig, + which(logdir,StartOpts#opts.logdir), + StartOpts#opts.event_handlers) of + ok -> % go on read tests from start flags + script_start3(StartOpts#opts{config=InitConfig}, Args); + Error -> + Error + end. check_and_install_configfiles(Configs, LogDir, EvHandlers) -> case ct_config:check_config_files(Configs) of @@ -386,7 +407,17 @@ script_start3(StartOpts, Args) -> script_start4(#opts{vts = true, config = Config, event_handlers = EvHandlers, tests = Tests, logdir = LogDir}, _Args) -> - vts:init_data(Config, EvHandlers, ?abs(LogDir), Tests); + ConfigFiles = + lists:foldl(fun({ct_config_plain,CfgFiles}, AllFiles) when + is_list(hd(CfgFiles)) -> + AllFiles ++ CfgFiles; + ({ct_config_plain,CfgFile}, AllFiles) when + is_integer(hd(CfgFile)) -> + AllFiles ++ [CfgFile]; + (_, AllFiles) -> + AllFiles + end, [], Config), + vts:init_data(ConfigFiles, EvHandlers, ?abs(LogDir), Tests); script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers, logdir = LogDir, testspecs = Specs}, _Args) -> @@ -396,6 +427,12 @@ script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers, true -> io:format("\nInstalling: ~p\n\n", [Config]) end, + + %%! --- Sun Jun 6 00:58:41 2010 --- peppe was here! + %%! HERE!! + %%! Something's not right here. Can't start shell mode + %%! properly! + case install(InstallOpts) of ok -> ct_util:start(interactive, LogDir), @@ -610,8 +647,13 @@ run_test1(StartOpts) -> fun(all) -> []; (Conns) -> Conns end, StartOpts), + %% stylesheet + Stylesheet = get_start_opt(stylesheet, + fun(SS) -> ?abs(SS) end, + StartOpts), %% code coverage - Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, StartOpts), + Cover = get_start_opt(cover, + fun(CoverFile) -> ?abs(CoverFile) end, StartOpts), %% timetrap manipulation MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts), @@ -653,7 +695,7 @@ run_test1(StartOpts) -> Key={key,_} -> application:set_env(common_test, decrypt, Key); {file,KeyFile} -> - application:set_env(common_test, decrypt, {file,filename:absname(KeyFile)}) + application:set_env(common_test, decrypt, {file,?abs(KeyFile)}) end, %% basic html - used by ct_logs @@ -669,7 +711,9 @@ run_test1(StartOpts) -> Opts = #opts{cover = Cover, step = Step, logdir = LogDir, config = CfgFiles, event_handlers = EvHandlers, include = Include, - silent_connections = SilentConns, multiply_timetraps = MultiplyTT, + silent_connections = SilentConns, + stylesheet = Stylesheet, + multiply_timetraps = MultiplyTT, scale_timetraps = ScaleTT}, %% test specification @@ -1079,25 +1123,14 @@ do_run(Tests, Skip, Opts, Args) -> "run ct:start_interactive()\n\n",[]), {error,interactive_mode}; _Pid -> - %% save style sheet info - case lists:keysearch(stylesheet, 1, Args) of - {value,{_,SSFile}} -> - ct_util:set_testdata({stylesheet,SSFile}); - _ -> - ct_util:set_testdata({stylesheet,undefined}) - end, - - case lists:keysearch(silent_connections, 1, Args) of - {value,{silent_connections,undefined}} -> - ok; - {value,{silent_connections,[]}} -> + %% save stylesheet info + ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}), + %% enable silent connections + case Opts#opts.silent_connections of + [] -> Conns = ct_util:override_silence_all_connections(), ct_logs:log("Silent connections", "~p", [Conns]); - {value,{silent_connections,Cs}} -> - Conns = lists:map(fun(S) when is_list(S) -> - list_to_atom(S); - (A) -> A - end, Cs), + Conns when is_list(Conns) -> ct_util:override_silence_connections(Conns), ct_logs:log("Silent connections", "~p", [Conns]); _ -> diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl index f1692caf24..40925b761a 100644 --- a/lib/common_test/src/ct_util.erl +++ b/lib/common_test/src/ct_util.erl @@ -125,7 +125,6 @@ do_start(Parent,Mode,LogDir) -> Parent ! {self(),Error}, exit(Error) end, - %% start an event manager (if not already started by master) case ct_event:start_link() of {error,{already_started,_}} -> @@ -305,9 +304,9 @@ loop(Mode,TestData,StartDir) -> ets:delete(?board_table), ets:delete(?suite_table), ct_logs:close(How), - file:set_cwd(StartDir), ct_event:stop(), ct_config:stop(), + file:set_cwd(StartDir), return(From,ok); {get_mode,From} -> return(From,Mode), diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl index 22399aff2a..eb6d82d601 100644 --- a/lib/common_test/src/vts.erl +++ b/lib/common_test/src/vts.erl @@ -160,9 +160,9 @@ init(Parent) -> loop(State) -> receive - {{init_data,ConfigFiles,EvHandlers,LogDir,Tests},From} -> - ct:pal("State#state.current_log_dir=~p", [State#state.current_log_dir]), - NewState = State#state{config=ConfigFiles,event_handler=EvHandlers, + {{init_data,Config,EvHandlers,LogDir,Tests},From} -> + %% ct:pal("State#state.current_log_dir=~p", [State#state.current_log_dir]), + NewState = State#state{config=Config,event_handler=EvHandlers, current_log_dir=LogDir,tests=Tests}, ct_install(NewState), return(From,ok), diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl index e311876fb1..94865b68f0 100644 --- a/lib/common_test/test/ct_misc_1_SUITE.erl +++ b/lib/common_test/test/ct_misc_1_SUITE.erl @@ -103,7 +103,7 @@ beam_me_up(Config) when is_list(Config) -> reformat(Events, ?eh), ?config(priv_dir, Config)), - TestEvents = events_to_check(beam_me_up), + TestEvents = events_to_check(beam_me_up, 1), ok = ct_test_support:verify_events(TestEvents, Events, Config). %%%----------------------------------------------------------------- diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index eaf3e23859..b3387e0ccf 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -280,7 +280,6 @@ verify_events1([TestEv|_], [{TEH,#event{name=stop_logging,node=Node,data=_}}|_], exit({event_not_found,TestEv}); verify_events1(TEvs = [TestEv | TestEvs], Evs = [_|Events], Node, Config) -> -%% test_server:format("Next expected event: ~p~n", [TestEv]), case catch locate(TestEv, Node, Evs, Config) of nomatch -> verify_events1(TEvs, Events, Node, Config); -- cgit v1.2.3 From adfd6da3afe99ca20d8a0c91a46453b74f8206cc Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Sun, 6 Jun 2010 01:06:21 +0200 Subject: Fix failing multiply timetrap test case --- lib/test_server/test/test_server_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_server/test/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE.erl index dfe1028d3a..0563e1104f 100644 --- a/lib/test_server/test/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE.erl @@ -183,7 +183,7 @@ multiply_timetrap(suite) -> []; multiply_timetrap(doc) -> ["Test multiply timetrap"]; multiply_timetrap(Config) when is_list(Config) -> %% This simulates the call to test_server_ctrl:multiply_timetraps/1: - put(test_server_multiply_timetraps,2), + put(test_server_multiply_timetraps,{2,true}), Dog = ?t:timetrap(500), timer:sleep(800), -- cgit v1.2.3 From e2698f918a4ec969593181fdad7d86064ef462f0 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 7 Jun 2010 10:34:11 +0200 Subject: Fix bug that prevents the interactive shell mode to start properly --- lib/common_test/src/ct_run.erl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 4b9c9b6981..2d79021ce4 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -154,7 +154,7 @@ script_start1(Parent, Args) -> Vts = get_start_opt(vts, true, Args), Shell = get_start_opt(shell, true, Args), Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args), - LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), + LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, ".", Args), MultTT = get_start_opt(multiply_timetraps, fun([MT]) -> list_to_integer(MT) end, 1, Args), ScaleTT = get_start_opt(scale_timetraps, @@ -427,12 +427,6 @@ script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers, true -> io:format("\nInstalling: ~p\n\n", [Config]) end, - - %%! --- Sun Jun 6 00:58:41 2010 --- peppe was here! - %%! HERE!! - %%! Something's not right here. Can't start shell mode - %%! properly! - case install(InstallOpts) of ok -> ct_util:start(interactive, LogDir), -- cgit v1.2.3 From 4ed31623e145531a1aa84f6c1091481820f62099 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 7 Jun 2010 12:24:26 +0200 Subject: Fix bug in handling framework:end_tc timeouts --- lib/common_test/test/ct_error_SUITE.erl | 17 +++++- .../error/test/cfg_error_12_SUITE.erl | 68 ++++++++++++++++++++++ lib/test_server/src/test_server.erl | 1 + 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index b479d4133c..7327fa5816 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -89,7 +89,8 @@ cfg_error(Config) when is_list(Config) -> Join(DataDir, "cfg_error_8_SUITE"), Join(DataDir, "cfg_error_9_SUITE"), Join(DataDir, "cfg_error_10_SUITE"), - Join(DataDir, "cfg_error_11_SUITE") + Join(DataDir, "cfg_error_11_SUITE"), + Join(DataDir, "cfg_error_12_SUITE") ], {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), @@ -230,7 +231,7 @@ test_events(cfg_error) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{11,11,36}}, + {?eh,start_info,{12,12,39}}, {?eh,tc_start,{cfg_error_1_SUITE,init_per_suite}}, {?eh,tc_done, @@ -563,6 +564,18 @@ test_events(cfg_error) -> {?eh,test_stats,{13,3,{1,19}}}, {?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}}, {?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}}, + {?eh,tc_start,{cfg_error_12_SUITE,tc1}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc1,{failed,{timetrap_timeout,500}}}}, + {?eh,test_stats,{13,4,{1,19}}}, + {?eh,tc_start,{cfg_error_12_SUITE,tc2}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc2, + {failed, + {cfg_error_12_SUITE,end_per_testcase, + {timetrap_timeout,500}}}}}, + {?eh,test_stats,{14,4,{1,19}}}, + {?eh,tc_start,{cfg_error_12_SUITE,tc3}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}}, + {?eh,test_stats,{15,4,{1,19}}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl new file mode 100644 index 0000000000..271eaa673e --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl @@ -0,0 +1,68 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cfg_error_12_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +end_per_testcase(tc2, _Config) -> + timer:sleep(2000), + exit(this_should_not_be_printed); +end_per_testcase(_, _) -> + ok. + +all() -> + [tc1, tc2, tc3]. + +%%%----------------------------------------------------------------- +tc1() -> + put('$test_server_framework_test', + fun(init_tc, _Default) -> + ct:pal("init_tc(~p): Night time...",[self()]), + timer:sleep(2000), + ct:pal("init_tc(~p): Day time!",[self()]), + exit(this_should_not_be_printed); + (_, Default) -> Default + end), + [{timetrap,500}]. + +tc1(_) -> + exit(this_should_not_be_printed). + +%%%----------------------------------------------------------------- +tc2() -> + [{timetrap,500}]. + +tc2(_) -> + ok. + +%%%----------------------------------------------------------------- +tc3() -> + [{timetrap,500}]. + +tc3(_) -> + put('$test_server_framework_test', + fun(end_tc, _Default) -> + ct:pal("end_tc(~p): Night time...",[self()]), + timer:sleep(1000), + ct:pal("end_tc(~p): Day time!",[self()]); + (_, Default) -> Default + end), + {comment,"should succeed since ct_fw cancels timetrap in end_tc"}. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index d6de907a1b..940935edf5 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -1126,6 +1126,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) -> _ -> {FWReturn,TSReturn,EndConf1} end, + put(test_server_init_or_end_conf,undefined), case test_server_sup:framework_call(end_tc, [?pl2a(Mod), Func, {FWReturn1,[EndConf2]}]) of {fail,Reason} -> -- cgit v1.2.3 From 2dbeff631ce93744972cb04039d434aa172d28f4 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 7 Jun 2010 17:28:01 +0200 Subject: Fix so that ct_run converts relative diretories in the code path to absolute Directories added with run_test -pa or -pz in the pre erl_args part of the command line will be converted from relative to absolute, this to avoid confusion when Common Test switches working directory during the test run. Also fixed in this commit: Problem with timeouts during init_tc or end_tc (in the Test Server framework). --- lib/common_test/src/ct_run.erl | 52 +++++++- lib/common_test/test/Makefile | 14 +-- lib/common_test/test/ct_error_SUITE.erl | 35 ++++-- .../error/test/cfg_error_12_SUITE.erl | 22 +++- .../error/test/cfg_error_13_SUITE.erl | 47 +++++++ .../error/test/cfg_error_14_SUITE.erl | 46 +++++++ lib/test_server/src/test_server.erl | 136 +++++++++++++-------- lib/test_server/src/test_server_sup.erl | 1 + 8 files changed, 282 insertions(+), 71 deletions(-) create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl create mode 100644 lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 2d79021ce4..9dc0bff26a 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -77,6 +77,12 @@ script_start() -> Init = init:get_arguments(), CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false; (_) -> true end, Init), + + %% convert relative dirs added with pa or pz (pre erl_args on + %% the run_test command line) to absolute so that app modules + %% can be found even after CT changes CWD to logdir + rel_to_abs(CtArgs), + Args = case application:get_env(common_test, run_test_start_opts) of {ok,EnvStartOpts} -> @@ -1910,9 +1916,51 @@ event_handler_init_args2opts([EH, Arg]) -> event_handler_init_args2opts([]) -> []. -%% this function translates ct:run_test/1 start options +%% This function reads pa and pz arguments, converts dirs from relative +%% to absolute, and re-inserts them in the code path. The order of the +%% dirs in the code path remain the same. Note however that since this +%% function is only used for arguments "pre run_test erl_args", the order +%% 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 -> + io:format(user, "Converting ~p to ~p and re-inserting " + "with add_pathz/1~n", + [D, Abs]); + true -> + ok + end + end || D <- PZ], + [begin + code:del_path(filename:basename(D)), + Abs = filename:absname(D), + code:add_patha(Abs), + if D /= Abs -> + io:format(user, "Converting ~p to ~p and re-inserting " + "with add_patha/1~n", + [D, Abs]); + true ->ok + end + end || D <- PA], + io:format(user, "~n", []). + +get_pa_pz([{pa,Dirs} | Args], PA, PZ) -> + get_pa_pz(Args, PA ++ Dirs, PZ); +get_pa_pz([{pz,Dirs} | Args], PA, PZ) -> + get_pa_pz(Args, PA, PZ ++ Dirs); +get_pa_pz([_ | Args], PA, PZ) -> + get_pa_pz(Args, PA, PZ); +get_pa_pz([], PA, PZ) -> + {PA,PZ}. + +%% This function translates ct:run_test/1 start options %% to run_test start arguments (on the init arguments format) - -%% this is useful mainly for testing the ct_run start functions +%% this is useful mainly for testing the ct_run start functions. opts2args(EnvStartOpts) -> lists:flatmap(fun({config,CfgFiles}) -> [{ct_config,[CfgFiles]}]; diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 2f1ff9a835..97ded5eb9a 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -65,18 +65,18 @@ EBIN = . # Targets # ---------------------------------------------------- -.PHONY: make_emakefile +#.PHONY: make_emakefile -make_emakefile: - $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\ - > $(EMAKEFILE) +#make_emakefile: +# $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES)\ +# > $(EMAKEFILE) -tests debug opt: make_emakefile +tests debug opt: erl $(ERL_MAKE_FLAGS) -make clean: - rm -f $(EMAKEFILE) rm -f $(TARGET_FILES) +# rm -f $(EMAKEFILE) rm -f core docs: @@ -88,7 +88,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk release_spec: opt -release_tests_spec: make_emakefile +release_tests_spec: $(INSTALL_DIR) $(RELSYSDIR) $(INSTALL_DATA) $(ERL_FILES) $(COVERFILE) $(RELSYSDIR) $(INSTALL_PROGRAM) common_test.spec $(RELSYSDIR) diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl index 7327fa5816..2fa031b884 100644 --- a/lib/common_test/test/ct_error_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE.erl @@ -90,7 +90,9 @@ cfg_error(Config) when is_list(Config) -> Join(DataDir, "cfg_error_9_SUITE"), Join(DataDir, "cfg_error_10_SUITE"), Join(DataDir, "cfg_error_11_SUITE"), - Join(DataDir, "cfg_error_12_SUITE") + Join(DataDir, "cfg_error_12_SUITE"), + Join(DataDir, "cfg_error_13_SUITE"), + Join(DataDir, "cfg_error_14_SUITE") ], {Opts,ERPid} = setup([{suite,Suites}], Config), ok = ct_test_support:run(Opts, Config), @@ -231,7 +233,7 @@ test_events(cfg_error) -> [ {?eh,start_logging,{'DEF','RUNDIR'}}, {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {?eh,start_info,{12,12,39}}, + {?eh,start_info,{14,14,42}}, {?eh,tc_start,{cfg_error_1_SUITE,init_per_suite}}, {?eh,tc_done, @@ -568,15 +570,34 @@ test_events(cfg_error) -> {?eh,tc_done,{cfg_error_12_SUITE,tc1,{failed,{timetrap_timeout,500}}}}, {?eh,test_stats,{13,4,{1,19}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc2}}, - {?eh,tc_done,{cfg_error_12_SUITE,tc2, - {failed, - {cfg_error_12_SUITE,end_per_testcase, - {timetrap_timeout,500}}}}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc2,{failed, + {cfg_error_12_SUITE,end_per_testcase, + {timetrap_timeout,500}}}}}, {?eh,test_stats,{14,4,{1,19}}}, {?eh,tc_start,{cfg_error_12_SUITE,tc3}}, {?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}}, {?eh,test_stats,{15,4,{1,19}}}, - + {?eh,tc_start,{cfg_error_12_SUITE,tc4}}, + {?eh,tc_done,{cfg_error_12_SUITE,tc4,{failed, + {cfg_error_12_SUITE,end_per_testcase, + {timetrap_timeout,500}}}}}, + {?eh,test_stats,{16,4,{1,19}}}, + {?eh,tc_start,{cfg_error_13_SUITE,init_per_suite}}, + {?eh,tc_done,{cfg_error_13_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{cfg_error_13_SUITE,tc1}}, + {?eh,tc_done,{cfg_error_13_SUITE,tc1,ok}}, + {?eh,test_stats,{17,4,{1,19}}}, + {?eh,tc_start,{cfg_error_13_SUITE,end_per_suite}}, + {?eh,tc_done,{cfg_error_13_SUITE,end_per_suite,ok}}, + {?eh,tc_start,{cfg_error_14_SUITE,init_per_suite}}, + {?eh,tc_done,{cfg_error_14_SUITE,init_per_suite,ok}}, + {?eh,tc_start,{cfg_error_14_SUITE,tc1}}, + {?eh,tc_done,{cfg_error_14_SUITE,tc1,ok}}, + {?eh,test_stats,{18,4,{1,19}}}, + {?eh,tc_start,{cfg_error_14_SUITE,end_per_suite}}, + {?eh,tc_done,{cfg_error_14_SUITE,end_per_suite, + {comment, + "should succeed since ct_fw cancels timetrap in end_tc"}}}, {?eh,test_done,{'DEF','STOP_TIME'}}, {?eh,stop_logging,[]} ]; diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl index 271eaa673e..75f00c1de3 100644 --- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl @@ -22,14 +22,20 @@ -include_lib("common_test/include/ct.hrl"). +init_per_testcase(_, Config) -> + Config. + end_per_testcase(tc2, _Config) -> timer:sleep(2000), exit(this_should_not_be_printed); +end_per_testcase(tc4, _Config) -> + timer:sleep(2000), + exit(this_should_not_be_printed); end_per_testcase(_, _) -> ok. all() -> - [tc1, tc2, tc3]. + [tc1, tc2, tc3, tc4]. %%%----------------------------------------------------------------- tc1() -> @@ -66,3 +72,17 @@ tc3(_) -> (_, Default) -> Default end), {comment,"should succeed since ct_fw cancels timetrap in end_tc"}. + +%%%----------------------------------------------------------------- +tc4() -> + put('$test_server_framework_test', + fun(end_tc, _Default) -> + ct:pal("end_tc(~p): Night time...",[self()]), + timer:sleep(1000), + ct:pal("end_tc(~p): Day time!",[self()]); + (_, Default) -> Default + end), + [{timetrap,500}]. + +tc4(_) -> + ok. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl new file mode 100644 index 0000000000..d1c18d0bb0 --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl @@ -0,0 +1,47 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cfg_error_13_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +init_per_suite() -> + put('$test_server_framework_test', + fun(end_tc, _Default) -> + ct:pal("end_tc(~p): Night time...",[self()]), + timer:sleep(1000), + ct:pal("end_tc(~p): Day time!",[self()]); + (_, Default) -> Default + end), + [{timetrap,500}]. + +init_per_suite(Config) -> + ct:comment("should succeed since ct_fw cancels timetrap in end_tc"), + Config. + +end_per_suite(_) -> + ok. + +all() -> + [tc1]. + +%%%----------------------------------------------------------------- +tc1(_) -> + dummy. diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl new file mode 100644 index 0000000000..367f5f06d6 --- /dev/null +++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl @@ -0,0 +1,46 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2010. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cfg_error_14_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +init_per_suite(Config) -> + Config. + +end_per_suite() -> + put('$test_server_framework_test', + fun(end_tc, _Default) -> + ct:pal("end_tc(~p): Night time...",[self()]), + timer:sleep(1000), + ct:pal("end_tc(~p): Day time!",[self()]); + (_, Default) -> Default + end), + [{timetrap,500}]. + +end_per_suite(_Config) -> + {comment,"should succeed since ct_fw cancels timetrap in end_tc"}. + +all() -> + [tc1]. + +%%%----------------------------------------------------------------- +tc1(_) -> + dummy. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 940935edf5..acc9dbaab8 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -737,64 +737,91 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment, CurrConf) -> case Reason of {timetrap_timeout,TVal,Loc} -> %% convert Loc to form that can be formatted - Loc1 = mod_loc(Loc), - {Mod,Func} = get_mf(Loc1), - %% call end_per_testcase on a separate process, only so that the - %% user has a chance to clean up after init_per_testcase, even - %% after a timetrap timeout - NewCurrConf = - case CurrConf of - {{Mod,Func},Conf} -> - EndConfPid = - call_end_conf(Mod,Func,Pid, - {timetrap_timeout,TVal}, - Loc1,[{tc_status, - {failed, - timetrap_timeout}}|Conf], - TVal), - {EndConfPid,{Mod,Func},Conf}; - _ -> - %% The framework functions mustn't execute on this - %% group leader process or io will cause deadlock, - %% so we spawn a dedicated process for the operation - %% and let the group leader go back to handle io. - spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal}, - Loc1,self(),Comment), - undefined - end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, - Comment,NewCurrConf); + case mod_loc(Loc) of + {FwMod,FwFunc,framework} -> + %% timout during framework call + spawn_fw_call(FwMod,FwFunc,Pid, + {framework_error,{timetrap,TVal}}, + unknown,self(),Comment), + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, + Comment,undefined); + Loc1 -> + {Mod,Func} = get_mf(Loc1), + %% call end_per_testcase on a separate process, + %% only so that the user has a chance to clean up + %% after init_per_testcase, even after a timetrap timeout + NewCurrConf = + case CurrConf of + {{Mod,Func},Conf} -> + EndConfPid = + call_end_conf(Mod,Func,Pid, + {timetrap_timeout,TVal}, + Loc1,[{tc_status, + {failed, + timetrap_timeout}}|Conf], + TVal), + {EndConfPid,{Mod,Func},Conf}; + _ -> + %% The framework functions mustn't execute on this + %% group leader process or io will cause deadlock, + %% so we spawn a dedicated process for the operation + %% and let the group leader go back to handle io. + spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal}, + Loc1,self(),Comment), + undefined + end, + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, + Comment,NewCurrConf) + end; {timetrap_timeout,TVal,Loc,InitOrEnd} -> - Loc1 = mod_loc(Loc), - {Mod,_Func} = get_mf(Loc1), - spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal}, - Loc1,self(),Comment), + case mod_loc(Loc) of + {FwMod,FwFunc,framework} -> + %% timout during framework call + spawn_fw_call(FwMod,FwFunc,Pid, + {framework_error,{timetrap,TVal}}, + unknown,self(),Comment); + Loc1 -> + {Mod,_Func} = get_mf(Loc1), + spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal}, + Loc1,self(),Comment) + end, run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf); {testcase_aborted,AbortReason,AbortLoc} -> - Loc1 = mod_loc(AbortLoc), - {Mod,Func} = get_mf(Loc1), - %% call end_per_testcase on a separate process, only so that the - %% user has a chance to clean up after init_per_testcase, even - %% after abortion ErrorMsg = {testcase_aborted,AbortReason}, - NewCurrConf = - case CurrConf of - {{Mod,Func},Conf} -> - TVal = case lists:keysearch(default_timeout,1,Conf) of - {value,{default_timeout,Tmo}} -> Tmo; - _ -> ?DEFAULT_TIMETRAP_SECS*1000 - end, - EndConfPid = - call_end_conf(Mod,Func,Pid,ErrorMsg, - Loc1,[{tc_status,{failed,ErrorMsg}}|Conf], - TVal), - {EndConfPid,{Mod,Func},Conf}; - _ -> - spawn_fw_call(Mod,Func,Pid,ErrorMsg, - Loc1,self(),Comment), - undefined - end, - run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,NewCurrConf); + case mod_loc(AbortLoc) of + {FwMod,FwFunc,framework} -> + %% abort during framework call + spawn_fw_call(FwMod,FwFunc,Pid, + {framework_error,ErrorMsg}, + unknown,self(),Comment), + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, + Comment,undefined); + Loc1 -> + {Mod,Func} = get_mf(Loc1), + %% call end_per_testcase on a separate process, only so + %% that the user has a chance to clean up after init_per_testcase, + %% even after abortion + NewCurrConf = + case CurrConf of + {{Mod,Func},Conf} -> + TVal = case lists:keysearch(default_timeout,1,Conf) of + {value,{default_timeout,Tmo}} -> Tmo; + _ -> ?DEFAULT_TIMETRAP_SECS*1000 + end, + EndConfPid = + call_end_conf(Mod,Func,Pid,ErrorMsg, + Loc1, + [{tc_status,{failed,ErrorMsg}}|Conf], + TVal), + {EndConfPid,{Mod,Func},Conf}; + _ -> + spawn_fw_call(Mod,Func,Pid,ErrorMsg, + Loc1,self(),Comment), + undefined + end, + run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate, + Comment,NewCurrConf) + end; killed -> %% result of an exit(TestCase,kill) call, which is the %% only way to abort a testcase process that traps exits @@ -920,6 +947,7 @@ spawn_fw_call(Mod,{init_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why, {TVal/1000,Skip,Loc,[],Comment}} end, spawn_link(FwCall); + spawn_fw_call(Mod,{end_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why, Loc,SendTo,_Comment) -> FwCall = diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl index 2b4e1efb71..625724fbb5 100644 --- a/lib/test_server/src/test_server_sup.erl +++ b/lib/test_server/src/test_server_sup.erl @@ -504,6 +504,7 @@ framework_call(Callback,Func,Args,DefaultReturn) -> end, case erlang:function_exported(Mod,Func,length(Args)) of true -> + put(test_server_loc, {Mod,Func,framework}), EH = fun(Reason) -> exit({fw_error,{Mod,Func,Reason}}) end, try apply(Mod,Func,Args) of Result -> -- cgit v1.2.3 From 10a06d2d2d1f967608877a8de2ad9c7fc5702353 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 7 Jun 2010 18:41:43 +0200 Subject: Make {repeat*,N} property in group execute the group N times exactly To be consistent with the behaviour of the run_test repeat flag/option, the repeat* group property has been changed to specify absolute number of test runs. Previously {repeat,N} meant "execute the group 1 time + N repeats". Now it means "execute the group N times". --- lib/common_test/test/ct_groups_test_1_SUITE.erl | 40 ++++++++--------- .../groups_1/test/groups_12_SUITE.erl | 6 +-- .../groups_2/test/groups_22_SUITE.erl | 6 +-- lib/common_test/test/ct_groups_test_2_SUITE.erl | 32 +++++++------- .../groups_1/repeat_1_SUITE.erl | 8 ++-- lib/test_server/src/test_server_ctrl.erl | 50 +++++++++++++++++----- 6 files changed, 86 insertions(+), 56 deletions(-) diff --git a/lib/common_test/test/ct_groups_test_1_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE.erl index 18a00f7f2b..64d61fc104 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE.erl @@ -335,14 +335,14 @@ test_events(groups_suite_2) -> {?eh,tc_start,{groups_12_SUITE,testcase_2a}}, {?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}}, - [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}}, + [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, {?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3b}}, {?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}}, - {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}], + {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}], [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}}, @@ -529,14 +529,14 @@ test_events(groups_suites_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_2a}}, {?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}}, - [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}}, + [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, {?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3b}}, {?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}}, - {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}], + {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}], [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, @@ -715,14 +715,14 @@ test_events(groups_dir_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_2a}}, {?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}}, - [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}}, + [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, {?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3b}}, {?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}}, - {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}], + {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}], [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, @@ -902,14 +902,14 @@ test_events(groups_dirs_1) -> {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_2a}}, {?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}}, - [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}}, + [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, {?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3b}}, {?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}}, - {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}}, - {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}], + {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}}, + {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}], [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}}, {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}}, {?eh,tc_start,{groups_12_SUITE,testcase_3a}}, @@ -1130,17 +1130,17 @@ test_events(groups_dirs_1) -> {?eh,tc_start,{groups_22_SUITE,testcase_2a}}, {?eh,tc_done,{groups_22_SUITE,testcase_2a,ok}}, [{?eh,tc_start, - {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}}, + {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}}, {?eh,tc_done, - {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}}, + {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}}, {?eh,tc_start,{groups_22_SUITE,testcase_3a}}, {?eh,tc_done,{groups_22_SUITE,testcase_3a,ok}}, {?eh,tc_start,{groups_22_SUITE,testcase_3b}}, {?eh,tc_done,{groups_22_SUITE,testcase_3b,ok}}, {?eh,tc_start, - {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}}, + {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}}, {?eh,tc_done, - {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}], + {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}], [{?eh,tc_start, {groups_22_SUITE,{init_per_group,test_group_3,[]}}}, {?eh,tc_done, diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl index 22eacde1f3..ec90ef95d1 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl @@ -37,7 +37,7 @@ groups() -> {test_group_2, [parallel], [testcase_2a, - {test_group_3, [{repeat,1}], + {test_group_3, [{repeat,2}], [testcase_3a, testcase_3b]}, testcase_2b]}, @@ -102,8 +102,8 @@ init_per_group(Group, Config) -> io_lib:format("shuffled, ~w", [S]); {test_group_1b,[{name,test_group_1b},parallel]} -> "parallel"; {test_group_2,[{name,test_group_2},parallel]} -> "parallel"; - {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1"; - {test_group_3,[{name,test_group_3}]} -> "repeat 0"; + {test_group_3,[{name,test_group_3},{repeat,2}]} -> "repeat 2"; + {test_group_3,[{name,test_group_3}]} -> "repeat 1"; {test_group_4,[{name,test_group_4}]} -> ok; {test_group_5,[{name,test_group_5},parallel]} -> "parallel"; {test_group_6,[{name,test_group_6},parallel]} -> "parallel"; diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl index 2e19cf6310..ec0adc5df0 100644 --- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl @@ -37,7 +37,7 @@ groups() -> {test_group_2, [parallel], [testcase_2a, - {test_group_3, [{repeat,1}], + {test_group_3, [{repeat,2}], [testcase_3a, testcase_3b]}, testcase_2b]}, @@ -102,8 +102,8 @@ init_per_group(Group, Config) -> io_lib:format("shuffled, ~w", [S]); {test_group_1b,[{name,test_group_1b},parallel]} -> "parallel"; {test_group_2,[{name,test_group_2},parallel]} -> "parallel"; - {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1"; - {test_group_3,[{name,test_group_3}]} -> "repeat 0"; + {test_group_3,[{name,test_group_3},{repeat,2}]} -> "repeat 2"; + {test_group_3,[{name,test_group_3}]} -> "repeat 1"; {test_group_4,[{name,test_group_4}]} -> ok; {test_group_5,[{name,test_group_5},parallel]} -> "parallel"; {test_group_6,[{name,test_group_6},parallel]} -> "parallel"; diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl index bdf7303a59..56e0ac30c7 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl @@ -184,9 +184,9 @@ test_events(repeat_1) -> {?eh,tc_start,{repeat_1_SUITE,init_per_suite}}, {?eh,tc_done,{repeat_1_SUITE,init_per_suite,ok}}, [{?eh,tc_start, - {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,1}]}}}, + {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,2}]}}}, {?eh,tc_done, - {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,1}]},ok}}, + {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,2}]},ok}}, {?eh,tc_start,{repeat_1_SUITE,testcase_1a}}, {?eh,tc_done,{repeat_1_SUITE,testcase_1a,ok}}, {?eh,test_stats,{1,0,{0,0}}}, @@ -194,9 +194,9 @@ test_events(repeat_1) -> {?eh,tc_done,{repeat_1_SUITE,testcase_1b,ok}}, {?eh,test_stats,{2,0,{0,0}}}, {?eh,tc_start, - {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,1}]}}}, + {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,2}]}}}, {?eh,tc_done, - {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,1}]},ok}}], + {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,2}]},ok}}], [{?eh,tc_start, {repeat_1_SUITE,{init_per_group,test_group_1,[]}}}, {?eh,tc_done, @@ -212,9 +212,9 @@ test_events(repeat_1) -> {?eh,tc_done, {repeat_1_SUITE,{end_per_group,test_group_1,[]},ok}}], [{?eh,tc_start, - {repeat_1_SUITE,{init_per_group,test_group_2,[{repeat,0}]}}}, + {repeat_1_SUITE,{init_per_group,test_group_2,[]}}}, {?eh,tc_done, - {repeat_1_SUITE,{init_per_group,test_group_2,[{repeat,0}]},ok}}, + {repeat_1_SUITE,{init_per_group,test_group_2,[]},ok}}, {?eh,tc_start,{repeat_1_SUITE,testcase_2a}}, {?eh,tc_done,{repeat_1_SUITE,testcase_2a,ok}}, {?eh,test_stats,{5,0,{0,0}}}, @@ -222,25 +222,25 @@ test_events(repeat_1) -> {?eh,tc_done,{repeat_1_SUITE,testcase_2b,ok}}, {?eh,test_stats,{6,0,{0,0}}}, {?eh,tc_start, - {repeat_1_SUITE,{end_per_group,test_group_2,[{repeat,0}]}}}, + {repeat_1_SUITE,{end_per_group,test_group_2,[]}}}, {?eh,tc_done, - {repeat_1_SUITE,{end_per_group,test_group_2,[{repeat,0}]},ok}}], + {repeat_1_SUITE,{end_per_group,test_group_2,[]},ok}}], [{?eh,tc_start, {repeat_1_SUITE, - {init_per_group,test_group_3,[{repeat_until_all_fail,0}]}}}, + {init_per_group,test_group_3,[]}}}, {?eh,tc_done, {repeat_1_SUITE, - {init_per_group,test_group_3,[{repeat_until_all_fail,0}]}, + {init_per_group,test_group_3,[]}, ok}}, {?eh,tc_start,{repeat_1_SUITE,testcase_3a}}, {?eh,tc_done,{repeat_1_SUITE,testcase_3a,ok}}, {?eh,test_stats,{7,0,{0,0}}}, [{?eh,tc_start, {repeat_1_SUITE, - {init_per_group,test_group_4,[{repeat_until_any_fail,0}]}}}, + {init_per_group,test_group_4,[]}}}, {?eh,tc_done, {repeat_1_SUITE, - {init_per_group,test_group_4,[{repeat_until_any_fail,0}]}, + {init_per_group,test_group_4,[]}, ok}}, {?eh,tc_start,{repeat_1_SUITE,testcase_4a}}, {?eh,tc_done,{repeat_1_SUITE,testcase_4a,ok}}, @@ -250,20 +250,20 @@ test_events(repeat_1) -> {?eh,test_stats,{9,0,{0,0}}}, {?eh,tc_start, {repeat_1_SUITE, - {end_per_group,test_group_4,[{repeat_until_any_fail,0}]}}}, + {end_per_group,test_group_4,[]}}}, {?eh,tc_done, {repeat_1_SUITE, - {end_per_group,test_group_4,[{repeat_until_any_fail,0}]}, + {end_per_group,test_group_4,[]}, ok}}], {?eh,tc_start,{repeat_1_SUITE,testcase_3b}}, {?eh,tc_done,{repeat_1_SUITE,testcase_3b,ok}}, {?eh,test_stats,{10,0,{0,0}}}, {?eh,tc_start, {repeat_1_SUITE, - {end_per_group,test_group_3,[{repeat_until_all_fail,0}]}}}, + {end_per_group,test_group_3,[]}}}, {?eh,tc_done, {repeat_1_SUITE, - {end_per_group,test_group_3,[{repeat_until_all_fail,0}]}, + {end_per_group,test_group_3,[]}, ok}}], {?eh,tc_start,{repeat_1_SUITE,end_per_suite}}, {?eh,tc_done,{repeat_1_SUITE,end_per_suite,ok}}, diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl index 4edbc3e384..91a0a2e882 100644 --- a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl +++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl @@ -31,12 +31,12 @@ suite() -> groups() -> [ - {test_group_1, [{repeat,1}], [testcase_1a,testcase_1b]}, - {test_group_2, [{repeat,0}], [testcase_2a,testcase_2b]}, + {test_group_1, [{repeat,2}], [testcase_1a,testcase_1b]}, + {test_group_2, [{repeat,1}], [testcase_2a,testcase_2b]}, - {test_group_3, [{repeat_until_all_fail,0}], + {test_group_3, [{repeat_until_all_fail,1}], [testcase_3a, - {test_group_4, [{repeat_until_any_fail,0}], + {test_group_4, [{repeat_until_any_fail,1}], [testcase_4a, testcase_4b]}, testcase_3b]} ]. diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 9e6f1cedbb..1245c10a01 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -1572,7 +1572,7 @@ remove_conf([{conf, _Ref, Props, _MF}|Cases], NoConf, Repeats) -> case get_repeat(Props) of undefined -> remove_conf(Cases, NoConf, Repeats); - {_RepType,0} -> + {_RepType,1} -> remove_conf(Cases, NoConf, Repeats); _ -> remove_conf(Cases, NoConf, true) @@ -2248,7 +2248,7 @@ maybe_get_privdir() -> run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases], Config, TimetrapData, Mode, Status) when Type==conf; - Type==make -> + Type==make -> file:set_cwd(filename:dirname(get(test_server_dir))), CurrIOHandler = get(test_server_common_io_handler), @@ -2490,7 +2490,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, %% will continously update status with test case results %% without knowing the Ref (but update hd(Status)) {false,new_status(Ref, Status1),Cases1,?void_fun}; - {_RepType,0} -> + {_RepType,N} when N =< 1 -> {false,new_status(Ref, Status1),Cases1,?void_fun}; _ -> {Copied,_} = copy_cases(Ref, make_ref(), Cs1), @@ -2509,7 +2509,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0, case RepVal of undefined -> {false,EndStatus,Cases1,?void_fun}; - {_RepType,0} -> + {_RepType,N} when N =< 1 -> {false,EndStatus,Cases1,?void_fun}; {repeat,_} -> {true,EndStatus,CopiedCases++Cases1,?void_fun}; @@ -2903,9 +2903,9 @@ update_repeat(Props) -> Props1 = if N == forever -> [{RepType,N}|lists:keydelete(RepType, 1, Props)]; - N < 2 -> + N < 3 -> lists:keydelete(RepType, 1, Props); - N >= 2 -> + N >= 3 -> [{RepType,N-1}|lists:keydelete(RepType, 1, Props)] end, %% if shuffle is used in combination with repeat, a new @@ -4454,9 +4454,19 @@ collect_cases({conf,InitMF,CaseList,FinF}, St) when is_atom(FinF) -> collect_cases({conf,InitMF,CaseList,FinMF}, St0) -> collect_cases({conf,[],InitMF,CaseList,FinMF}, St0); collect_cases({conf,Props,InitF,CaseList,FinMF}, St) when is_atom(InitF) -> - collect_cases({conf,Props,{St#cc.mod,InitF},CaseList,FinMF}, St); + case init_props(Props) of + {error,_} -> + {ok,[],St}; + Props1 -> + collect_cases({conf,Props1,{St#cc.mod,InitF},CaseList,FinMF}, St) + end; collect_cases({conf,Props,InitMF,CaseList,FinF}, St) when is_atom(FinF) -> - collect_cases({conf,Props,InitMF,CaseList,{St#cc.mod,FinF}}, St); + case init_props(Props) of + {error,_} -> + {ok,[],St}; + Props1 -> + collect_cases({conf,Props1,InitMF,CaseList,{St#cc.mod,FinF}}, St) + end; collect_cases({conf,Props,InitMF,CaseList,FinMF}, St0) -> case collect_cases(CaseList, St0) of {ok,[],_St}=Empty -> @@ -4468,8 +4478,15 @@ collect_cases({conf,Props,InitMF,CaseList,FinMF}, St0) -> {ok,[{skip_case,{conf,Ref,InitMF,Comment}} | FlatCases ++ [{conf,Ref,[],FinMF}]],St}; false -> - {ok,[{conf,Ref,Props,InitMF} | - FlatCases ++ [{conf,Ref,keep_name(Props),FinMF}]],St} + case init_props(Props) of + {error,_} -> + {ok,[],St}; + Props1 -> + {ok,[{conf,Ref,Props1,InitMF} | + FlatCases ++ [{conf,Ref, + keep_name(Props1), + FinMF}]],St} + end end; {error,_Reason}=Error -> Error @@ -4627,6 +4644,19 @@ in_skip_list({Mod,Func}, [_|SkipList]) -> in_skip_list(_, []) -> false. +%% remove unnecessary properties +init_props(Props) -> + case get_repeat(Props) of + Repeat = {_RepType,N} when N < 2 -> + if N == 0 -> + {error,{invalid_property,Repeat}}; + true -> + lists:delete(Repeat, Props) + end; + _ -> + Props + end. + keep_name(Props) -> lists:filter(fun({name,_}) -> true; (_) -> false end, Props). -- cgit v1.2.3 From aa70d238156df715f7177ec716cd463c1dd35098 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Mon, 7 Jun 2010 19:26:34 +0200 Subject: Step vsn for test_server to 3.4 --- lib/test_server/vsn.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test_server/vsn.mk b/lib/test_server/vsn.mk index 3c6efeffde..ee3c957d59 100644 --- a/lib/test_server/vsn.mk +++ b/lib/test_server/vsn.mk @@ -1,2 +1,2 @@ -TEST_SERVER_VSN = 3.3.7 +TEST_SERVER_VSN = 3.4 -- cgit v1.2.3 From aef5c74eba885b7c28d310b4a2522de854012e33 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Tue, 8 Jun 2010 12:41:57 +0200 Subject: Add documentation for run_test program --- lib/common_test/doc/src/run_test.xml | 33 ++++++++++++++++++++++++++------- lib/common_test/src/ct_run.erl | 10 ++++++---- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/lib/common_test/doc/src/run_test.xml b/lib/common_test/doc/src/run_test.xml index f98cefd0ee..feef18fe33 100644 --- a/lib/common_test/doc/src/run_test.xml +++ b/lib/common_test/doc/src/run_test.xml @@ -50,8 +50,26 @@ (making it possible to e.g. add directories to the code server path, change the cookie on the node, start additional applications, etc).

-

If run_test is called without parameters, it prints all valid - start flags to stdout.

+

With the optional flag:

+
-erl_args
+

it's possible to divide the options on the run_test command line into + two groups, one that Common Test should process (those preceding -erl_args), + and one it should completely ignore and pass on directly to the emulator + (those following -erl_args). Options preceding -erl_args that Common Test + doesn't recognize, also get passed on to the emulator untouched. + By means of -erl_args the user may specify flags with the same name, but + with different destinations, on the run_test command line.

+

If -pa or -pz flags are specified in the Common Test group of options + (preceding -erl_args), relative directories will be converted to + absolute and re-inserted into the code path by Common Test (to avoid + problems loading user modules when Common Test changes working directory + during test runs). Common Test will however ignore -pa and -pz flags + following -erl_args on the command line. These directories are added + to the code path normally (i.e. on specified form)

+ +

If run_test is called with option:

+
-help
+

it prints all valid start flags to stdout.

@@ -74,6 +92,8 @@ EvHandler2 InitArg2 and .. EvHandlerN InitArgN] [-include InclDir1 InclDir2 .. InclDirN] [-no_auto_compile] + [-muliply_timetraps Multiplier] + [-scale_timetraps] [-repeat N [-force_stop]] | [-duration HHMMSS [-force_stop]] | [-until [YYMoMoDD]HHMMSS [-force_stop]] @@ -97,6 +117,8 @@ EvHandler2 InitArg2 and .. EvHandlerN InitArgN] [-include InclDir1 InclDir2 .. InclDirN] [-no_auto_compile] + [-muliply_timetraps Multiplier] + [-scale_timetraps] [-repeat N [-force_stop]] | [-duration HHMMSS [-force_stop]] | [-until [YYMoMoDD]HHMMSS [-force_stop]] @@ -114,6 +136,8 @@ [-suite Suite [[-group Group] [-case Case]]] [-include InclDir1 InclDir2 .. InclDirN] [-no_auto_compile] + [-muliply_timetraps Multiplier] + [-scale_timetraps] [-basic_html]
@@ -130,11 +154,6 @@ ConfigString2 and .. and CallbackModuleN ConfigStringN] [-decrypt_key Key] | [-decrypt_file KeyFile]
-
- Start an Erlang node with a given name -
-	run_test -ctname NodeName
-
Start a Common Test Master node
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 9dc0bff26a..6a9c42d1b9 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -164,7 +164,9 @@ script_start1(Parent, Args) ->
     MultTT = get_start_opt(multiply_timetraps,
 			   fun([MT]) -> list_to_integer(MT) end, 1, Args),
     ScaleTT = get_start_opt(scale_timetraps,
-			    fun([CT]) -> list_to_atom(CT) end, false, Args),
+			    fun([CT]) -> list_to_atom(CT);
+			       ([]) -> true
+			    end, false, Args),
     EvHandlers = event_handler_args2opts(Args),
 
     %% check flags and set corresponding application env variables
@@ -478,7 +480,7 @@ script_usage() ->
 	      "\n\t[-suite Suite [-case Case]]"
 	      "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
 	      "\n\t[-no_auto_compile]"
-	      "\n\t[-multiply_timetraps]"
+	      "\n\t[-multiply_timetraps N]"
 	      "\n\t[-scale_timetraps]"
 	      "\n\t[-basic_html]\n\n"),
     io:format("Run tests from command line:\n\n"
@@ -495,7 +497,7 @@ script_usage() ->
 	      "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
 	      "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
 	      "\n\t[-no_auto_compile]"
-	      "\n\t[-multiply_timetraps]"
+	      "\n\t[-multiply_timetraps N]"
 	      "\n\t[-scale_timetraps]"
 	      "\n\t[-basic_html]"
 	      "\n\t[-repeat N [-force_stop]] |"
@@ -513,7 +515,7 @@ script_usage() ->
 	      "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
 	      "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
 	      "\n\t[-no_auto_compile]"
-	      "\n\t[-multiply_timetraps]"
+	      "\n\t[-multiply_timetraps N]"
 	      "\n\t[-scale_timetraps]"
 	      "\n\t[-basic_html]"
 	      "\n\t[-repeat N [-force_stop]] |"
-- 
cgit v1.2.3


From 2d7ba88ebbb59a473dbcefd0f9dee1f1b816935e Mon Sep 17 00:00:00 2001
From: Peter Andersson 
Date: Tue, 8 Jun 2010 14:44:05 +0200
Subject: Misc documentation updates

---
 lib/common_test/doc/src/common_test_app.xml       |  4 +--
 lib/common_test/doc/src/event_handler_chapter.xml | 16 +++++-----
 lib/common_test/doc/src/run_test_chapter.xml      | 31 +++++++++++++-----
 lib/common_test/doc/src/write_test_chapter.xml    | 38 ++++++++++++++++++++---
 4 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 7b52883f8a..70efe5a14b 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -337,7 +337,7 @@
       
       
     
-      Module:testcase() -> [Info] 
+      Module:Testcase() -> [Info] 
       Test case info function. 
       
 	 Info = {timetrap,Time} | {require,Required} | 
@@ -396,7 +396,7 @@
       
     
     
-	Module:testcase(Config) ->  void() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() 
+	Module:Testcase(Config) ->  void() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() 
       A test case
       
 	 Config = SaveConfig = [{Key,Value}]
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index 35b684730e..1508b7ce33 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -68,29 +68,27 @@
     Example:

$ run_test -suite test/my_SUITE -event_handler handlers/my_evh1 handlers/my_evh2 -pa $PWD/handlers

+

Use the option instead of + to pass start arguments to the event handler + init function.

All event handler modules must have gen_event behaviour. Note also that these modules must be precompiled, and that their locations must be added explicitly to the Erlang code server search path (like in the example).

-

It is not possible to specify start arguments to the event handlers when - using the run_test program. You may however pass along start arguments - if you use the ct:run_test/1 function. An event_handler tuple in the argument - Opts has the following definition (see also ct:run_test/1 in the - reference manual):

+

An event_handler tuple in the argument Opts has the following + definition (see also ct:run_test/1 in the reference manual):

     {event_handler,EventHandlers}
 
     EventHandlers = EH | [EH]
     EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
-    InitArgs = [term()] 
-    
+ InitArgs = [term()]

Example:

-    1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).
-    
+ 1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).

This will install two event handlers for the my_SUITE test. Event handler my_evh1 is started with [] as argument to the init function. Event handler my_evh2 is started with the name of the current node in the init argument list.

diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 7fd3174e71..54e9a3174c 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -97,8 +97,12 @@ the option with . With automatic compilation disabled, the user is responsible for compiling the test suite modules - (and any help modules) before the test run. Common Test will only verify - that the specified test suites exist before starting the tests.

+ (and any help modules) before the test run. If the modules can not be loaded + from the local file system during startup of Common Test, the user needs to + pre-load the modules before starting the test. Common Test will only verify + that the specified test suites exist (i.e. that they are, or can be, loaded). + This is useful e.g. if the test suites are transferred and loaded as binaries via + RPC from a remote node.

@@ -139,8 +143,14 @@ Code Coverage Analysis). ]]>, to install event handlers. + ]]>, to install + event handlers including start arguments. , specifies include directories (see above). , disables the automatic test suite compilation feature (see above). + ]]>, extends timetrap + timeout values. + ]]>, enables automatic timetrap + timeout scaling. ]]>, tells Common Test to repeat the tests n times (see below). ]]>, tells Common Test to repeat the tests for duration of time (see below). ]]>, tells Common Test to repeat the tests until stop_time (see below). @@ -243,11 +253,12 @@ call ct:start_interactive/0 to start Common Test. If you use the run_test program, you may start the Erlang shell and Common Test in the same go by using the -shell and, optionally, the -config - flag: + and/or -userconfig flag. Examples:

run_test -shell - ]]> + +

If no config file is given with the run_test command, @@ -272,7 +283,8 @@ 2> ct_telnet:open(unix_telnet). {ok,<0.105.0>} 4> ct_telnet:cmd(unix_telnet, "ls ."). - {ok,["ls .","file1 ...",...]} + {ok,["ls .","file1 ...",...]} +

Everything that Common Test normally prints in the test case logs, will in the interactive mode be written to a log named @@ -362,10 +374,13 @@ chapter.

Config terms:

+      {node, NodeAlias, Node}.
+
       {init, InitOptions}.
       {init, [NodeAlias], InitOptions}.
 
-      {node, NodeAlias, Node}.
+      {multiply_timetraps, N}.
+      {scale_timetraps, Bool}.
  
       {cover, CoverSpecFile}.
       {cover, NodeRef, CoverSpecFile}.
@@ -405,11 +420,13 @@
     

Types:

-      InitOptions   = term()
       NodeAlias     = atom()
+      InitOptions   = term()
       Node          = node()
       NodeRef       = NodeAlias | Node | master
       NodeRefs      = all_nodes | [NodeRef] | NodeRef
+      N             = integer()
+      Bool          = true | false
       CoverSpecFile = string()
       IncludeDirs   = string() | [string()]
       ConfigFiles   = string() | [string()]
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 212e3d85be..e2106469b7 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -157,6 +157,15 @@
       {skipped,Reason} (where Reason is a user specific term).
     

+

The end_per_testcase/2 function is called even after a + test case terminates due to a call to ct:abort_current_testcase/1, + or after a timetrap timeout. However, end_per_testcase + will then execute on a different process than the test case + function, and in this situation, end_per_testcase will + not be able to change the reason for test case termination by + returning {fail,Reason}, nor will it be able to save data with + {save_config,Data}.

+

If init_per_testcase crashes, the test case itself is skipped automatically (so called auto skipped). If init_per_testcase returns a skip tuple, also then will the test case be skipped (so @@ -682,12 +691,33 @@ end_per_suite execute, like test cases, on dedicated Erlang processes.

+
+
+ Timetrap timeouts +

The default time limit for a test case is 30 minutes, unless a - timetrap is specified either by the test case info function - or the suite/0 function. -

- + timetrap is specified either by the suite info function + or a test case info function. The timetrap timeout value defined + in suite/0 is the value that will be used for each test case + in the suite (as well as for the configuration functions + init_per_suite/1 and end_per_suite). A timetrap timeout + value set with the test case info function will override the value set + by suite/0, but only for that particular test case.

+

It is also possible to set/reset a timetrap during test case (or + configuration function) execution. This is done by calling + ct:timetrap/1. This function will cancel the current timetrap + and start a new one.

+

Timetrap values can be extended with a multiplier value specified at + startup with the multiply_timetraps option. It is also possible + to let Test Server decide to scale up timetrap timeout values + automatically, e.g. if tools such as cover or trace are running during + the test. This feature is disabled by default and can be enabled with + the scale_timetraps start option.

+

If a test case needs to suspend itself for a time that also gets + multipled by multiply_timetraps, and possibly scaled up if + scale_timetraps is enabled, the function ct:sleep/1 + may be called.

-- cgit v1.2.3
Line Number
~w
Not covered (Lines)