diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile index aad618f4211e..da1d32fe52ca 100644 --- a/deps/rabbit/Makefile +++ b/deps/rabbit/Makefile @@ -239,22 +239,16 @@ define ct_master.erl peer:call(Pid2, persistent_term, put, [rabbit_ct_tcp_port_base, 25000]), peer:call(Pid3, persistent_term, put, [rabbit_ct_tcp_port_base, 27000]), peer:call(Pid4, persistent_term, put, [rabbit_ct_tcp_port_base, 29000]), - ct_master_fork:run("$1"), - Fail1 = peer:call(Pid1, cth_parallel_ct_detect_failure, has_failures, []), - Fail2 = peer:call(Pid2, cth_parallel_ct_detect_failure, has_failures, []), - Fail3 = peer:call(Pid3, cth_parallel_ct_detect_failure, has_failures, []), - Fail4 = peer:call(Pid4, cth_parallel_ct_detect_failure, has_failures, []), + [{[_], {ok, Results}}] = ct_master_fork:run("$1"), peer:stop(Pid4), peer:stop(Pid3), peer:stop(Pid2), peer:stop(Pid1), - if - Fail1 -> halt(1); - Fail2 -> halt(2); - Fail3 -> halt(3); - Fail4 -> halt(4); - true -> halt(0) - end + lists:foldl(fun + ({_, {_, 0, {_, 0}}}, Err) -> Err + 1; + (What, Peer) -> halt(Peer) + end, 1, Results), + halt(0) endef PARALLEL_CT_SET_1_A = amqp_client unit_cluster_formation_locking_mocks unit_cluster_formation_sort_nodes unit_collections unit_config_value_encryption unit_connection_tracking @@ -293,6 +287,7 @@ define tpl_parallel_ct_test_spec {logdir, "$(CT_LOGS_DIR)"}. {logdir, master, "$(CT_LOGS_DIR)"}. {create_priv_dir, all_nodes, auto_per_run}. +{auto_compile, false}. {node, shard1, 'rabbit_shard1@localhost'}. {node, shard2, 'rabbit_shard2@localhost'}. diff --git a/deps/rabbitmq_ct_helpers/app.bzl b/deps/rabbitmq_ct_helpers/app.bzl index a2f85973d675..5754e5f4c8aa 100644 --- a/deps/rabbitmq_ct_helpers/app.bzl +++ b/deps/rabbitmq_ct_helpers/app.bzl @@ -11,9 +11,10 @@ def all_beam_files(name = "all_beam_files"): name = "other_beam", testonly = True, srcs = [ + "src/ct_master_event_fork.erl", "src/ct_master_fork.erl", + "src/ct_master_logs_fork.erl", "src/cth_log_redirect_any_domains.erl", - "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", @@ -39,9 +40,10 @@ def all_test_beam_files(name = "all_test_beam_files"): name = "test_other_beam", testonly = True, srcs = [ + "src/ct_master_event_fork.erl", "src/ct_master_fork.erl", + "src/ct_master_logs_fork.erl", "src/cth_log_redirect_any_domains.erl", - "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", @@ -103,9 +105,10 @@ def all_srcs(name = "all_srcs"): name = "srcs", testonly = True, srcs = [ + "src/ct_master_event_fork.erl", "src/ct_master_fork.erl", + "src/ct_master_logs_fork.erl", "src/cth_log_redirect_any_domains.erl", - "src/cth_parallel_ct_detect_failure.erl", "src/rabbit_control_helper.erl", "src/rabbit_ct_broker_helpers.erl", "src/rabbit_ct_config_schema.erl", diff --git a/deps/rabbitmq_ct_helpers/src/ct_master_event_fork.erl b/deps/rabbitmq_ct_helpers/src/ct_master_event_fork.erl new file mode 100644 index 000000000000..2ac634840849 --- /dev/null +++ b/deps/rabbitmq_ct_helpers/src/ct_master_event_fork.erl @@ -0,0 +1,217 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2024. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%%% Common Test Framework Event Handler +%%% +%%% This module implements an event handler that the CT Master +%%% uses to handle status and progress notifications sent to the +%%% master node during test runs. It also keeps track of the +%%% details of failures which are used by the CT Master to print +%%% a summary at the end of its run. This module may be used as a +%%% template for other event handlers that can be plugged in to +%%% handle logging and reporting on the master node. +-module(ct_master_event_fork). +-moduledoc false. + +-behaviour(gen_event). + +%% API +-export([start_link/0, add_handler/0, add_handler/1, stop/0]). +-export([notify/1, sync_notify/1, get_results/0]). + +%% gen_event callbacks +-export([init/1, handle_event/2, handle_call/2, + handle_info/2, terminate/2, code_change/3]). + +-include_lib("common_test/include/ct_event.hrl"). +-include_lib("common_test/src/ct_util.hrl"). + + +-record(state, {auto_skipped=[], failed=[]}). + +%%==================================================================== +%% gen_event callbacks +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | {error,Error} +%% Description: Creates an event manager. +%%-------------------------------------------------------------------- +start_link() -> + gen_event:start_link({local,?CT_MEVMGR}). + +%%-------------------------------------------------------------------- +%% Function: add_handler() -> ok | {'EXIT',Reason} | term() +%% Description: Adds an event handler +%%-------------------------------------------------------------------- +add_handler() -> + gen_event:add_handler(?CT_MEVMGR_REF,?MODULE,[]). +add_handler(Args) -> + gen_event:add_handler(?CT_MEVMGR_REF,?MODULE,Args). + +%%-------------------------------------------------------------------- +%% Function: stop() -> ok +%% Description: Stops the event manager +%%-------------------------------------------------------------------- +stop() -> + case flush() of + {error,Reason} -> + ct_master_logs_fork:log("Error", + "No response from CT Master Event.\n" + "Reason = ~tp\n" + "Terminating now!\n",[Reason]), + %% communication with event manager fails, kill it + catch exit(whereis(?CT_MEVMGR_REF), kill); + _ -> + gen_event:stop(?CT_MEVMGR_REF) + end. + +flush() -> + try gen_event:call(?CT_MEVMGR_REF,?MODULE,flush,1800000) of + flushing -> + timer:sleep(1), + flush(); + done -> + ok; + Error = {error,_} -> + Error + catch + _:Reason -> + {error,Reason} + end. + +%%-------------------------------------------------------------------- +%% Function: notify(Event) -> ok +%% Description: Asynchronous notification to event manager. +%%-------------------------------------------------------------------- +notify(Event) -> + gen_event:notify(?CT_MEVMGR_REF,Event). + +%%-------------------------------------------------------------------- +%% Function: sync_notify(Event) -> ok +%% Description: Synchronous notification to event manager. +%%-------------------------------------------------------------------- +sync_notify(Event) -> + gen_event:sync_notify(?CT_MEVMGR_REF,Event). + +%%-------------------------------------------------------------------- +%% Function: sync_notify(Event) -> Results +%% Description: Get the results for auto-skipped and failed test cases. +%%-------------------------------------------------------------------- +get_results() -> + gen_event:call(?CT_MEVMGR_REF,?MODULE,get_results). + +%%==================================================================== +%% gen_event callbacks +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} +%% Description: Whenever a new event handler is added to an event manager, +%% this function is called to initialize the event handler. +%%-------------------------------------------------------------------- +init(_) -> + ct_util:mark_process(), + ct_master_logs_fork:log("CT Master Event Handler started","",[]), + {ok,#state{}}. + +%%-------------------------------------------------------------------- +%% Function: +%% handle_event(Event, State) -> {ok, State} | +%% {swap_handler, Args1, State1, Mod2, Args2} | +%% remove_handler +%% Description:Whenever an event manager receives an event sent using +%% gen_event:notify/2 or gen_event:sync_notify/2, this function is called for +%% each installed event handler to handle the event. +%%-------------------------------------------------------------------- +handle_event(#event{name=start_logging,node=Node,data=RunDir},State) -> + ct_master_logs_fork:log("CT Master Event Handler","Got ~ts from ~w",[RunDir,Node]), + ct_master_logs_fork:nodedir(Node,RunDir), + {ok,State}; + +handle_event(Event=#event{name=Name,node=Node,data=Data},State) -> + print("~n=== ~w ===~n", [?MODULE]), + print("~tw on ~w: ~tp~n", [Name,Node,Data]), + {ok,maybe_store_event(Event,State)}. + +%%-------------------------------------------------------------------- +%% Function: +%% handle_call(Request, State) -> {ok, Reply, State} | +%% {swap_handler, Reply, Args1, State1, +%% Mod2, Args2} | +%% {remove_handler, Reply} +%% Description: Whenever an event manager receives a request sent using +%% gen_event:call/3,4, this function is called for the specified event +%% handler to handle the request. +%%-------------------------------------------------------------------- +handle_call(get_results,State=#state{auto_skipped=AutoSkipped,failed=Failed}) -> + {ok,#{ + auto_skipped => lists:sort(AutoSkipped), + failed => lists:sort(Failed) + },State}; +handle_call(flush,State) -> + case process_info(self(),message_queue_len) of + {message_queue_len,0} -> + {ok,done,State}; + _ -> + {ok,flushing,State} + end. + +%%-------------------------------------------------------------------- +%% Function: +%% handle_info(Info, State) -> {ok, State} | +%% {swap_handler, Args1, State1, Mod2, Args2} | +%% remove_handler +%% Description: This function is called for each installed event handler when +%% an event manager receives any other message than an event or a synchronous +%% request (or a system message). +%%-------------------------------------------------------------------- +handle_info(_Info,State) -> + {ok,State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> ok +%% Description:Whenever an event handler is deleted from an event manager, +%% this function is called. It should be the opposite of Module:init/1 and +%% do any necessary cleaning up. +%%-------------------------------------------------------------------- +terminate(_Reason,_State) -> + ct_master_logs_fork:log("CT Master Event Handler stopping","",[]), + ok. + +%%-------------------------------------------------------------------- +%% Function: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn,State,_Extra) -> + {ok,State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +print(_Str,_Args) -> +% io:format(_Str,_Args), + ok. + +maybe_store_event(#event{name=tc_done,node=Node,data={Suite,FuncOrGroup,{auto_skipped,Reason}}},State=#state{auto_skipped=Acc}) -> + State#state{auto_skipped=[{Node,Suite,FuncOrGroup,Reason}|Acc]}; +maybe_store_event(#event{name=tc_done,node=Node,data={Suite,FuncOrGroup,{failed,Reason}}},State=#state{failed=Acc}) -> + State#state{failed=[{Node,Suite,FuncOrGroup,Reason}|Acc]}; +maybe_store_event(_Event,State) -> + State. diff --git a/deps/rabbitmq_ct_helpers/src/ct_master_fork.erl b/deps/rabbitmq_ct_helpers/src/ct_master_fork.erl index 443635fe912a..a698ca9e1613 100644 --- a/deps/rabbitmq_ct_helpers/src/ct_master_fork.erl +++ b/deps/rabbitmq_ct_helpers/src/ct_master_fork.erl @@ -177,23 +177,14 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS), Tests -> RunResult = lists:map( - fun({Specs,TSRec=#testspec{logdir=AllLogDirs, - config=StdCfgFiles, - userconfig=UserCfgFiles, - include=AllIncludes, - init=AllInitOpts, - event_handler=AllEvHs}}) -> - AllCfgFiles = - {StdCfgFiles,UserCfgFiles}, + fun({Specs,TSRec=#testspec{}}) -> RunSkipPerNode = ct_testspec:prepare_tests(TSRec), RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode), TSList = if is_integer(hd(TS)) -> [TS]; true -> TS end, - {Specs,run_all(RunSkipPerNode2,AllLogDirs, - AllCfgFiles,AllEvHs, - AllIncludes,[],[],AllInitOpts,TSList)} + {Specs,run_all(RunSkipPerNode2,TSRec,[],[],TSList)} end, Tests), RunResult ++ run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes) end; @@ -258,19 +249,11 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) - Tests -> RunResult = lists:map( - fun({Specs,TSRec=#testspec{logdir=AllLogDirs, - config=StdCfgFiles, - init=AllInitOpts, - include=AllIncludes, - userconfig=UserCfgFiles, - event_handler=AllEvHs}}) -> - AllCfgFiles = {StdCfgFiles,UserCfgFiles}, + fun({Specs,TSRec=#testspec{}}) -> {Run,Skip} = ct_testspec:prepare_tests(TSRec,Node), TSList = if is_integer(hd(TS)) -> [TS]; true -> TS end, - {Specs,run_all([{Node,Run,Skip}],AllLogDirs, - AllCfgFiles,AllEvHs, - AllIncludes, [],[],AllInitOpts,TSList)} + {Specs,run_all([{Node,Run,Skip}],TSRec,[],[],TSList)} end, Tests), RunResult ++ run_on_node(TestSpecs,AllowUserTerms,Node) end; @@ -291,54 +274,117 @@ run_on_node(TestSpecs,Node) -> -run_all([{Node,Run,Skip}|Rest],AllLogDirs, - {AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles, - AllEvHs,AllIncludes,NodeOpts,LogDirs,InitOptions,Specs) -> - LogDir = - lists:foldl(fun({N,Dir},_Found) when N == Node -> - Dir; - ({_N,_Dir},Found) -> - Found; - (Dir,".") -> - Dir; - (_Dir,Found) -> - Found - end,".",AllLogDirs), - - StdCfgFiles = - lists:foldr(fun({N,F},Fs) when N == Node -> [F|Fs]; - ({_N,_F},Fs) -> Fs; - (F,Fs) -> [F|Fs] - end,[],AllStdCfgFiles), - UserCfgFiles = +run_all([{Node,Run,Skip}|Rest],TSRec=#testspec{label = Labels, +% profile = Profiles, + logdir = LogDirs, + logopts = LogOptsList, + basic_html = BHs, + esc_chars = EscChs, + stylesheet = SSs, + verbosity = VLvls, + silent_connections = SilentConnsList, + cover = CoverFs, + cover_stop = CoverStops, + config = Cfgs, + userconfig = UsrCfgs, + event_handler = EvHs, + ct_hooks = CTHooks, + %% Not available in OTP-26. We don't use it so leave commented for now. +% ct_hooks_order = CTHooksOrder0, + enable_builtin_hooks = EnableBuiltinHooks0, + auto_compile = ACs, + abort_if_missing_suites = AiMSs, + include = Incl, + multiply_timetraps = MTs, + scale_timetraps = STs, + create_priv_dir = PDs}, + NodeOpts,LogDirsRun,Specs) -> + %% We mirror ct_run:get_data_for_node to retrieve data from #testspec, + %% but set the default values where appropriate. + Label = proplists:get_value(Node, Labels), +% Profile = proplists:get_value(Node, Profiles), + LogDir = case proplists:get_value(Node, LogDirs) of + undefined -> "."; + Dir -> Dir + end, + LogOpts = case proplists:get_value(Node, LogOptsList) of + undefined -> []; + LOs -> LOs + end, + BasicHtml = proplists:get_value(Node, BHs, false), + EscChars = proplists:get_value(Node, EscChs, true), + Stylesheet = proplists:get_value(Node, SSs), + Verbosity = case proplists:get_value(Node, VLvls) of + undefined -> []; + Lvls -> Lvls + end, + SilentConns = case proplists:get_value(Node, SilentConnsList) of + undefined -> []; + SCs -> SCs + end, + Cover = proplists:get_value(Node, CoverFs), + CoverStop = proplists:get_value(Node, CoverStops, true), + MT = proplists:get_value(Node, MTs, 1), + ST = proplists:get_value(Node, STs, false), + CreatePrivDir = proplists:get_value(Node, PDs, auto_per_run), + %% For these two values we can't exactly mirror get_data_for_node. + ConfigFiles = + lists:foldr(fun({N,F},Fs) when N == Node -> [F|Fs]; + ({_N,_F},Fs) -> Fs; + (F,Fs) -> [F|Fs] + end,[],Cfgs), + UsrConfigFiles = lists:foldr(fun({N,F},Fs) when N == Node -> [{userconfig, F}|Fs]; - ({_N,_F},Fs) -> Fs; - (F,Fs) -> [{userconfig, F}|Fs] - end,[],AllUserCfgFiles), - - Includes = lists:foldr(fun({N,I},Acc) when N =:= Node -> - [I|Acc]; - ({_,_},Acc) -> - Acc; - (I,Acc) -> - [I | Acc] - end, [], AllIncludes), - EvHs = - lists:foldr(fun({N,H,A},Hs) when N == Node -> [{H,A}|Hs]; - ({_N,_H,_A},Hs) -> Hs; - ({H,A},Hs) -> [{H,A}|Hs] - end,[],AllEvHs), - - NO = {Node,[{prepared_tests,{Run,Skip},Specs}, - {ct_hooks, [cth_parallel_ct_detect_failure]}, - {logdir,LogDir}, - {include, Includes}, - {config,StdCfgFiles}, - {event_handler,EvHs}] ++ UserCfgFiles}, - run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,AllIncludes, - [NO|NodeOpts],[LogDir|LogDirs],InitOptions,Specs); -run_all([],AllLogDirs,_,AllEvHs,_AllIncludes, - NodeOpts,LogDirs,InitOptions,Specs) -> + ({_N,_F},Fs) -> Fs; + (F,Fs) -> [{userconfig, F}|Fs] + end,[],UsrCfgs), + EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node], + FiltCTHooks = [Hook || {N,Hook} <- CTHooks, N==Node], +% CTHooksOrder = case CTHooksOrder0 of +% undefined -> test; +% _ -> CTHooksOrder0 +% end, + EnableBuiltinHooks = case EnableBuiltinHooks0 of + undefined -> true; + _ -> EnableBuiltinHooks0 + end, + AutoCompile = proplists:get_value(Node, ACs, true), + AbortIfMissing = proplists:get_value(Node, AiMSs, false), + Include = [I || {N,I} <- Incl, N==Node], + %% We then build the ct:run_test/1 options list. + RunTestOpts0 = + [{label, Label} || Label =/= undefined] ++ + [{stylesheet, Stylesheet} || Stylesheet =/= undefined] ++ + [{cover, Cover} || Cover =/= undefined] ++ + UsrConfigFiles, + RunTestOpts = [ +% {profile, Profile}, + {logdir, LogDir}, + {logopts, LogOpts}, + {basic_html, BasicHtml}, + {esc_chars, EscChars}, + {verbosity, Verbosity}, + {silent_connections, SilentConns}, + {cover_stop, CoverStop}, + {config, ConfigFiles}, + {event_handler, EvHandlers}, + {ct_hooks, FiltCTHooks}, +% {ct_hooks_order, CTHooksOrder}, + {enable_builtin_hooks, EnableBuiltinHooks}, + {auto_compile, AutoCompile}, + {abort_if_missing_suites, AbortIfMissing}, + {include, Include}, + {multiply_timetraps, MT}, + {scale_timetraps, ST}, + {create_priv_dir, CreatePrivDir} + |RunTestOpts0], + NO = {Node,[{prepared_tests,{Run,Skip},Specs}|RunTestOpts]}, + run_all(Rest,TSRec,[NO|NodeOpts],[LogDir|LogDirsRun],Specs); +run_all([],#testspec{ + logdir=AllLogDirs, + init=InitOptions, + event_handler=AllEvHs}, + NodeOpts,LogDirsRun,Specs) -> Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master], MasterLogDir = case lists:keysearch(master,1,AllLogDirs) of {value,{_,Dir}} -> Dir; @@ -346,8 +392,7 @@ run_all([],AllLogDirs,_,AllEvHs,_AllIncludes, end, log(tty,"Master Logdir","~ts",[MasterLogDir]), start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir, - LogDirs,InitOptions,Specs), - ok. + LogDirsRun,InitOptions,Specs). %-doc """ @@ -446,7 +491,7 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs, end, %% start master logger - {MLPid,_} = ct_master_logs:start(MasterLogDir, + {MLPid,_} = ct_master_logs_fork:start(MasterLogDir, [N || {N,_} <- NodeOptsList]), log(all,"Master Logger process started","~w",[MLPid]), @@ -456,13 +501,13 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs, SpecsStr = lists:map(fun(Name) -> Name ++ " " end,Specs), - ct_master_logs:log("Test Specification file(s)","~ts", + ct_master_logs_fork:log("Test Specification file(s)","~ts", [lists:flatten(SpecsStr)]) end, %% start master event manager and add default handler {ok, _} = start_ct_master_event(), - ct_master_event:add_handler(), + ct_master_event_fork:add_handler(), %% add user handlers for master event manager Add = fun({H,Args}) -> log(all,"Adding Event Handler","~w",[H]), @@ -484,7 +529,7 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs, init_master1(Parent,NodeOptsList,InitOptions,LogDirs). start_ct_master_event() -> - case ct_master_event:start_link() of + case ct_master_event_fork:start_link() of {error, {already_started, Pid}} -> {ok, Pid}; Else -> @@ -510,8 +555,8 @@ init_master1(Parent,NodeOptsList,InitOptions,LogDirs) -> init_master1(Parent,NodeOptsList,InitOptions1,LogDirs); _ -> log(html,"Aborting Tests","",[]), - ct_master_event:stop(), - ct_master_logs:stop(), + ct_master_event_fork:stop(), + ct_master_logs_fork:stop(), exit(aborted) end end. @@ -535,20 +580,22 @@ init_master2(Parent,NodeOptsList,LogDirs) -> Parent ! {self(),Result}. master_loop(#state{node_ctrl_pids=[], - logdirs=LogDirs, - results=Finished}) -> + results=Finished0}) -> + Finished = lists:sort(Finished0), Str = lists:map(fun({Node,Result}) -> io_lib:format("~-40.40.*ts~tp\n", [$_,atom_to_list(Node),Result]) - end,lists:reverse(Finished)), + end,Finished), log(all,"TEST RESULTS","~ts", [Str]), log(all,"Info","Updating log files",[]), - refresh_logs(LogDirs,[]), - - ct_master_event:stop(), - ct_master_logs:stop(), - ok; + + %% Print the failed and auto skipped tests. + master_print_summary(), + + ct_master_event_fork:stop(), + ct_master_logs_fork:stop(), + {ok, Finished}; master_loop(State=#state{node_ctrl_pids=NodeCtrlPids, results=Results, @@ -658,11 +705,31 @@ master_loop(State=#state{node_ctrl_pids=NodeCtrlPids, blocked=Blocked1}); {cast,Event} when is_record(Event,event) -> - ct_master_event:notify(Event), + ct_master_event_fork:notify(Event), master_loop(State) end. +master_print_summary() -> + #{ + auto_skipped := AutoSkipped, + failed := Failed + } = ct_master_event_fork:get_results(), + master_print_summary_for("Auto skipped test cases", AutoSkipped), + master_print_summary_for("Failed test cases", Failed), + ok. + +master_print_summary_for(Title,List) -> + _ = case List of + [] -> ok; + _ -> + Chars = [ + io_lib:format("Node: ~w~nCase: ~w:~w~nReason: ~p~n~n", + [Node, Suite, FuncOrGroup, Reason]) + || {Node, Suite, FuncOrGroup, Reason} <- List], + log(all,Title,Chars,[]) + end, + ok. update_queue(take,Node,From,Lock={Op,Resource},Locks,Blocked) -> %% Locks: [{{Operation,Resource},Node},...] @@ -740,34 +807,6 @@ master_progress(NodeCtrlPids,Results) -> {Node,ongoing} end,NodeCtrlPids). -%% refresh those dirs where more than one node has written logs -refresh_logs([D|Dirs],Refreshed) -> - case lists:member(D,Dirs) of - true -> - case lists:keymember(D,1,Refreshed) of - true -> - refresh_logs(Dirs,Refreshed); - false -> - {ok,Cwd} = file:get_cwd(), - case catch ct_run:refresh_logs(D, unknown) of - {'EXIT',Reason} -> - ok = file:set_cwd(Cwd), - refresh_logs(Dirs,[{D,{error,Reason}}|Refreshed]); - Result -> - refresh_logs(Dirs,[{D,Result}|Refreshed]) - end - end; - false -> - refresh_logs(Dirs,Refreshed) - end; -refresh_logs([],Refreshed) -> - Str = - lists:map(fun({D,Result}) -> - io_lib:format("Refreshing logs in ~tp... ~tp", - [D,Result]) - end,Refreshed), - log(all,"Info","~ts", [Str]). - %%%----------------------------------------------------------------- %%% NODE CONTROLLER, runs and controls tests on a test node. %%%----------------------------------------------------------------- @@ -851,7 +890,7 @@ log(To,Heading,Str,Args) -> ok end, if To == all ; To == html -> - ct_master_logs:log(Heading,Str,Args); + ct_master_logs_fork:log(Heading,Str,Args); true -> ok end. diff --git a/deps/rabbitmq_ct_helpers/src/ct_master_logs_fork.erl b/deps/rabbitmq_ct_helpers/src/ct_master_logs_fork.erl new file mode 100644 index 000000000000..9541c941708b --- /dev/null +++ b/deps/rabbitmq_ct_helpers/src/ct_master_logs_fork.erl @@ -0,0 +1,569 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2024. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%%% Logging functionality for Common Test Master. +%%% +%%% This module implements a logger for the master +%%% node. +-module(ct_master_logs_fork). +-moduledoc false. + +-export([start/2, make_all_runs_index/0, log/3, nodedir/2, + stop/0]). + +-include_lib("common_test/src/ct_util.hrl"). + +-record(state, {log_fd, start_time, logdir, rundir, + nodedir_ix_fd, nodes, nodedirs=[]}). + +-define(ct_master_log_name, "ct_master_log.html"). +-define(all_runs_name, "master_runs.html"). +-define(nodedir_index_name, "index.html"). +-define(details_file_name,"details.info"). +-define(table_color,"lightblue"). + +-define(now, os:timestamp()). + +%%%-------------------------------------------------------------------- +%%% API +%%%-------------------------------------------------------------------- + +start(LogDir,Nodes) -> + Self = self(), + Pid = spawn_link(fun() -> init(Self,LogDir,Nodes) end), + MRef = erlang:monitor(process,Pid), + receive + {started,Pid,Result} -> + erlang:demonitor(MRef, [flush]), + {Pid,Result}; + {'DOWN',MRef,process,_,Reason} -> + exit({could_not_start_process,?MODULE,Reason}) + end. + +log(Heading,Format,Args) -> + cast({log,self(),[{int_header(),[log_timestamp(?now),Heading]}, + {Format,Args}, + {int_footer(),[]}]}), + ok. + +make_all_runs_index() -> + call(make_all_runs_index). + +nodedir(Node,RunDir) -> + call({nodedir,Node,RunDir}). + +stop() -> + case whereis(?MODULE) of + Pid when is_pid(Pid) -> + MRef = erlang:monitor(process,Pid), + ?MODULE ! stop, + receive + {'DOWN',MRef,process,_,_} -> + ok + end; + undefined -> + ok + end, + ok. + +%%%-------------------------------------------------------------------- +%%% Logger process +%%%-------------------------------------------------------------------- + +init(Parent,LogDir,Nodes) -> + register(?MODULE,self()), + ct_util:mark_process(), + Time = calendar:local_time(), + RunDir = make_dirname(Time), + RunDirAbs = filename:join(LogDir,RunDir), + ok = make_dir(RunDirAbs), + _ = write_details_file(RunDirAbs,{node(),Nodes}), + + case basic_html() of + true -> + put(basic_html, true); + BasicHtml -> + put(basic_html, BasicHtml), + %% copy priv files to log dir (both top dir and test run + %% dir) so logs are independent of Common Test installation + CTPath = code:lib_dir(common_test), + PrivFiles = [?css_default,?jquery_script,?tablesorter_script], + PrivFilesSrc = [filename:join(filename:join(CTPath, "priv"), F) || + F <- PrivFiles], + PrivFilesDestTop = [filename:join(LogDir, F) || F <- PrivFiles], + PrivFilesDestRun = [filename:join(RunDirAbs, F) || F <- PrivFiles], + case copy_priv_files(PrivFilesSrc, PrivFilesDestTop) of + {error,Src1,Dest1,Reason1} -> + io:format(user, "ERROR! "++ + "Priv file ~tp could not be copied to ~tp. "++ + "Reason: ~tp~n", + [Src1,Dest1,Reason1]), + exit({priv_file_error,Dest1}); + ok -> + case copy_priv_files(PrivFilesSrc, PrivFilesDestRun) of + {error,Src2,Dest2,Reason2} -> + io:format(user, "ERROR! "++ + "Priv file ~tp could not be copied to ~tp. "++ + "Reason: ~tp~n", + [Src2,Dest2,Reason2]), + exit({priv_file_error,Dest2}); + ok -> + ok + end + end + end, + + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(LogDir), + _ = make_all_runs_index(LogDir), + CtLogFd = open_ct_master_log(RunDirAbs), + ok = file:set_cwd(Cwd), + + NodeStr = + lists:flatten(lists:map(fun(N) -> + atom_to_list(N) ++ " " + end,Nodes)), + + io:format(CtLogFd,int_header(),[log_timestamp(?now),"Test Nodes\n"]), + io:format(CtLogFd,"~ts\n",[NodeStr]), + io:put_chars(CtLogFd,[int_footer(),"\n"]), + + NodeDirIxFd = open_nodedir_index(RunDirAbs,Time), + Parent ! {started,self(),{Time,RunDirAbs}}, + loop(#state{log_fd=CtLogFd, + start_time=Time, + logdir=LogDir, + rundir=RunDirAbs, + nodedir_ix_fd=NodeDirIxFd, + nodes=Nodes, + nodedirs=lists:map(fun(N) -> + {N,""} + end,Nodes)}). + +copy_priv_files([SrcF | SrcFs], [DestF | DestFs]) -> + case file:copy(SrcF, DestF) of + {error,Reason} -> + {error,SrcF,DestF,Reason}; + _ -> + copy_priv_files(SrcFs, DestFs) + end; +copy_priv_files([], []) -> + ok. + +loop(State) -> + receive + {log,_From,List} -> + Fd = State#state.log_fd, + Fun = + fun({Str,Args}) -> + case catch io:format(Fd,Str++"\n",Args) of + {'EXIT',Reason} -> + io:format(Fd, + "Logging fails! Str: ~tp, Args: ~tp~n", + [Str,Args]), + exit({logging_failed,Reason}), + ok; + _ -> + ok + end + end, + lists:foreach(Fun,List), + loop(State); + {make_all_runs_index,From} -> + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(State#state.logdir), + _ = make_all_runs_index(State#state.logdir), + ok = file:set_cwd(Cwd), + return(From,State#state.logdir), + loop(State); + {{nodedir,Node,RunDir},From} -> + print_nodedir(Node,RunDir,State#state.nodedir_ix_fd), + return(From,ok), + loop(State); + stop -> + {ok,Cwd} = file:get_cwd(), + ok = file:set_cwd(State#state.logdir), + _ = make_all_runs_index(State#state.logdir), + ok = file:set_cwd(Cwd), + io:format(State#state.log_fd, + int_header()++int_footer(), + [log_timestamp(?now),"Finished!"]), + _ = close_ct_master_log(State#state.log_fd), + _ = close_nodedir_index(State#state.nodedir_ix_fd), + ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Master Log functions %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +open_ct_master_log(Dir) -> + FullName = filename:join(Dir,?ct_master_log_name), + {ok,Fd} = file:open(FullName,[write,{encoding,utf8}]), + io:put_chars(Fd,header("Common Test Master Log", {[],[1,2],[]})), + %% maybe add config info here later + io:put_chars(Fd,config_table([])), + io:put_chars(Fd, + "\n"), + io:put_chars(Fd, + xhtml("

Progress Log

\n
\n",
+		       "

Progress Log

\n
\n")),
+    Fd.
+
+close_ct_master_log(Fd) ->
+    io:put_chars(Fd,["
",footer()]), + file:close(Fd). + +config_table(Vars) -> + [config_table_header()|config_table1(Vars)]. + +config_table_header() -> + ["

Configuration

\n", + xhtml(["\n", + "\n"]), + "\n", + xhtml("", "\n\n")]. + +config_table1([]) -> + ["\n
KeyValue
\n"]. + +int_header() -> + "
\n
*** CT MASTER ~s *** ~ts".
+int_footer() ->
+    "
\n
".
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% NodeDir Index functions %%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+open_nodedir_index(Dir,StartTime) ->
+    FullName = filename:join(Dir,?nodedir_index_name),
+    {ok,Fd} = file:open(FullName,[write,{encoding,utf8}]),
+    io:put_chars(Fd,nodedir_index_header(StartTime)),
+    Fd.
+
+print_nodedir(Node,RunDir,Fd) ->
+    Index = filename:join(RunDir,"index.html"),
+    io:put_chars(Fd,
+		 ["\n"
+		  "",atom_to_list(Node),"\n",
+		  "",Index,
+		  "\n",
+		  "\n"]),
+    ok.
+
+close_nodedir_index(Fd) ->
+    io:put_chars(Fd,index_footer()),
+    file:close(Fd).
+
+nodedir_index_header(StartTime) ->
+    [header("Log Files " ++ format_time(StartTime), {[],[1,2],[]}) |
+     ["
\n", + "

Common Test Master Log

", + xhtml(["\n"], + ["
\n", + "\n\n"]), + "\n", + "\n", + xhtml("", "\n\n\n")]]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% All Run Index functions %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +make_all_runs_index(LogDir) -> + FullName = filename:join(LogDir,?all_runs_name), + Match = filename:join(LogDir,logdir_prefix()++"*.*"), + Dirs = filelib:wildcard(Match), + DirsSorted = (catch sort_all_runs(Dirs)), + Header = all_runs_header(), + Index = [runentry(Dir) || Dir <- DirsSorted], + Result = file:write_file(FullName, + unicode:characters_to_binary( + Header++Index++index_footer())), + Result. + +sort_all_runs(Dirs) -> + %% sort on time string, always last and on the format: + %% "YYYY-MM-DD_HH.MM.SS" + KeyList = + lists:map(fun(Dir) -> + case lists:reverse(string:lexemes(Dir,[$.,$_])) of + [SS,MM,HH,Date|_] -> + {{Date,HH,MM,SS},Dir}; + _Other -> + throw(Dirs) + end + end,Dirs), + lists:reverse(lists:map(fun({_,Dir}) -> + Dir + end,lists:keysort(1,KeyList))). + +runentry(Dir) -> + {MasterStr,NodesStr} = + case read_details_file(Dir) of + {Master,Nodes} when is_list(Nodes) -> + [_,Host] = string:lexemes(atom_to_list(Master),"@"), + {Host,lists:concat(lists:join(", ",Nodes))}; + _Error -> + {"unknown",""} + end, + Index = filename:join(Dir,?nodedir_index_name), + ["\n" + "\n", + "\n", + "\n", + "\n"]. + +all_runs_header() -> + [header("Master Test Runs", {[1],[2,3],[]}) | + ["
\n", + xhtml(["
NodeLog
", + timestamp(Dir),"",MasterStr,"",NodesStr,"
\n"], + ["
\n", + "\n\n"]), + "\n" + "\n" + "\n", + xhtml("", "\n\n")]]. + +timestamp(Dir) -> + [S,Min,H,D,M,Y|_] = lists:reverse(string:lexemes(Dir,".-_")), + [S1,Min1,H1,D1,M1,Y1] = [list_to_integer(N) || N <- [S,Min,H,D,M,Y]], + format_time({{Y1,M1,D1},{H1,Min1,S1}}). + +write_details_file(Dir,Details) -> + FullName = filename:join(Dir,?details_file_name), + force_write_file(FullName,term_to_binary(Details)). + +read_details_file(Dir) -> + FullName = filename:join(Dir,?details_file_name), + case file:read_file(FullName) of + {ok,Bin} -> + binary_to_term(Bin); + Error -> + Error + end. + +%%%-------------------------------------------------------------------- +%%% Internal functions +%%%-------------------------------------------------------------------- + +header(Title, TableCols) -> + CSSFile = xhtml(fun() -> "" end, + fun() -> make_relative(locate_priv_file(?css_default)) end), + JQueryFile = + xhtml(fun() -> "" end, + fun() -> make_relative(locate_priv_file(?jquery_script)) end), + TableSorterFile = + xhtml(fun() -> "" end, + fun() -> make_relative(locate_priv_file(?tablesorter_script)) end), + + [xhtml(["\n", + "\n"], + ["\n", + "\n"]), + "\n", + "\n", + "" ++ Title ++ "\n", + "\n", + "\n", + xhtml("", + ["\n"]), + xhtml("", + ["\n"]), + xhtml("", + ["\n"]), + xhtml(fun() -> "" end, + fun() -> ct_logs:insert_javascript({tablesorter, + ?sortable_table_name, + TableCols}) end), + "\n", + body_tag(), + "
\n", + "

" ++ Title ++ "

\n", + "
\n"]. + +index_footer() -> + ["\n
HistoryMaster HostTest Nodes
\n" + "
\n" | footer()]. + +footer() -> + ["
\n", + xhtml("

\n", "
\n"), + xhtml("

\n", "

"), + "Copyright © ", year(), + " Open Telecom Platform", + xhtml("
\n", "
\n"), + "Updated: ", current_time(), "", + xhtml("
\n", "
\n"), + xhtml("

\n", "
\n"), + "
\n" + "\n"]. + +body_tag() -> + xhtml("\n", + "\n"). + +current_time() -> + format_time(calendar:local_time()). + +format_time({{Y, Mon, D}, {H, Min, S}}) -> + Weekday = weekday(calendar:day_of_the_week(Y, Mon, D)), + lists:flatten(io_lib:format("~s ~s ~2.2.0w ~w ~2.2.0w:~2.2.0w:~2.2.0w", + [Weekday, month(Mon), D, Y, H, Min, S])). + +weekday(1) -> "Mon"; +weekday(2) -> "Tue"; +weekday(3) -> "Wed"; +weekday(4) -> "Thu"; +weekday(5) -> "Fri"; +weekday(6) -> "Sat"; +weekday(7) -> "Sun". + +month(1) -> "Jan"; +month(2) -> "Feb"; +month(3) -> "Mar"; +month(4) -> "Apr"; +month(5) -> "May"; +month(6) -> "Jun"; +month(7) -> "Jul"; +month(8) -> "Aug"; +month(9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +year() -> + {Y, _, _} = date(), + integer_to_list(Y). + + +make_dirname({{YY,MM,DD},{H,M,S}}) -> + io_lib:format(logdir_prefix()++".~w-~2.2.0w-~2.2.0w_~2.2.0w.~2.2.0w.~2.2.0w", + [YY,MM,DD,H,M,S]). + +logdir_prefix() -> + "ct_master_run". + +log_timestamp(Now) -> + put(log_timestamp,Now), + {_,{H,M,S}} = calendar:now_to_local_time(Now), + lists:flatten(io_lib:format("~2.2.0w:~2.2.0w:~2.2.0w", + [H,M,S])). + +basic_html() -> + case application:get_env(common_test_master, basic_html) of + {ok,true} -> + true; + _ -> + false + end. + +xhtml(HTML, XHTML) -> + ct_logs:xhtml(HTML, XHTML). + +locate_priv_file(File) -> + ct_logs:locate_priv_file(File). + +make_relative(Dir) -> + ct_logs:make_relative(Dir). + +force_write_file(Name,Contents) -> + _ = force_delete(Name), + file:write_file(Name,Contents). + +force_delete(Name) -> + case file:delete(Name) of + {error,eacces} -> + force_rename(Name,Name++".old.",0); + Other -> + Other + end. + +force_rename(From,To,Number) -> + Dest = [To|integer_to_list(Number)], + case file:read_file_info(Dest) of + {ok,_} -> + force_rename(From,To,Number+1); + {error,_} -> + file:rename(From,Dest) + end. + +call(Msg) -> + case whereis(?MODULE) of + undefined -> + {error,does_not_exist}; + Pid -> + MRef = erlang:monitor(process,Pid), + Ref = make_ref(), + ?MODULE ! {Msg,{self(),Ref}}, + receive + {Ref, Result} -> + erlang:demonitor(MRef, [flush]), + Result; + {'DOWN',MRef,process,_,Reason} -> + {error,{process_down,?MODULE,Reason}} + end + end. + +return({To,Ref},Result) -> + To ! {Ref, Result}, + ok. + +cast(Msg) -> + case whereis(?MODULE) of + undefined -> + io:format("Warning: ct_master_logs not started~n"), + {_,_,Content} = Msg, + FormatArgs = get_format_args(Content), + _ = [io:format(Format, Args) || {Format, Args} <- FormatArgs], + ok; + _Pid -> + ?MODULE ! Msg, + ok + end. + +get_format_args(Content) -> + lists:map(fun(C) -> + case C of + {_, FA, _} -> FA; + _ -> C + end + end, Content). + +make_dir(Dir) -> + case file:make_dir(Dir) of + {error, eexist} -> + ok; + Else -> + Else + end. diff --git a/deps/rabbitmq_ct_helpers/src/cth_parallel_ct_detect_failure.erl b/deps/rabbitmq_ct_helpers/src/cth_parallel_ct_detect_failure.erl deleted file mode 100644 index 428e37468bf4..000000000000 --- a/deps/rabbitmq_ct_helpers/src/cth_parallel_ct_detect_failure.erl +++ /dev/null @@ -1,23 +0,0 @@ --module(cth_parallel_ct_detect_failure). - --export([init/2]). --export([on_tc_fail/4]). --export([has_failures/0]). - -init(_Id, _Opts) -> - {ok, undefined}. - -%% We silence failures in end_per_suite/end_per_group -%% to mirror the default behavior. It should be modified -%% so that they are configured failures as well, but can -%% be done at a later time. -on_tc_fail(_SuiteName, end_per_suite, _Reason, CTHState) -> - CTHState; -on_tc_fail(_SuiteName, {end_per_group, _GroupName}, _Reason, CTHState) -> - CTHState; -on_tc_fail(_SuiteName, _TestName, _Reason, CTHState) -> - persistent_term:put(?MODULE, true), - CTHState. - -has_failures() -> - persistent_term:get(?MODULE, false). diff --git a/deps/rabbitmq_ct_helpers/src/rabbit_ct_broker_helpers.erl b/deps/rabbitmq_ct_helpers/src/rabbit_ct_broker_helpers.erl index b01ea002842e..77c78cc98ac5 100644 --- a/deps/rabbitmq_ct_helpers/src/rabbit_ct_broker_helpers.erl +++ b/deps/rabbitmq_ct_helpers/src/rabbit_ct_broker_helpers.erl @@ -275,7 +275,7 @@ run_make_dist(Config) -> end; _ -> global:del_lock(LockId, [node()]), - ct:pal(?LOW_IMPORTANCE, "(skip `$MAKE test-dist`)", []), + ct:log(?LOW_IMPORTANCE, "(skip `$MAKE test-dist`)", []), Config end. @@ -819,7 +819,7 @@ query_node(Config, NodeConfig) -> %% 3.7.x node. If this is the case, we can ignore %% this and leave the `enabled_plugins_file` config %% variable unset. - ct:pal("NO RABBITMQ_FEATURE_FLAGS_FILE"), + ct:log("NO RABBITMQ_FEATURE_FLAGS_FILE"), Vars0 end, cover_add_node(Nodename), @@ -883,7 +883,7 @@ handle_nodes_in_parallel(NodeConfigs, Fun) -> T1 = erlang:monotonic_time(), Ret = Fun(NodeConfig), T2 = erlang:monotonic_time(), - ct:pal( + ct:log( ?LOW_IMPORTANCE, "Time to run ~tp for node ~ts: ~b us", [Fun, @@ -901,7 +901,7 @@ handle_nodes_in_parallel(NodeConfigs, Fun) -> wait_for_node_handling([], Fun, T0, Results) -> T3 = erlang:monotonic_time(), - ct:pal( + ct:log( ?LOW_IMPORTANCE, "Time to run ~tp for all nodes: ~b us", [Fun, erlang:convert_time_unit(T3 - T0, native, microsecond)]), @@ -956,7 +956,7 @@ configured_metadata_store(Config) -> end. configure_metadata_store(Config) -> - ct:pal("Configuring metadata store..."), + ct:log("Configuring metadata store..."), case configured_metadata_store(Config) of {khepri, FFs0} -> case enable_khepri_metadata_store(Config, FFs0) of @@ -967,12 +967,12 @@ configure_metadata_store(Config) -> Config1 end; mnesia -> - ct:pal("Enabling Mnesia metadata store"), + ct:log("Enabling Mnesia metadata store"), Config end. enable_khepri_metadata_store(Config, FFs0) -> - ct:pal("Enabling Khepri metadata store"), + ct:log("Enabling Khepri metadata store"), FFs = [khepri_db | FFs0], lists:foldl(fun(_FF, {skip, _Reason} = Skip) -> Skip; @@ -1143,7 +1143,7 @@ stop_rabbitmq_node(Config, NodeConfig) -> NodeConfig. find_crashes_in_logs(NodeConfigs, IgnoredCrashes) -> - ct:pal( + ct:log( "Looking up any crash reports in the nodes' log files. If we find " "some, they will appear below:"), CrashesCount = lists:foldl( @@ -1152,7 +1152,11 @@ find_crashes_in_logs(NodeConfigs, IgnoredCrashes) -> NodeConfig, IgnoredCrashes), Total + Count end, 0, NodeConfigs), - ct:pal("Found ~b crash report(s)", [CrashesCount]), + LogFn = case CrashesCount of + 0 -> log; + _ -> pal + end, + ct:LogFn("Found ~b crash report(s)", [CrashesCount]), ?assertEqual(0, CrashesCount). count_crashes_in_logs(NodeConfig, IgnoredCrashes) -> diff --git a/deps/rabbitmq_ct_helpers/src/rabbit_ct_helpers.erl b/deps/rabbitmq_ct_helpers/src/rabbit_ct_helpers.erl index d9e34cf38fa6..c9b351ddd6ab 100644 --- a/deps/rabbitmq_ct_helpers/src/rabbit_ct_helpers.erl +++ b/deps/rabbitmq_ct_helpers/src/rabbit_ct_helpers.erl @@ -66,7 +66,7 @@ log_environment() -> Vars = lists:sort(fun(A, B) -> A =< B end, os:getenv()), - ct:pal(?LOW_IMPORTANCE, "Environment variables:~n~ts", + ct:log(?LOW_IMPORTANCE, "Environment variables:~n~ts", [[io_lib:format(" ~ts~n", [V]) || V <- Vars]]). run_setup_steps(Config) -> @@ -152,7 +152,7 @@ run_steps(Config, []) -> Config. redirect_logger_to_ct_logs(Config) -> - ct:pal( + ct:log( ?LOW_IMPORTANCE, "Configuring logger to send logs to common_test logs"), ok = logger:set_handler_config(cth_log_redirect, level, debug), @@ -172,7 +172,7 @@ redirect_logger_to_ct_logs(Config) -> ok = logger:remove_handler(default), - ct:pal( + ct:log( ?LOW_IMPORTANCE, "Logger configured to send logs to common_test logs; you should see " "a message below saying so"), @@ -433,12 +433,12 @@ ensure_rabbitmqctl_cmd(Config) -> false -> find_script(Config, "rabbitmqctl"); R -> - ct:pal(?LOW_IMPORTANCE, + ct:log(?LOW_IMPORTANCE, "Using rabbitmqctl from RABBITMQCTL: ~tp~n", [R]), R end; R -> - ct:pal(?LOW_IMPORTANCE, + ct:log(?LOW_IMPORTANCE, "Using rabbitmqctl from rabbitmqctl_cmd: ~tp~n", [R]), R end, @@ -470,7 +470,7 @@ find_script(Config, Script) -> filelib:is_file(File)], case Locations of [Location | _] -> - ct:pal(?LOW_IMPORTANCE, "Using ~ts at ~tp~n", [Script, Location]), + ct:log(?LOW_IMPORTANCE, "Using ~ts at ~tp~n", [Script, Location]), Location; [] -> false @@ -555,7 +555,7 @@ ensure_rabbitmq_queues_cmd(Config) -> R -> R end; R -> - ct:pal(?LOW_IMPORTANCE, + ct:log(?LOW_IMPORTANCE, "Using rabbitmq-queues from rabbitmq_queues_cmd: ~tp~n", [R]), R end, @@ -654,12 +654,12 @@ symlink_priv_dir(Config) -> Target = filename:join([SrcDir, "logs", Name]), case exec(["ln", "-snf", PrivDir, Target]) of {ok, _} -> ok; - _ -> ct:pal(?LOW_IMPORTANCE, + _ -> ct:log(?LOW_IMPORTANCE, "Failed to symlink private_log directory.") end, Config; not_found -> - ct:pal(?LOW_IMPORTANCE, + ct:log(?LOW_IMPORTANCE, "Failed to symlink private_log directory."), Config end @@ -684,7 +684,7 @@ load_elixir(Config) -> {skip, _} = Skip -> Skip; ElixirLibDir -> - ct:pal(?LOW_IMPORTANCE, "Elixir lib dir: ~ts~n", [ElixirLibDir]), + ct:log(?LOW_IMPORTANCE, "Elixir lib dir: ~ts~n", [ElixirLibDir]), true = code:add_pathz(ElixirLibDir), {ok, _} = application:ensure_all_started(elixir), Config @@ -720,14 +720,18 @@ long_running_testsuite_monitor(TimerRef, Testcases) -> long_running_testsuite_monitor(TimerRef, Testcases1); ping_ct -> T1 = erlang:monotonic_time(seconds), - ct:pal(?STD_IMPORTANCE, "Testcases still in progress:~ts", - [[ + InProgress = [ begin TDiff = format_time_diff(T1, T0), rabbit_misc:format("~n - ~ts (~ts)", [TC, TDiff]) end || {TC, T0} <- Testcases - ]]), + ], + case InProgress of + [] -> ok; + _ -> ct:pal(?STD_IMPORTANCE, "Testcases still in progress:~ts", + [InProgress]) + end, long_running_testsuite_monitor(TimerRef, Testcases); stop -> timer:cancel(TimerRef) @@ -905,7 +909,7 @@ exec([Cmd | Args], Options) when is_list(Cmd) orelse is_binary(Cmd) -> %% Because Args1 may contain binaries, we don't use string:join(). %% Instead we do a list comprehension. ArgsIoList = [Cmd1, [[$\s, Arg] || Arg <- Args1]], - ct:pal(?LOW_IMPORTANCE, Log1, [ArgsIoList, self()]), + ct:log(?LOW_IMPORTANCE, Log1, [ArgsIoList, self()]), try Port = erlang:open_port( {spawn_executable, Cmd1}, [ @@ -951,10 +955,10 @@ port_receive_loop(Port, Stdout, Options, Until, DumpTimer) -> Stdout =:= "", if DropStdout -> - ct:pal(?LOW_IMPORTANCE, "Exit code: ~tp (pid ~tp)", + ct:log(?LOW_IMPORTANCE, "Exit code: ~tp (pid ~tp)", [X, self()]); true -> - ct:pal(?LOW_IMPORTANCE, "~ts~nExit code: ~tp (pid ~tp)", + ct:log(?LOW_IMPORTANCE, "~ts~nExit code: ~tp (pid ~tp)", [Stdout, X, self()]) end, case proplists:get_value(match_stdout, Options) of @@ -976,7 +980,7 @@ port_receive_loop(Port, Stdout, Options, Until, DumpTimer) -> DropStdout -> ok; true -> - ct:pal(?LOW_IMPORTANCE, "~ts~n[Command still in progress] (pid ~tp)", + ct:log(?LOW_IMPORTANCE, "~ts~n[Command still in progress] (pid ~tp)", [Stdout, self()]) end, port_receive_loop(Port, Stdout, Options, Until, stdout_dump_timer()); @@ -1101,7 +1105,7 @@ eventually({Line, Assertion} = TestObj, PollInterval, PollCount) ok -> ok; Err -> - ct:pal(?LOW_IMPORTANCE, + ct:log(?LOW_IMPORTANCE, "Retrying in ~bms for ~b more times due to failed assertion in line ~b: ~tp", [PollInterval, PollCount - 1, Line, Err]), timer:sleep(PollInterval), diff --git a/deps/rabbitmq_mqtt/Makefile b/deps/rabbitmq_mqtt/Makefile index 48dcca6c934f..6a74a6a80c97 100644 --- a/deps/rabbitmq_mqtt/Makefile +++ b/deps/rabbitmq_mqtt/Makefile @@ -82,22 +82,16 @@ define ct_master.erl peer:call(Pid2, persistent_term, put, [rabbit_ct_tcp_port_base, 25000]), peer:call(Pid3, persistent_term, put, [rabbit_ct_tcp_port_base, 27000]), peer:call(Pid4, persistent_term, put, [rabbit_ct_tcp_port_base, 29000]), - ct_master_fork:run("$1"), - Fail1 = peer:call(Pid1, cth_parallel_ct_detect_failure, has_failures, []), - Fail2 = peer:call(Pid2, cth_parallel_ct_detect_failure, has_failures, []), - Fail3 = peer:call(Pid3, cth_parallel_ct_detect_failure, has_failures, []), - Fail4 = peer:call(Pid4, cth_parallel_ct_detect_failure, has_failures, []), + [{[_], {ok, Results}}] = ct_master_fork:run("$1"), peer:stop(Pid4), peer:stop(Pid3), peer:stop(Pid2), peer:stop(Pid1), - if - Fail1 -> halt(1); - Fail2 -> halt(2); - Fail3 -> halt(3); - Fail4 -> halt(4); - true -> halt(0) - end + lists:foldl(fun + ({_, {_, 0, {_, 0}}}, Err) -> Err + 1; + (What, Peer) -> halt(Peer) + end, 1, Results), + halt(0) endef PARALLEL_CT_SET_1_A = auth retainer @@ -116,6 +110,7 @@ define tpl_parallel_ct_test_spec {logdir, "$(CT_LOGS_DIR)"}. {logdir, master, "$(CT_LOGS_DIR)"}. {create_priv_dir, all_nodes, auto_per_run}. +{auto_compile, false}. {node, shard1, 'rabbit_shard1@localhost'}. {node, shard2, 'rabbit_shard2@localhost'}. diff --git a/moduleindex.yaml b/moduleindex.yaml index 969c58a7ace3..298a9a8b1413 100755 --- a/moduleindex.yaml +++ b/moduleindex.yaml @@ -868,9 +868,10 @@ rabbitmq_consistent_hash_exchange: rabbitmq_ct_client_helpers: - rabbit_ct_client_helpers rabbitmq_ct_helpers: +- ct_master_event_fork - ct_master_fork +- ct_master_logs_fork - cth_log_redirect_any_domains -- cth_parallel_ct_detect_failure - rabbit_control_helper - rabbit_ct_broker_helpers - rabbit_ct_config_schema