From 71a49b5c88e2149e288168beb8cb6ff0ed39c671 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Thu, 28 Jul 2011 15:35:19 +0200 Subject: Update internal hooks state to use a record instead of tuples --- lib/common_test/src/ct_hooks.erl | 67 +++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 28 deletions(-) (limited to 'lib/common_test/src') diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index ece592e320..d298873d99 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -34,6 +34,8 @@ %% If you change this, remember to update ct_util:look -> stop clause as well. -define(config_name, ct_hooks). +-record(ct_hook_config, {id, module, prio, scope, opts = [], state = []}). + %% ------------------------------------------------------------------------- %% API Functions %% ------------------------------------------------------------------------- @@ -42,15 +44,15 @@ -spec init(State :: term()) -> ok | {error, Reason :: term()}. init(Opts) -> - call([{Hook, call_id, undefined} || Hook <- get_new_hooks(Opts)], - ok, init, []). + call(get_new_hooks(Opts, undefined), ok, init, []). %% @doc Called after all suites are done. -spec terminate(Hooks :: term()) -> ok. terminate(Hooks) -> - call([{HookId, fun call_terminate/3} || {HookId,_,_} <- Hooks], + call([{HookId, fun call_terminate/3} + || #ct_hook_config{id = HookId} <- Hooks], ct_hooks_terminate_dummy, terminate, Hooks), ok. @@ -129,35 +131,36 @@ on_tc_fail(_How, {Suite, Case, Reason}) -> %% ------------------------------------------------------------------------- %% Internal Functions %% ------------------------------------------------------------------------- -call_id(Mod, Config, Meta) when is_atom(Mod) -> - call_id({Mod, []}, Config, Meta); -call_id({Mod, Opts}, Config, Scope) -> +call_id(#ct_hook_config{ module = Mod, opts = Opts} = Hook, Config, Scope) -> Id = catch_apply(Mod,id,[Opts], make_ref()), - {Config, {Id, scope(Scope), {Mod, {Id,Opts}}}}. + {Config, Hook#ct_hook_config{ id = Id, scope = scope(Scope)}}. -call_init({Mod,{Id,Opts}},Config,_Meta) -> +call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id} = Hook, + Config,_Meta) -> NewState = Mod:init(Id, Opts), - {Config, {Mod, NewState}}. + {Config, Hook#ct_hook_config{ state = NewState } }. -call_terminate({Mod, State}, _, _) -> +call_terminate(#ct_hook_config{ module = Mod, state = State} = Hook, _, _) -> catch_apply(Mod,terminate,[State], ok), - {[],{Mod,State}}. + {[],Hook}. -call_cleanup({Mod, State}, Reason, [Function, _Suite | Args]) -> +call_cleanup(#ct_hook_config{ module = Mod, state = State} = Hook, + Reason, [Function, _Suite | Args]) -> NewState = catch_apply(Mod,Function, Args ++ [Reason, State], State), - {Reason, {Mod, NewState}}. + {Reason, Hook#ct_hook_config{ state = NewState } }. -call_generic({Mod, State}, Value, [Function | Args]) -> +call_generic(#ct_hook_config{ module = Mod, state = State} = Hook, + Value, [Function | Args]) -> {NewValue, NewState} = catch_apply(Mod, Function, Args ++ [Value, State], {Value,State}), - {NewValue, {Mod, NewState}}. + {NewValue, Hook#ct_hook_config{ state = NewState } }. %% Generic call function call(Fun, Config, Meta) -> maybe_lock(), Hooks = get_hooks(), - Res = call([{HookId,Fun} || {HookId,_, _} <- Hooks] ++ + Res = call([{HookId,Fun} || #ct_hook_config{id = HookId} <- Hooks] ++ get_new_hooks(Config, Fun), remove(?config_name,Config), Meta, Hooks), maybe_unlock(), @@ -171,9 +174,10 @@ call(Fun, Config, Meta, NoChangeRet) when is_function(Fun) -> call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> try - {Config, {NewId, _, _} = NewHook} = call_id(Hook, Config, Meta), + {Config, #ct_hook_config{ id = NewId } = NewHook} = + call_id(Hook, Config, Meta), {NewHooks, NewRest} = - case lists:keyfind(NewId, 1, Hooks) of + case lists:keyfind(NewId, #ct_hook_config.id, Hooks) of false when NextFun =:= undefined -> {Hooks ++ [NewHook], [{NewId, fun call_init/3} | Rest]}; @@ -193,10 +197,10 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> end; call([{HookId, Fun} | Rest], Config, Meta, Hooks) -> try - {_,Scope,ModState} = lists:keyfind(HookId, 1, Hooks), - {NewConf, NewHookInfo} = Fun(ModState, Config, Meta), + Hook = lists:keyfind(HookId, #ct_hook_config.id, Hooks), + {NewConf, NewHook} = Fun(Hook, Config, Meta), NewCalls = get_new_hooks(NewConf, Fun), - NewHooks = lists:keyreplace(HookId, 1, Hooks, {HookId, Scope, NewHookInfo}), + NewHooks = lists:keyreplace(HookId, #ct_hook_config.id, Hooks, NewHook), call(NewCalls ++ Rest, remove(?config_name, NewConf), Meta, terminate_if_scope_ends(HookId, Meta, NewHooks)) catch throw:{error_in_cth_call,Reason} -> @@ -235,19 +239,26 @@ terminate_if_scope_ends(HookId, [on_tc_skip,Suite,end_per_suite], Hooks) -> terminate_if_scope_ends(HookId, [Function,Tag|T], Hooks) when T =/= [] -> terminate_if_scope_ends(HookId,[Function,Tag],Hooks); terminate_if_scope_ends(HookId, Function, Hooks) -> - case lists:keyfind(HookId, 1, Hooks) of - {HookId, Function, _ModState} = Hook -> + case lists:keyfind(HookId, #ct_hook_config.id, Hooks) of + #ct_hook_config{ id = HookId, scope = Function} = Hook -> terminate([Hook]), - lists:keydelete(HookId, 1, Hooks); + lists:keydelete(HookId, #ct_hook_config.id, Hooks); _ -> Hooks end. %% Fetch hook functions get_new_hooks(Config, Fun) -> - lists:foldl(fun(NewHook, Acc) -> - [{NewHook, call_id, Fun} | Acc] - end, [], get_new_hooks(Config)). + lists:map(fun(NewHook) when is_atom(NewHook) -> + {#ct_hook_config{ module = NewHook }, call_id, Fun}; + ({NewHook,Opts}) -> + {#ct_hook_config{ module = NewHook, + opts = Opts}, call_id, Fun}; + ({NewHook,Opts,Prio}) -> + {#ct_hook_config{ module = NewHook, + opts = Opts, + prio = Prio }, call_id, Fun} + end, get_new_hooks(Config)). get_new_hooks(Config) when is_list(Config) -> lists:flatmap(fun({?config_name, HookConfigs}) -> @@ -262,7 +273,7 @@ save_suite_data_async(Hooks) -> ct_util:save_suite_data_async(?config_name, Hooks). get_hooks() -> - ct_util:read_suite_data(?config_name). + lists:keysort(#ct_hook_config.prio,ct_util:read_suite_data(?config_name)). catch_apply(M,F,A, Default) -> try -- cgit v1.2.3 From 6b9f8b54324891f2d7fed236cb2860896215d755 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Thu, 28 Jul 2011 19:12:37 +0200 Subject: Add priority functionality and tests for ct hooks Priority allows the user of ct hooks to specify which order the hooks should execute in. The priority of a hook is specified when installing the hook, and stays the same for both pre and post hooks --- lib/common_test/src/ct_framework.erl | 12 +++++++----- lib/common_test/src/ct_hooks.erl | 21 +++++++++++++++------ lib/common_test/src/ct_run.erl | 25 +++++++++++++++++++------ 3 files changed, 41 insertions(+), 17 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 809616d8e3..9e597edf38 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -240,7 +240,8 @@ add_defaults(Mod,Func,FuncInfo,DoInit) -> case (catch Mod:suite()) of {'EXIT',{undef,_}} -> SuiteInfo = merge_with_suite_defaults(Mod,[]), - case add_defaults1(Mod,Func,FuncInfo,SuiteInfo,DoInit) of + SuiteInfoNoCTH = [I || I <- SuiteInfo, element(1,I) =/= ct_hooks], + case add_defaults1(Mod,Func,FuncInfo,SuiteInfoNoCTH,DoInit) of Error = {error,_} -> {SuiteInfo,Error}; MergedInfo -> {SuiteInfo,MergedInfo} end; @@ -251,10 +252,11 @@ add_defaults(Mod,Func,FuncInfo,DoInit) -> (_) -> false end, SuiteInfo) of true -> - SuiteInfoNoCTH = - lists:keydelete(ct_hooks,1,SuiteInfo), - SuiteInfo1 = merge_with_suite_defaults(Mod,SuiteInfoNoCTH), - case add_defaults1(Mod,Func,FuncInfo,SuiteInfo1,DoInit) of + SuiteInfo1 = merge_with_suite_defaults(Mod,SuiteInfo), + SuiteInfoNoCTH = [I || I <- SuiteInfo1, + element(1,I) =/= ct_hooks], + case add_defaults1(Mod,Func,FuncInfo, + SuiteInfoNoCTH,DoInit) of Error = {error,_} -> {SuiteInfo1,Error}; MergedInfo -> {SuiteInfo1,MergedInfo} end; diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index d298873d99..bbadb657e1 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -68,11 +68,11 @@ init_tc(ct_framework, _Func, Args) -> init_tc(Mod, init_per_suite, Config) -> Info = try proplists:get_value(ct_hooks, Mod:suite(),[]) of List when is_list(List) -> - [{ct_hooks,List}]; + [{?config_name,List}]; CTHook when is_atom(CTHook) -> - [{ct_hooks,[CTHook]}] + [{?config_name,[CTHook]}] catch error:undef -> - [{ct_hooks,[]}] + [{?config_name,[]}] end, call(fun call_generic/3, Config ++ Info, [pre_init_per_suite, Mod]); init_tc(Mod, end_per_suite, Config) -> @@ -160,8 +160,8 @@ call_generic(#ct_hook_config{ module = Mod, state = State} = Hook, call(Fun, Config, Meta) -> maybe_lock(), Hooks = get_hooks(), - Res = call([{HookId,Fun} || #ct_hook_config{id = HookId} <- Hooks] ++ - get_new_hooks(Config, Fun), + Res = call(get_new_hooks(Config, Fun) ++ + [{HookId,Fun} || #ct_hook_config{id = HookId} <- Hooks], remove(?config_name,Config), Meta, Hooks), maybe_unlock(), Res. @@ -187,7 +187,7 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> {Hooks ++ [NewHook], [{NewId, fun call_init/3},{NewId,NextFun} | Rest]} end, - call(NewRest, Config, Meta, NewHooks) + call(resort(NewRest,NewHooks), Config, Meta, NewHooks) catch Error:Reason -> Trace = erlang:get_stacktrace(), ct_logs:log("Suite Hook","Failed to start a CTH: ~p:~p", @@ -275,6 +275,15 @@ save_suite_data_async(Hooks) -> get_hooks() -> lists:keysort(#ct_hook_config.prio,ct_util:read_suite_data(?config_name)). +%% Call with three element tuples are call_id so always do them first +resort(Calls, Hooks) -> + [Call || {_,_,_} = Call <- Calls] ++ + resort1(Calls, lists:keysort(#ct_hook_config.prio, Hooks)). +resort1(Calls, [#ct_hook_config{ id = Id }|Rest]) -> + [Call || {CId,_} = Call <- Calls, CId =:= Id] ++ resort1(Calls,Rest); +resort1(_,[]) -> + []. + catch_apply(M,F,A, Default) -> try apply(M,F,A) diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index c01e97b358..877ec9c7dd 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -521,8 +521,8 @@ script_usage() -> "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" - "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" + "\n\t[-event_handler EvHandler1 and EvHandler2 .. EvHandlerN]" + "\n\t[-ct_hooks CTHook1 and CTHook2 .. CTHookN]" "\n\t[-include InclDir1 InclDir2 .. InclDirN]" "\n\t[-no_auto_compile]" "\n\t[-multiply_timetraps N]" @@ -540,8 +540,8 @@ script_usage() -> "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" - "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" - "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" + "\n\t[-event_handler EvHandler1 and EvHandler2 .. EvHandlerN]" + "\n\t[-ct_hooks CTHook1 and CTHook2 .. CTHookN]" "\n\t[-include InclDir1 InclDir2 .. InclDirN]" "\n\t[-no_auto_compile]" "\n\t[-multiply_timetraps N]" @@ -2070,15 +2070,21 @@ ct_hooks_args2opts(Args) -> ct_hooks_args2opts( proplists:get_value(ct_hooks, Args, []),[]). +ct_hooks_args2opts([CTH,Arg,Prio,"and"| Rest],Acc) -> + ct_hooks_args2opts(Rest,[{list_to_atom(CTH), + parse_cth_args(Arg), + parse_cth_args(Prio)}|Acc]); ct_hooks_args2opts([CTH,Arg,"and"| Rest],Acc) -> ct_hooks_args2opts(Rest,[{list_to_atom(CTH), - parse_cth_args(Arg)}|Acc]); + parse_cth_args(Arg)}|Acc]); ct_hooks_args2opts([CTH], Acc) -> ct_hooks_args2opts([CTH,"and"],Acc); ct_hooks_args2opts([CTH, "and" | Rest], Acc) -> ct_hooks_args2opts(Rest,[list_to_atom(CTH)|Acc]); ct_hooks_args2opts([CTH, Args], Acc) -> ct_hooks_args2opts([CTH, Args, "and"],Acc); +ct_hooks_args2opts([CTH, Args, Prio], Acc) -> + ct_hooks_args2opts([CTH, Args, Prio, "and"],Acc); ct_hooks_args2opts([],Acc) -> lists:reverse(Acc). @@ -2225,7 +2231,14 @@ opts2args(EnvStartOpts) -> ({ct_hooks,CTHs}) when is_list(CTHs) -> io:format(user,"ct_hooks: ~p",[CTHs]), Strs = lists:flatmap( - fun({CTH,Arg}) -> + fun({CTH,Arg,Prio}) -> + [atom_to_list(CTH), + lists:flatten( + io_lib:format("~p",[Arg])), + lists:flatten( + io_lib:format("~p",[Prio])), + "and"]; + ({CTH,Arg}) -> [atom_to_list(CTH), lists:flatten( io_lib:format("~p",[Arg])), -- cgit v1.2.3 From 187cf0a2a5cb5cc5f94e2b9bb07b03955ad6eb4d Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Fri, 29 Jul 2011 11:51:15 +0200 Subject: Update the return from init/2 to be {ok, NewState} or {ok,NewState,Priority} instead of NewState. NewState can still be returned, but is only kept for backward compatability reasons. --- lib/common_test/src/ct_hooks.erl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'lib/common_test/src') diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index bbadb657e1..9b288ad168 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -135,10 +135,19 @@ call_id(#ct_hook_config{ module = Mod, opts = Opts} = Hook, Config, Scope) -> Id = catch_apply(Mod,id,[Opts], make_ref()), {Config, Hook#ct_hook_config{ id = Id, scope = scope(Scope)}}. -call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id} = Hook, +call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id, prio = P} = Hook, Config,_Meta) -> - NewState = Mod:init(Id, Opts), - {Config, Hook#ct_hook_config{ state = NewState } }. + case Mod:init(Id, Opts) of + {ok, NewState} -> + {Config, Hook#ct_hook_config{ state = NewState } }; + {ok, NewState, Prio} when P =:= undefined -> + %% Only set prio if not already set when installing hook + {Config, Hook#ct_hook_config{ state = NewState, prio = Prio } }; + {ok, NewState, _} -> + {Config, Hook#ct_hook_config{ state = NewState } }; + NewState -> %% Keep for backward compatability reasons + {Config, Hook#ct_hook_config{ state = NewState } } + end. call_terminate(#ct_hook_config{ module = Mod, state = State} = Hook, _, _) -> catch_apply(Mod,terminate,[State], ok), -- cgit v1.2.3 From 850f0268000dfb3e062689c9b5820c7d49feb2a2 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Fri, 29 Jul 2011 14:47:23 +0200 Subject: Update CTH priority default to be 0 --- lib/common_test/src/ct_hooks.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/common_test/src') diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index 9b288ad168..44aba94cd1 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -34,7 +34,7 @@ %% If you change this, remember to update ct_util:look -> stop clause as well. -define(config_name, ct_hooks). --record(ct_hook_config, {id, module, prio, scope, opts = [], state = []}). +-record(ct_hook_config, {id, module, prio = 0, scope, opts = [], state = []}). %% ------------------------------------------------------------------------- %% API Functions -- cgit v1.2.3 From 1e18245b72b1a22f90d78ee3f21fe2ea52bebdaf Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Tue, 2 Aug 2011 19:42:27 +0200 Subject: Fix a couple of minor bugs with hook priority The bugs caused the sorting priority to be wrong when using installed priority and built in priority. Tests to prove the order of hooks to be correct have also been added. --- lib/common_test/src/ct_hooks.erl | 54 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) (limited to 'lib/common_test/src') diff --git a/lib/common_test/src/ct_hooks.erl b/lib/common_test/src/ct_hooks.erl index 44aba94cd1..f243b87f54 100644 --- a/lib/common_test/src/ct_hooks.erl +++ b/lib/common_test/src/ct_hooks.erl @@ -34,7 +34,7 @@ %% If you change this, remember to update ct_util:look -> stop clause as well. -define(config_name, ct_hooks). --record(ct_hook_config, {id, module, prio = 0, scope, opts = [], state = []}). +-record(ct_hook_config, {id, module, prio, scope, opts = [], state = []}). %% ------------------------------------------------------------------------- %% API Functions @@ -138,6 +138,8 @@ call_id(#ct_hook_config{ module = Mod, opts = Opts} = Hook, Config, Scope) -> call_init(#ct_hook_config{ module = Mod, opts = Opts, id = Id, prio = P} = Hook, Config,_Meta) -> case Mod:init(Id, Opts) of + {ok, NewState} when P =:= undefined -> + {Config, Hook#ct_hook_config{ state = NewState, prio = 0 } }; {ok, NewState} -> {Config, Hook#ct_hook_config{ state = NewState } }; {ok, NewState, Prio} when P =:= undefined -> @@ -189,12 +191,12 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> case lists:keyfind(NewId, #ct_hook_config.id, Hooks) of false when NextFun =:= undefined -> {Hooks ++ [NewHook], - [{NewId, fun call_init/3} | Rest]}; + [{NewId, call_init} | Rest]}; ExistingHook when is_tuple(ExistingHook) -> {Hooks, Rest}; _ -> {Hooks ++ [NewHook], - [{NewId, fun call_init/3},{NewId,NextFun} | Rest]} + [{NewId, call_init}, {NewId,NextFun} | Rest]} end, call(resort(NewRest,NewHooks), Config, Meta, NewHooks) catch Error:Reason -> @@ -204,13 +206,16 @@ call([{Hook, call_id, NextFun} | Rest], Config, Meta, Hooks) -> call([], {fail,"Failed to start CTH" ", see the CT Log for details"}, Meta, Hooks) end; +call([{HookId, call_init} | Rest], Config, Meta, Hooks) -> + call([{HookId, fun call_init/3} | Rest], Config, Meta, Hooks); call([{HookId, Fun} | Rest], Config, Meta, Hooks) -> try Hook = lists:keyfind(HookId, #ct_hook_config.id, Hooks), {NewConf, NewHook} = Fun(Hook, Config, Meta), NewCalls = get_new_hooks(NewConf, Fun), NewHooks = lists:keyreplace(HookId, #ct_hook_config.id, Hooks, NewHook), - call(NewCalls ++ Rest, remove(?config_name, NewConf), Meta, + call(resort(NewCalls ++ Rest,NewHooks), %% Resort if call_init changed prio + remove(?config_name, NewConf), Meta, terminate_if_scope_ends(HookId, Meta, NewHooks)) catch throw:{error_in_cth_call,Reason} -> call(Rest, {fail, Reason}, Meta, @@ -284,14 +289,41 @@ save_suite_data_async(Hooks) -> get_hooks() -> lists:keysort(#ct_hook_config.prio,ct_util:read_suite_data(?config_name)). -%% Call with three element tuples are call_id so always do them first +%% Sort all calls in this order: +%% call_id < call_init < Hook Priority 1 < .. < Hook Priority N +%% If Hook Priority is equal, check when it has been installed and +%% sort on that instead. resort(Calls, Hooks) -> - [Call || {_,_,_} = Call <- Calls] ++ - resort1(Calls, lists:keysort(#ct_hook_config.prio, Hooks)). -resort1(Calls, [#ct_hook_config{ id = Id }|Rest]) -> - [Call || {CId,_} = Call <- Calls, CId =:= Id] ++ resort1(Calls,Rest); -resort1(_,[]) -> - []. + lists:sort( + fun({_,_,_},_) -> + true; + (_,{_,_,_}) -> + false; + ({_,call_init},_) -> + true; + (_,{_,call_init}) -> + false; + ({Id1,_},{Id2,_}) -> + P1 = (lists:keyfind(Id1, #ct_hook_config.id, Hooks))#ct_hook_config.prio, + P2 = (lists:keyfind(Id2, #ct_hook_config.id, Hooks))#ct_hook_config.prio, + if + P1 == P2 -> + %% If priorities are equal, we check the position in the + %% hooks list + pos(Id1,Hooks) < pos(Id2,Hooks); + true -> + P1 < P2 + end + end,Calls). + +pos(Id,Hooks) -> + pos(Id,Hooks,0). +pos(Id,[#ct_hook_config{ id = Id}|_],Num) -> + Num; +pos(Id,[_|Rest],Num) -> + pos(Id,Rest,Num+1). + + catch_apply(M,F,A, Default) -> try -- cgit v1.2.3