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 (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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 +++++--- 5 files changed, 22 insertions(+), 16 deletions(-) (limited to 'lib/common_test/src') 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); -- 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 +++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'lib/common_test/src') 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); -- 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/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 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'lib/common_test/src') 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. -- 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(-) (limited to 'lib/common_test/src') 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 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 ++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 19 deletions(-) (limited to 'lib/common_test/src') 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. -- 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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 +++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 34 deletions(-) (limited to 'lib/common_test/src') 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. -- 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(-) (limited to 'lib/common_test/src') 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(-) (limited to 'lib/common_test/src') 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 ++- 5 files changed, 51 insertions(+), 13 deletions(-) (limited to 'lib/common_test/src') 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). -- 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(-) (limited to 'lib/common_test/src') 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/src/ct.erl | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/common_test/src') 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). -- 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/src/Makefile | 3 +- lib/common_test/src/common_test.app.src | 4 +- lib/common_test/src/ct_slave.erl | 416 ++++++++++++++++++++++++++++++++ 3 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 lib/common_test/src/ct_slave.erl (limited to 'lib/common_test/src') 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. -- 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(-) (limited to 'lib/common_test/src') 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 + 4 files changed, 162 insertions(+), 47 deletions(-) (limited to 'lib/common_test/src') 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=[], -- 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 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/common_test/src') 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, -- 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/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 +- 5 files changed, 141 insertions(+), 133 deletions(-) (limited to 'lib/common_test/src') 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=[], -- 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/src/ct_slave.erl | 8 ++++++++ lib/common_test/src/ct_testspec.erl | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'lib/common_test/src') 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. --- 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 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) 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 (limited to 'lib/common_test/src') 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" -- 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/src/ct_config_plain.erl | 2 +- lib/common_test/src/ct_config_xml.erl | 5 ++-- lib/common_test/src/ct_run.erl | 46 +-------------------------------- 3 files changed, 4 insertions(+), 49 deletions(-) (limited to 'lib/common_test/src') 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 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 +++++++++++++++++++++------------------- 2 files changed, 691 insertions(+), 574 deletions(-) (limited to 'lib/common_test/src') 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. - -- 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 + 3 files changed, 244 insertions(+), 150 deletions(-) (limited to 'lib/common_test/src') 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=[]}). -- 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/src/ct.erl | 5 +- lib/common_test/src/ct_config.erl | 21 ++++-- lib/common_test/src/ct_run.erl | 154 +++++++++++++++++++++++++------------- 3 files changed, 120 insertions(+), 60 deletions(-) (limited to 'lib/common_test/src') 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). -- 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 +-- 3 files changed, 64 insertions(+), 36 deletions(-) (limited to 'lib/common_test/src') 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=[]}). -- 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 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'lib/common_test/src') 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); -- 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 +++++++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 15 deletions(-) (limited to 'lib/common_test/src') 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 -- 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(-) (limited to 'lib/common_test/src') 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 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/common_test/src') 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) -- 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 ++++++++++++++++++------- 3 files changed, 53 insertions(+), 21 deletions(-) (limited to 'lib/common_test/src') 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. -- 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 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'lib/common_test/src') 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) -- 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 ++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 16 deletions(-) (limited to 'lib/common_test/src') 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([]) -> []; -- 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 +-- 5 files changed, 67 insertions(+), 33 deletions(-) (limited to 'lib/common_test/src') 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), -- 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(-) (limited to 'lib/common_test/src') 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 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 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) (limited to 'lib/common_test/src') 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]}]; -- 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/src/ct_run.erl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/common_test/src') 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