From 6b57abc785818bccf8ee615d3071dc24fa54daca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Thu, 13 Sep 2012 08:28:09 +0200 Subject: common_test: Enable running cover on common_test --- lib/common_test/test/Makefile | 2 +- lib/common_test/test/common_test.cover | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 lib/common_test/test/common_test.cover diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 64e2cb6507..652a9f6efe 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -108,7 +108,7 @@ release_spec: opt release_tests_spec: $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(ERL_FILES) $(COVERFILE) "$(RELSYSDIR)" - $(INSTALL_DATA) common_test.spec "$(RELSYSDIR)" + $(INSTALL_DATA) common_test.spec common_test.cover "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) diff --git a/lib/common_test/test/common_test.cover b/lib/common_test/test/common_test.cover new file mode 100644 index 0000000000..b6e5fa4de6 --- /dev/null +++ b/lib/common_test/test/common_test.cover @@ -0,0 +1,13 @@ +%% -*- erlang -*- +{incl_app,common_test,details}. +{excl_mods,common_test,[ct_config, + ct_gen_conn, + ct_event, + ct_logs, + ct_master, + ct_master_logs, + ct_master_status, + ct_run, + ct_slave, + ct_util, + vts]}. -- cgit v1.2.3 From 8179e41007922c059b8f9441f563edf694b8b315 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 12 Oct 2012 15:49:22 +0200 Subject: [cover] Add support for test_server OTP-10523 * Added cover:flush(Nodes), which will fetch data from remote nodes without stopping cover on those nodes. * Added cover:get_main_node(), which returns the node name of the main node. This is used by test_server to avoid {error,not_main_node} when a slave starts another slave (e.g. in test_server's own tests). --- lib/tools/doc/src/cover.xml | 15 ++++++++++++++- lib/tools/src/cover.erl | 31 +++++++++++++++++++++++++++++-- lib/tools/test/cover_SUITE.erl | 17 ++++++++++++++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml index 683acc025d..be0fd5bd47 100644 --- a/lib/tools/doc/src/cover.xml +++ b/lib/tools/doc/src/cover.xml @@ -5,7 +5,7 @@
2001 - 2011 + 2012 Ericsson AB, All Rights Reserved @@ -104,6 +104,8 @@ remove nodes. The same Cover compiled code will be loaded on each node, and analysis will collect and sum up coverage data results from all nodes.

+

To only collect data from remote nodes without stopping + cover on those nodes, use cover:flush/1

@@ -477,6 +479,17 @@ remote nodes is fetched and stored on the main node.

+ + flush(Nodes) -> ok | {error,not_main_node} + Collect cover data from remote nodes. + + Nodes = [atom()] + + +

Fetch data from the Cover database on the remote nodes and + stored on the main node.

+
+
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index e21bd1b88c..fb65206938 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2011. All Rights Reserved. +%% Copyright Ericsson AB 2001-2012. 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 @@ -81,8 +81,9 @@ export/1, export/2, import/1, modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1, reset/1, reset/0, + flush/1, stop/0, stop/1]). --export([remote_start/1]). +-export([remote_start/1,get_main_node/0]). %-export([bump/5]). -export([transform/4]). % for test purposes @@ -497,6 +498,19 @@ stop(Node) when is_atom(Node) -> stop(Nodes) -> call({stop,remove_myself(Nodes,[])}). +%% flush(Nodes) -> ok | {error,not_main_node} +%% Nodes = [Node] | Node +%% Node = atom() +%% Error = {not_cover_compiled,Module} +flush(Node) when is_atom(Node) -> + flush([Node]); +flush(Nodes) -> + call({flush,remove_myself(Nodes,[])}). + +%% Used by test_server only. Not documented. +get_main_node() -> + call(get_main_node). + %% bump(Module, Function, Arity, Clause, Line) %% Module = Function = atom() %% Arity = Clause = Line = integer() @@ -710,6 +724,11 @@ main_process_loop(State) -> State1 = State#main_state{nodes=State#main_state.nodes--Nodes}, main_process_loop(State1); + {From, {flush,Nodes}} -> + remote_collect('_',Nodes,false), + reply(From, ok), + main_process_loop(State); + {From, stop} -> lists:foreach( fun(Node) -> @@ -796,6 +815,10 @@ main_process_loop(State) -> State1 = State#main_state{nodes=State#main_state.nodes--[node(Pid)]}, main_process_loop(State1); + {From, get_main_node} -> + reply(From, node()), + main_process_loop(State); + get_status -> io:format("~p~n",[State]), main_process_loop(State) @@ -852,6 +875,10 @@ remote_process_loop(State) -> unregister(?SERVER), remote_reply(State#remote_state.main_node, ok); + {From, get_main_node} -> + reply(From, State#remote_state.main_node), + remote_process_loop(State); + get_status -> io:format("~p~n",[State]), remote_process_loop(State); diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index c2c708d806..950c4ddbcb 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -326,14 +326,16 @@ distribution(Config) when is_list(Config) -> ?line {ok,N1} = ?t:start_node(cover_SUITE_distribution1,slave,[]), ?line {ok,N2} = ?t:start_node(cover_SUITE_distribution2,slave,[]), ?line {ok,N3} = ?t:start_node(cover_SUITE_distribution3,slave,[]), + ?line {ok,N4} = ?t:start_node(cover_SUITE_distribution4,slave,[]), %% Check that an already compiled module is loaded on new nodes ?line {ok,f} = cover:compile(f), - ?line {ok,[_,_,_]} = cover:start(nodes()), + ?line {ok,[_,_,_,_]} = cover:start(nodes()), ?line cover_compiled = code:which(f), ?line cover_compiled = rpc:call(N1,code,which,[f]), ?line cover_compiled = rpc:call(N2,code,which,[f]), ?line cover_compiled = rpc:call(N3,code,which,[f]), + ?line cover_compiled = rpc:call(N4,code,which,[f]), %% Check that a node cannot be started twice ?line {ok,[]} = cover:start(N2), @@ -351,6 +353,7 @@ distribution(Config) when is_list(Config) -> ?line cover_compiled = rpc:call(N1,code,which,[v]), ?line cover_compiled = rpc:call(N2,code,which,[v]), ?line cover_compiled = rpc:call(N3,code,which,[v]), + ?line cover_compiled = rpc:call(N4,code,which,[v]), %% this is lost when the node is killed ?line rpc:call(N3,f,f2,[]), @@ -385,6 +388,18 @@ distribution(Config) when is_list(Config) -> %% reset on the remote node(s)) ?line check_f_calls(1,1), + %% Another checn that data is not fetched twice, i.e. when flushed + %% then analyse should not add the same data again. + ?line rpc:call(N4,f,f2,[]), + ?line ok = cover:flush(N4), + ?line check_f_calls(1,2), + + %% Check that flush collects data so calls are not lost if node is killed + ?line rpc:call(N4,f,f2,[]), + ?line ok = cover:flush(N4), + ?line rpc:call(N4,erlang,halt,[]), + ?line check_f_calls(1,3), + %% Check that stop() unloads on all nodes ?line ok = cover:stop(), ?line timer:sleep(100), %% Give nodes time to unload on slow machines. -- cgit v1.2.3 From deaae761fb03ba2a49ac6ed2459acf577698d57d Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 12 Oct 2012 15:58:42 +0200 Subject: [test_server] Don't stop cover after test is finished This commit removes all calls to cover:stop from test_server. This is to avoid crash in some processes due to old code. The correction shall be further refined for general use. There will, for instance, be an option to turn off cover:stop/0 after completed test run, so the default behaviour is backwards compatible. --- lib/test_server/src/test_server.erl | 28 ++++++++++++++++----------- lib/test_server/test/test_server_test_lib.erl | 4 ++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index bcffe896c4..fc8fe1a9ce 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -377,9 +377,7 @@ module_names(Beams) -> do_cover_compile(Modules) -> do_cover_compile1(lists:usort(Modules)). % remove duplicates -do_cover_compile1([Dont|Rest]) when Dont=:=cover; - Dont=:=test_server; - Dont=:=test_server_ctrl -> +do_cover_compile1([Dont|Rest]) when Dont=:=cover -> do_cover_compile1(Rest); do_cover_compile1([M|Rest]) -> case {code:is_sticky(M),code:is_loaded(M)} of @@ -483,9 +481,13 @@ cover_analyse(Analyse,Modules) -> {M,Err} end end, Modules), - Sticky = unstick_all_sticky(node()), - cover:stop(), - stick_all_sticky(node(),Sticky), + +%%! SIRI: This must be checked further - is it always ok not to stop +%%! cover? Will the node always terminate after this? Probably not - +%%! maybe use an option? +% Sticky = unstick_all_sticky(node()), +% cover:stop(), +% stick_all_sticky(node(),Sticky), R. pmap(Fun,List) -> @@ -503,6 +505,8 @@ pmap(Fun,List) -> end, Pids). unstick_all_sticky(Node) -> + unstick_all_sticky(node(),Node). +unstick_all_sticky(MainCoverNode,Node) -> lists:filter( fun(M) -> case code:is_sticky(M) of @@ -513,7 +517,7 @@ unstick_all_sticky(Node) -> false end end, - cover:modules()). + rpc:call(MainCoverNode,cover,modules,[])). stick_all_sticky(Node,Sticky) -> lists:foreach( @@ -2577,8 +2581,9 @@ start_node(Name, Type, Options) -> net_adm:ping(Node), case Cover of true -> - Sticky = unstick_all_sticky(Node), - cover:start(Node), + MainCoverNode = cover:get_main_node(), + Sticky = unstick_all_sticky(MainCoverNode,Node), + rpc:call(MainCoverNode,cover,start,[Node]), stick_all_sticky(Node,Sticky); _ -> ok @@ -2619,8 +2624,9 @@ stop_node(Slave) -> Nocover = is_shielded(Slave) orelse not same_version(Slave), case is_cover() of true when not Nocover -> - Sticky = unstick_all_sticky(Slave), - cover:stop(Slave), + MainCoverNode = cover:get_main_node(), + Sticky = unstick_all_sticky(MainCoverNode,Slave), + rpc:call(MainCoverNode,cover,flush,[Slave]), stick_all_sticky(Slave,Sticky); _ -> ok diff --git a/lib/test_server/test/test_server_test_lib.erl b/lib/test_server/test/test_server_test_lib.erl index 8a808c5360..0dabc17173 100644 --- a/lib/test_server/test/test_server_test_lib.erl +++ b/lib/test_server/test/test_server_test_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2009-2011. All Rights Reserved. +%% Copyright Ericsson AB 2009-2012. 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 @@ -83,7 +83,7 @@ start_slave(Config,_Level) -> post_end_per_testcase(_TC, Config, Return, State) -> Node = proplists:get_value(node, Config), - cover:stop(Node), + cover:flush(Node), slave:stop(Node), {Return, State}. -- cgit v1.2.3 From 76795e21ef68e2af29d22deee09a729a14f8a29a Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 12 Oct 2012 16:34:42 +0200 Subject: [common_test] Don't stop cover before stopping slave node In order to avoid crash of processes that hold old code, cover is no longer stopped before slave nodes are stopped. Instead cover:flush is used which only fetches cover data from the slave. This, along with similar changes in test_server allows all common_test modules to be included in cover analysis. --- lib/common_test/test/common_test.cover | 11 ----------- lib/common_test/test/ct_test_support.erl | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/common_test/test/common_test.cover b/lib/common_test/test/common_test.cover index b6e5fa4de6..e8e01a9312 100644 --- a/lib/common_test/test/common_test.cover +++ b/lib/common_test/test/common_test.cover @@ -1,13 +1,2 @@ %% -*- erlang -*- {incl_app,common_test,details}. -{excl_mods,common_test,[ct_config, - ct_gen_conn, - ct_event, - ct_logs, - ct_master, - ct_master_logs, - ct_master_status, - ct_run, - ct_slave, - ct_util, - vts]}. diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 80cca4a1cc..c05b6d87c0 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -117,7 +117,7 @@ end_per_suite(Config) -> CTNode = proplists:get_value(ct_node, Config), PrivDir = proplists:get_value(priv_dir, Config), true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]), - cover:stop(CTNode), + cover:flush(CTNode), slave:stop(CTNode), ok. @@ -149,7 +149,7 @@ end_per_testcase(_TestCase, Config) -> case wait_for_ct_stop(CTNode) of %% Common test was not stopped to we restart node. false -> - cover:stop(CTNode), + cover:flush(CTNode), slave:stop(CTNode), start_slave(Config,proplists:get_value(trace_level,Config)), {fail, "Could not stop common_test"}; -- cgit v1.2.3 From 747775d07dc889af675771cb19b9f16e6a44205c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 16 Oct 2012 15:32:36 +0200 Subject: [common_test] Start cover on slave nodes if running cover tests Nodes started with ct_slave should automatically run cover when tests are executed with cover analysis. Nodes must also be flushed for cover data before stopped. --- lib/common_test/src/ct_slave.erl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index aa3413fa89..6f7e83d852 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -1,7 +1,7 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010. All Rights Reserved. +%% Copyright Ericsson AB 2010-2012. 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 @@ -311,6 +311,13 @@ do_start(Host, Node, Options) -> StartupTimeout = Options#options.startup_timeout, Result = case wait_for_node_alive(ENode, BootTimeout) of pong-> + case test_server:is_cover() of + true -> + MainCoverNode = cover:get_main_node(), + rpc:call(MainCoverNode,cover,start,[ENode]); + false -> + ok + end, call_functions(ENode, Functions2), receive {node_started, ENode}-> @@ -423,6 +430,8 @@ wait_for_node_alive(Node, N) -> % call init:stop on a remote node do_stop(ENode) -> + MainCoverNode = cover:get_main_node(), + rpc:call(MainCoverNode,cover,flush,[ENode]), spawn(ENode, init, stop, []), wait_for_node_dead(ENode, 5). -- cgit v1.2.3 From fa43ac879358e52dd8143f85aa467857635573d5 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 12 Oct 2012 16:23:46 +0200 Subject: Skip epp_SUITE:otp_8911 if cover is running This is because this test case itself starts and stops cover. --- lib/stdlib/test/epp_SUITE.erl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index f79414db49..77c615d6d9 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2012. 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 @@ -1236,6 +1236,13 @@ otp_8911(doc) -> otp_8911(suite) -> []; otp_8911(Config) when is_list(Config) -> + case test_server:is_cover() of + true -> + {skip, "Testing cover, so can not run when cover is already running"}; + false -> + do_otp_8911(Config) + end. +do_otp_8911(Config) -> ?line {ok, CWD} = file:get_cwd(), ?line ok = file:set_cwd(?config(priv_dir, Config)), -- cgit v1.2.3 From ef96ce5e6b956513575f237a6f5f74fe2dbf9eb3 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 12 Oct 2012 16:06:45 +0200 Subject: [test_server] Include all test_server modules in code coverage analysis --- lib/test_server/test/test_server.cover | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/test_server/test/test_server.cover b/lib/test_server/test/test_server.cover index c16212567e..052415377d 100644 --- a/lib/test_server/test/test_server.cover +++ b/lib/test_server/test/test_server.cover @@ -1,21 +1 @@ {incl_app,test_server,details}. - -{excl_mods, test_server, [test_server, - test_server_ctrl, - ts_selftest]}. - -%% Using incl_mods list here because the test_server might not find -%% lib_dir for test_server - and so it will not find which modules to -%% compile. -{incl_mods, test_server, [erl2html2, - test_server_node, - test_server_sup, - ts, - ts_autoconf_win32, - ts_erl_config, - ts_install, - ts_lib, - ts_make, - ts_run - ]}. - -- cgit v1.2.3 From e68de4fee8e23e8f465db84b51a108eba80dc808 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 15 Oct 2012 15:14:48 +0200 Subject: Include all stdlib modules in code coverage analysis --- lib/stdlib/test/stdlib.cover | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/stdlib/test/stdlib.cover b/lib/stdlib/test/stdlib.cover index 61f4f064b9..e71be880cb 100644 --- a/lib/stdlib/test/stdlib.cover +++ b/lib/stdlib/test/stdlib.cover @@ -1,17 +1,2 @@ %% -*- erlang -*- {incl_app,stdlib,details}. - -{excl_mods,stdlib, - [erl_parse, - erl_eval, - ets, - filename, - gen_event, - gen_server, - gen, - lists, - io, - io_lib, - io_lib_format, - io_lib_pretty, - proc_lib]}. -- cgit v1.2.3 From 2794f452c8197a9c242c7e3652481f4f4d6e3422 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 16 Oct 2012 18:28:30 +0200 Subject: [test_server] Multiply timers with timetrap_scale_factor when starting nodes In test_server_ctrl:start_node and wait_for_node, the timer in controller_call was not multiplied by the timetrap_scale_factor. This caused unexpected behaviour since the scale factor was used furhter down the the code, and timeout would happen in unexpected order when cover was running. --- lib/test_server/src/test_server_ctrl.erl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 7f04e2eb23..e0f66ddb54 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -5119,7 +5119,7 @@ get_target_info() -> %% Called by test_server. See test_server:start_node/3 for details start_node(Name, Type, Options) -> - T = 10 * ?ACCEPT_TIMEOUT, % give some extra time + T = 10 * ?ACCEPT_TIMEOUT * test_server:timetrap_scale_factor(), format(minor, "Attempt to start ~w node ~p with options ~p", [Type, Name, Options]), case controller_call({start_node,Name,Type,Options}, T) of @@ -5164,7 +5164,8 @@ start_node(Name, Type, Options) -> %% when the new node has contacted test_server_ctrl again wait_for_node(Slave) -> - case catch controller_call({wait_for_node,Slave},10000) of + T = 10000 * test_server:timetrap_scale_factor(), + case catch controller_call({wait_for_node,Slave},T) of {'EXIT',{timeout,_}} -> {error,timeout}; ok -> ok end. -- cgit v1.2.3 From f617b5b1f310dd37d606f3648246cc0851e12bef Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 17 Oct 2012 13:54:53 +0200 Subject: [test_server] Start cover in test_server:wait_for_node Without this change, there is no support in test_server for starting cover on a node started with option {wait,false}. --- lib/test_server/src/test_server.erl | 25 +++++++++++++++++++++- .../test_server_SUITE_data/test_server_SUITE.erl | 14 ++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index fc8fe1a9ce..330c4cc8a9 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -2612,7 +2612,30 @@ wait_for_node(Slave) -> group_leader() ! {sync_apply, self(), {test_server_ctrl,wait_for_node,[Slave]}}, - receive {sync_result,R} -> R end. + Result = receive {sync_result,R} -> R end, + case Result of + ok -> + Cover = case is_cover() of + true -> + not is_shielded(Slave) andalso same_version(Slave); + false -> + false + end, + + net_adm:ping(Slave), + case Cover of + true -> + MainCoverNode = cover:get_main_node(), + Sticky = unstick_all_sticky(MainCoverNode,Slave), + rpc:call(MainCoverNode,cover,start,[Slave]), + stick_all_sticky(Slave,Sticky); + _ -> + ok + end; + _ -> + ok + end, + Result. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl index ab25e4ad2f..fc2adcd651 100644 --- a/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl +++ b/lib/test_server/test/test_server_SUITE_data/test_server_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2011. All Rights Reserved. +%% Copyright Ericsson AB 1997-2012. 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 @@ -433,7 +433,7 @@ start_stop_node(Config) when is_list(Config) -> ?t:comment("WARNING: Node started with {wait,false}" " is up faster than expected..."); false -> - wait_for_node(Node4,0), + test_server:wait_for_node(Node4), true = lists:member(Node4,nodes()) end, @@ -450,16 +450,6 @@ start_stop_node(Config) when is_list(Config) -> ok. - -wait_for_node(Node,Acc) -> - case net_adm:ping(Node) of - pang -> - timer:sleep(100), - wait_for_node(Node,Acc+100); - pong -> - Acc - end. - cleanup_nodes_init(doc) -> ["Test that nodes are terminated when test case" " is finished unless {cleanup,false} is given."]; cleanup_nodes_init(Config) when is_list(Config) -> -- cgit v1.2.3 From 0c924848a3f7bae6fe3d4f2b5d4d47277e45665b Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 17 Oct 2012 16:56:34 +0200 Subject: [test_server] Allow cross cover analysis when testing through common_test The cross cover analysis functionality in test_server was broken since OTP converted to running daily tests through common_test. This has been corrected. --- lib/common_test/test/common_test.cover | 8 ++ lib/test_server/src/test_server_ctrl.erl | 136 +++++++++++++++---------------- lib/test_server/src/ts.erl | 56 ++++++++++++- 3 files changed, 130 insertions(+), 70 deletions(-) diff --git a/lib/common_test/test/common_test.cover b/lib/common_test/test/common_test.cover index e8e01a9312..66697854ea 100644 --- a/lib/common_test/test/common_test.cover +++ b/lib/common_test/test/common_test.cover @@ -1,2 +1,10 @@ %% -*- erlang -*- {incl_app,common_test,details}. +{cross_apps,common_test,[erl2html2, + test_server, + test_server_ctrl, + test_server_gl, + test_server_h, + test_server_io, + test_server_node, + test_server_sup]}. diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index e0f66ddb54..08761988bc 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -166,7 +166,7 @@ -export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]). -export([create_priv_dir/1]). -export([cover/2, cover/3, cover/7, - cross_cover_analyse/1, cross_cover_analyse/2, trc/1, stop_trace/0]). + cross_cover_analyse/2, cross_cover_analyse/3, trc/1, stop_trace/0]). -export([testcase_callback/1]). -export([set_random_seed/1]). -export([kill_slavenodes/0]). @@ -5513,15 +5513,20 @@ cover_analyse(Analyse, AnalyseMods) -> %% Cover analysis, cross application %% This can be executed on any node after all tests are finished. -%% The node's current directory must be the same as when the tests -%% were run. -cross_cover_analyse(Analyse) -> - cross_cover_analyse(Analyse, undefined). - -cross_cover_analyse(Analyse, CrossModules) -> - CoverdataFiles = get_coverdata_files(), +%% Apps = [{App,Dir}] +%% App = atom(), application name +%% Dir = string(), the log directory for App, normally where +%% run. is found. +%% Modules = [atom()], modules that have been cover compiled during tests +%% of other apps than the one they belong to. +cross_cover_analyse(Analyse, Apps) -> + cross_cover_analyse(Analyse, Apps, get_cross_modules()). +cross_cover_analyse(Analyse, Apps, Modules) -> + Apps1 = get_latest_run_dirs(Apps), + Apps2 = add_cross_modules(Modules,Apps1), + CoverdataFiles = get_coverdata_files(Apps2), lists:foreach(fun(CDF) -> cover:import(CDF) end, CoverdataFiles), - io:fwrite("Cover analysing... ", []), + io:fwrite("Cover analysing...\n", []), DetailsFun = case Analyse of details -> @@ -5535,25 +5540,15 @@ cross_cover_analyse(Analyse, CrossModules) -> _ -> fun(_,_) -> undefined end end, - SortedModules = - case CrossModules of - undefined -> - sort_modules([Mod || Mod <- get_all_cross_modules(), - lists:member(Mod, cover:imported_modules())], []); - _ -> - sort_modules(CrossModules, []) - end, - Coverage = analyse_apps(SortedModules, DetailsFun, []), + Coverage = analyse_apps(Apps2, DetailsFun, []), cover:stop(), - write_cross_cover_logs(Coverage). + write_cross_cover_logs(Coverage,Apps2). -%% For each application from which there are modules listed in the -%% cross.cover, write a cross cover log (cross_cover.html). -write_cross_cover_logs([{App,Coverage}|T]) -> - case last_test_for_app(App) of - false -> - ok; - Dir -> +%% For each application from which there are cross cover analysed +%% modules, write a cross cover log (cross_cover.html). +write_cross_cover_logs([{App,Coverage}|T],Apps) -> + case lists:keyfind(App,1,Apps) of + {_,Dir,Mods} when Mods=/=[] -> CoverLogName = filename:join(Dir,?cross_coverlog_name), {ok,CoverLog} = file:open(CoverLogName, [write]), write_coverlog_header(CoverLog), @@ -5561,54 +5556,51 @@ write_cross_cover_logs([{App,Coverage}|T]) -> "

Coverage results for \'~w\' from all tests

\n", [App]), write_cover_result_table(CoverLog, Coverage), - io:fwrite("Written file ~p\n", [CoverLogName]) + io:fwrite("Written file ~p\n", [CoverLogName]); + _ -> + ok end, - write_cross_cover_logs(T); -write_cross_cover_logs([]) -> + write_cross_cover_logs(T,Apps); +write_cross_cover_logs([],_) -> io:fwrite("done\n", []). -%% Find all exported coverdata files. First find all the latest -%% run. directories, and the check if there is a file named -%% all.coverdata. -get_coverdata_files() -> - PossibleFiles = [last_coverdata_file(Dir) || - Dir <- filelib:wildcard([$*|?logdir_ext]), - filelib:is_dir(Dir)], - [File || File <- PossibleFiles, filelib:is_file(File)]. - -last_coverdata_file(Dir) -> - LastDir = last_test(filelib:wildcard(filename:join(Dir,"run.[1-2]*")),false), - filename:join(LastDir,"all.coverdata"). - - -%% Find the latest run. directory for the given application. -last_test_for_app(App) -> - AppLogDir = atom_to_list(App)++?logdir_ext, - last_test(filelib:wildcard(filename:join(AppLogDir,"run.[1-2]*")),false). - -last_test([Run|Rest], false) -> - last_test(Rest, Run); -last_test([Run|Rest], Latest) when Run > Latest -> - last_test(Rest, Run); -last_test([_|Rest], Latest) -> - last_test(Rest, Latest); -last_test([], Latest) -> +%% Get the latest run. directories +get_latest_run_dirs([{App,Dir}|Apps]) -> + [{App,get_latest_run_dir(Dir)} | get_latest_run_dirs(Apps)]; +get_latest_run_dirs([]) -> + []. + +get_latest_run_dir(Dir) -> + case filelib:wildcard(filename:join(Dir,"run.[1-2]*")) of + [] -> + Dir; + [H|T] -> + get_latest_dir(T,H) + end. + +get_latest_dir([H|T],Latest) when H>Latest -> + get_latest_dir(T,H); +get_latest_dir([_|T],Latest) -> + get_latest_dir(T,Latest); +get_latest_dir([],Latest) -> Latest. -%% Sort modules according to the application they belong to. -%% Return [{App,LastTestDir,ModuleList}] -sort_modules([M|Modules], Acc) -> - App = get_app(M), - Acc1 = - case lists:keysearch(App, 1, Acc) of - {value,{App,LastTest,List}} -> - lists:keyreplace(App, 1, Acc, {App,LastTest,[M|List]}); +%% Associate the cross cover modules with their applications. +add_cross_modules(Mods,Apps)-> + do_add_cross_modules(Mods,[{App,Dir,[]} || {App,Dir} <- Apps]). +do_add_cross_modules([Mod|Mods],Apps)-> + App = get_app(Mod), + NewApps = + case lists:keytake(App,1,Apps) of + {value,{App,Dir,AppMods},Rest} -> + [{App,Dir,lists:umerge([Mod],AppMods)}|Rest]; false -> - [{App,last_test_for_app(App),[M]}|Acc] + Apps end, - sort_modules(Modules, Acc1); -sort_modules([], Acc) -> - Acc. + do_add_cross_modules(Mods,NewApps); +do_add_cross_modules([],Apps) -> + %% Just to get the modules in the same order as app-only cover log + [{App,Dir,lists:reverse(Mods)} || {App,Dir,Mods} <- Apps]. get_app(Module) -> Beam = code:which(Module), @@ -5616,6 +5608,14 @@ get_app(Module) -> [AppStr|_] = string:tokens(AppDir,"-"), list_to_atom(AppStr). +%% Find all exported coverdata files. +get_coverdata_files(Apps) -> + lists:flatmap( + fun({_,LatestAppDir,_}) -> + filelib:wildcard(filename:join(LatestAppDir,"all.coverdata")) + end, + Apps). + %% For each application, analyse all modules %% Used for cross cover analysis. @@ -5636,7 +5636,7 @@ analyse_modules(_Dir, [], _DetailsFun, Acc) -> %% Read the cross cover file (cross.cover) -get_all_cross_modules() -> +get_cross_modules() -> get_cross_modules(all). get_cross_modules(App) -> case file:consult(?cross_cover_file) of diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl index db16b6ecd2..1b2bd4296a 100644 --- a/lib/test_server/src/ts.erl +++ b/lib/test_server/src/ts.erl @@ -498,8 +498,60 @@ estone(Opts) when is_list(Opts) -> run(emulator,estone_SUITE,Opts). cross_cover_analyse([Level]) -> cross_cover_analyse(Level); cross_cover_analyse(Level) -> - test_server_ctrl:cross_cover_analyse(Level). - + Apps = get_last_app_tests(), + Modules = get_cross_modules(Apps,[]), + test_server_ctrl:cross_cover_analyse(Level,Apps,Modules). + +get_last_app_tests() -> + AllTests = filelib:wildcard(filename:join(["*","*_test.logs"])), + {ok,RE} = re:compile("^[^/]*/[^\.]*\.(.*)_test\.logs$"), + get_last_app_tests(AllTests,RE,[]). + +get_last_app_tests([Dir|Dirs],RE,Acc) -> + NewAcc = + case re:run(Dir,RE,[{capture,all,list}]) of + {match,[Dir,AppStr]} -> + App = list_to_atom(AppStr), + case lists:keytake(App,1,Acc) of + {value,{App,LastDir},Rest} -> + if Dir > LastDir -> + [{App,Dir}|Rest]; + true -> + Acc + end; + false -> + [{App,Dir} | Acc] + end; + _ -> + Acc + end, + get_last_app_tests(Dirs,RE,NewAcc); +get_last_app_tests([],_,Acc) -> + Acc. + +get_cross_modules([{App,_}|Apps],Acc) -> + Mods = cross_modules(App), + get_cross_modules(Apps,lists:umerge(Mods,Acc)); +get_cross_modules([],Acc) -> + Acc. + +cross_modules(App) -> + case default_coverfile(App) of + none -> + []; + File -> + case catch file:consult(File) of + {ok,CoverSpec} -> + case lists:keyfind(cross_apps,1,CoverSpec) of + false -> + []; + {cross_apps,App,Modules} -> + lists:usort(Modules) + end; + _ -> + [] + end + end. %%% Implementation. -- cgit v1.2.3 From f6c1c3127f027cd717d01613cd356c5ada35041c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 22 Oct 2012 16:06:32 +0200 Subject: [common_test] Add option cover_stop By default, test_server will always stop cover (and thus load back the non cover compiled original beam files) after a test with code coverage analysis is completed. The new option allows the common_test user to specify that cover shall not be stopped. This can be useful if there are processes still running old code, i.e. processes that have not done any fully qualified function call after the cover compilation, since loading the original code then will kill those processes. This is only recommended if the erlang node is to be stopped after the test run, or if cover can be manually stopped. --- lib/common_test/doc/src/cover_chapter.xml | 27 +++++++++++++ lib/common_test/doc/src/ct_run.xml | 2 + lib/common_test/doc/src/run_test_chapter.xml | 5 +++ lib/common_test/src/ct.erl | 2 +- lib/common_test/src/ct_master.erl | 2 +- lib/common_test/src/ct_run.erl | 44 +++++++++++++++------ lib/common_test/src/ct_testspec.erl | 4 ++ lib/common_test/src/ct_util.hrl | 1 + lib/test_server/src/test_server.erl | 57 ++++++++++++++++++---------- lib/test_server/src/test_server_ctrl.erl | 34 ++++++++--------- lib/test_server/src/ts_run.erl | 2 +- 11 files changed, 127 insertions(+), 53 deletions(-) diff --git a/lib/common_test/doc/src/cover_chapter.xml b/lib/common_test/doc/src/cover_chapter.xml index 803a71de07..b2e64bfff0 100644 --- a/lib/common_test/doc/src/cover_chapter.xml +++ b/lib/common_test/doc/src/cover_chapter.xml @@ -108,6 +108,33 @@ specifications).

+
+ + Stopping the cover tool when tests are completed +

By default the Cover tool is automatically stopped when the + tests are completed. This causes the original (non cover + compiled) modules to be loaded back in to the test node. If a + process at this point is still running old code of any of the + modules that are cover compiled, meaning that it has not done + any fully qualified function call after the cover compilation, + the process will now be killed. To avoid this it is possible to + set the value of the cover_stop option to + false. This means that the modules will stay cover + compiled, and it is therefore only recommended if the erlang + node(s) under test is terminated after the test is completed + or if cover can be manually stopped.

+ +

The option can be set by using the -cover_stop flag with + ct_run, by adding {cover_stop,true|false} to the + Opts argument to ct:run_test/1, or by adding + a cover_stop term in your test specification (see chapter + about test + specifications).

+ +
+
The cover specification file

These are the terms allowed in a cover specification file:

diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml index 9cc5495af7..da18640df7 100644 --- a/lib/common_test/doc/src/ct_run.xml +++ b/lib/common_test/doc/src/ct_run.xml @@ -104,6 +104,7 @@ [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]] [-stylesheet CSSFile] [-cover CoverCfgFile] + [-cover_stop Bool] [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] | [-event_handler_init EvHandler1 InitArg1 and EvHandler2 InitArg2 and .. EvHandlerN InitArgN] @@ -138,6 +139,7 @@ [-silent_connections [ConnType1 ConnType2 .. ConnTypeN]] [-stylesheet CSSFile] [-cover CoverCfgFile] + [-cover_stop Bool] [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] | [-event_handler_init EvHandler1 InitArg1 and EvHandler2 InitArg2 and .. EvHandlerN InitArgN] diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index ea62df27cc..b5b914d506 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -153,6 +153,8 @@ ]]>, points out a user HTML style sheet (see below). ]]>, to perform code coverage test (see Code Coverage Analysis). + ]]>, to specify if the cover tool shall be stopped after the test is completed (see + Code Coverage Analysis). ]]>, to install event handlers. ]]>, to install @@ -495,6 +497,9 @@ {cover, CoverSpecFile}. {cover, NodeRefs, CoverSpecFile}. + {cover_stop, Bool}. + {cover_stop, NodeRefs, Bool}. + {include, IncludeDirs}. {include, NodeRefs, IncludeDirs}. diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 5014309c0f..90e4e79ccf 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -148,7 +148,7 @@ run(TestDirs) -> %%% {config,CfgFiles} | {userconfig, UserConfig} | %%% {allow_user_terms,Bool} | {logdir,LogDir} | %%% {silent_connections,Conns} | {stylesheet,CSSFile} | -%%% {cover,CoverSpecFile} | {step,StepOpts} | +%%% {cover,CoverSpecFile} | {cover_stop,Bool} | {step,StepOpts} | %%% {event_handler,EventHandlers} | {include,InclDirs} | %%% {auto_compile,Bool} | {create_priv_dir,CreatePrivDir} | %%% {multiply_timetraps,M} | {scale_timetraps,Bool} | diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index 99bec3ea09..f29eba605c 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -51,7 +51,7 @@ %%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} | %%% {logdir,LogDir} | {event_handler,EventHandlers} | %%% {silent_connections,Conns} | {cover,CoverSpecFile} | -%%% {userconfig, UserCfgFiles} +%%% {cover_stop,Bool} | {userconfig, UserCfgFiles} %%% CfgFiles = string() | [string()] %%% TestDirs = string() | [string()] %%% Suites = atom() | [atom()] diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 4a6a3cdcac..ed7778c25a 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -58,6 +58,7 @@ vts, shell, cover, + cover_stop, coverspec, step, logdir, @@ -245,6 +246,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), + CoverStop = get_start_opt(cover_stop, fun([CS]) -> list_to_atom(CS) end, Args), LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, Args), LogOpts = get_start_opt(logopts, fun(Os) -> [list_to_atom(O) || O <- Os] end, [], Args), @@ -329,7 +331,8 @@ script_start1(Parent, Args) -> end, StartOpts = #opts{label = Label, profile = Profile, - vts = Vts, shell = Shell, cover = Cover, + vts = Vts, shell = Shell, + cover = Cover, cover_stop = CoverStop, logdir = LogDir, logopts = LogOpts, basic_html = BasicHtml, verbosity = Verbosity, @@ -416,6 +419,9 @@ script_start2(StartOpts = #opts{vts = undefined, Cover = choose_val(StartOpts#opts.cover, SpecStartOpts#opts.cover), + CoverStop = + choose_val(StartOpts#opts.cover_stop, + SpecStartOpts#opts.cover_stop), MultTT = choose_val(StartOpts#opts.multiply_timetraps, SpecStartOpts#opts.multiply_timetraps), @@ -475,6 +481,7 @@ script_start2(StartOpts = #opts{vts = undefined, profile = Profile, testspecs = Specs, cover = Cover, + cover_stop = CoverStop, logdir = LogDir, logopts = AllLogOpts, basic_html = BasicHtml, @@ -723,6 +730,7 @@ script_usage() -> "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" + "\n\t[-cover_stop Bool]" "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" "\n\t[-include InclDir1 InclDir2 .. InclDirN]" @@ -745,6 +753,7 @@ script_usage() -> "\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]" "\n\t[-stylesheet CSSFile]" "\n\t[-cover CoverCfgFile]" + "\n\t[-cover_stop Bool]" "\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]" "\n\t[-ct_hooks CTHook1 CTHook2 .. CTHookN]" "\n\t[-include InclDir1 InclDir2 .. InclDirN]" @@ -938,6 +947,7 @@ run_test2(StartOpts) -> %% code coverage Cover = get_start_opt(cover, fun(CoverFile) -> ?abs(CoverFile) end, StartOpts), + CoverStop = get_start_opt(cover_stop, value, StartOpts), %% timetrap manipulation MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts), @@ -1000,7 +1010,8 @@ run_test2(StartOpts) -> Step = get_start_opt(step, value, StartOpts), Opts = #opts{label = Label, profile = Profile, - cover = Cover, step = Step, logdir = LogDir, + cover = Cover, cover_stop = CoverStop, + step = Step, logdir = LogDir, logopts = LogOpts, basic_html = BasicHtml, config = CfgFiles, verbosity = Verbosity, @@ -1063,6 +1074,8 @@ run_spec_file(Relaxed, AllConfig = merge_vals([CfgFiles, SpecOpts#opts.config]), Cover = choose_val(Opts#opts.cover, SpecOpts#opts.cover), + CoverStop = choose_val(Opts#opts.cover_stop, + SpecOpts#opts.cover_stop), MultTT = choose_val(Opts#opts.multiply_timetraps, SpecOpts#opts.multiply_timetraps), ScaleTT = choose_val(Opts#opts.scale_timetraps, @@ -1103,6 +1116,7 @@ run_spec_file(Relaxed, Opts1 = Opts#opts{label = Label, profile = Profile, cover = Cover, + cover_stop = CoverStop, logdir = which(logdir, LogDir), logopts = AllLogOpts, stylesheet = Stylesheet, @@ -1374,6 +1388,7 @@ get_data_for_node(#testspec{label = Labels, verbosity = VLvls, silent_connections = SilentConnsList, cover = CoverFs, + cover_stop = CoverStops, config = Cfgs, userconfig = UsrCfgs, event_handler = EvHs, @@ -1405,6 +1420,7 @@ get_data_for_node(#testspec{label = Labels, SCs -> SCs end, Cover = proplists:get_value(Node, CoverFs), + CoverStop = proplists:get_value(Node, CoverStops), MT = proplists:get_value(Node, MTs), ST = proplists:get_value(Node, STs), CreatePrivDir = proplists:get_value(Node, PDs), @@ -1423,6 +1439,7 @@ get_data_for_node(#testspec{label = Labels, verbosity = Verbosity, silent_connections = SilentConns, cover = Cover, + cover_stop = CoverStop, config = ConfigFiles, event_handlers = EvHandlers, ct_hooks = FiltCTHooks, @@ -1576,14 +1593,7 @@ do_run(Tests, Misc, LogDir, LogOpts) when is_list(Misc), 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{logdir = LogDir}, []); + do_run(Tests, [], Opts#opts{logdir = LogDir}, []); do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) -> #opts{label = Label, profile = Profile, cover = Cover, @@ -1617,7 +1627,13 @@ do_run(Tests, Skip, Opts, Args) when is_record(Opts, opts) -> {error,Reason} -> exit({error,Reason}); CoverSpec -> - Opts#opts{coverspec = CoverSpec} + CoverStop = + case Opts#opts.cover_stop of + undefined -> true; + Stop -> Stop + end, + Opts#opts{coverspec = CoverSpec, + cover_stop = CoverStop} end end, %% This env variable is used by test_server to determine @@ -2120,7 +2136,8 @@ do_run_test(Tests, Skip, Opts) -> %% tell test_server which modules should be cover compiled %% note that actual compilation is done when tests start test_server_ctrl:cover(CovApp, CovFile, CovExcl, CovIncl, - CovCross, CovExport, CovLevel), + CovCross, CovExport, CovLevel, + Opts#opts.cover_stop), %% save cover data (used e.g. to add nodes dynamically) ct_util:set_testdata({cover,CovData}), %% start cover on specified nodes @@ -2584,6 +2601,9 @@ merge_arguments([LogDir={logdir,_}|Args], Merged) -> merge_arguments([CoverFile={cover,_}|Args], Merged) -> merge_arguments(Args, handle_arg(replace, CoverFile, Merged)); +merge_arguments([CoverStop={cover_stop,_}|Args], Merged) -> + merge_arguments(Args, handle_arg(replace, CoverStop, Merged)); + merge_arguments([{'case',TC}|Args], Merged) -> merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged)); diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index a8b67d0329..0221b87d49 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -903,6 +903,8 @@ handle_data(logdir,Node,Dir,Spec) -> [{Node,ref2dir(Dir,Spec)}]; handle_data(cover,Node,File,Spec) -> [{Node,get_absfile(File,Spec)}]; +handle_data(cover_stop,Node,Stop,_Spec) -> + [{Node,Stop}]; handle_data(include,Node,Dirs=[D|_],Spec) when is_list(D) -> [{Node,ref2dir(Dir,Spec)} || Dir <- Dirs]; handle_data(include,Node,Dir=[Ch|_],Spec) when is_integer(Ch) -> @@ -1258,6 +1260,8 @@ valid_terms() -> {node,3}, {cover,2}, {cover,3}, + {cover_stop,2}, + {cover_stop,3}, {config,2}, {config,3}, {config,4}, diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 196b5e46d0..c9c6514fa4 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -38,6 +38,7 @@ verbosity=[], silent_connections=[], cover=[], + cover_stop=[], config=[], userconfig=[], event_handler=[], diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 330c4cc8a9..2b15d07990 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -25,7 +25,7 @@ %%% TEST_SERVER_CTRL INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -export([run_test_case_apply/1,init_target_info/0,init_purify/0]). --export([cover_compile/1,cover_analyse/2]). +-export([cover_compile/1,cover_analyse/3]). %%% TEST_SERVER_SUP INTERFACE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -export([get_loc/1]). @@ -414,7 +414,7 @@ do_cover_compile1([]) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% cover_analyse(Analyse,Modules) -> [{M,{Cov,NotCov,Details}}] +%% cover_analyse(Analyse,Modules,Stop) -> [{M,{Cov,NotCov,Details}}] %% %% Analyse = {details,Dir} | details | {overview,void()} | overview %% Modules = [atom()], the modules to analyse @@ -430,7 +430,18 @@ do_cover_compile1([]) -> %% %% Also, if a Dir exists, cover data will be exported to a file called %% all.coverdata in that directory. -cover_analyse(Analyse,Modules) -> +%% +%% Finally, if Stop==true, then cover will be stopped after the +%% analysis is completed. Stopping cover causes the original (non +%% cover compiled) modules to be loaded back in. If a process at this +%% point is still running old code of any of the cover compiled +%% modules, meaning that is has not done any fully qualified function +%% call after the cover compilation, the process will now be +%% killed. To avoid this scenario, it is possible to set Stop=false, +%% which means that the modules will stay cover compiled. Note that +%% this is only recommended if the erlang node is being terminated +%% after the test is completed. +cover_analyse(Analyse,Modules,Stop) -> io:fwrite("Cover analysing...\n",[]), DetailsFun = case Analyse of @@ -482,12 +493,14 @@ cover_analyse(Analyse,Modules) -> end end, Modules), -%%! SIRI: This must be checked further - is it always ok not to stop -%%! cover? Will the node always terminate after this? Probably not - -%%! maybe use an option? -% Sticky = unstick_all_sticky(node()), -% cover:stop(), -% stick_all_sticky(node(),Sticky), + case Stop of + true -> + Sticky = unstick_all_sticky(node()), + cover:stop(), + stick_all_sticky(node(),Sticky); + false -> + ok + end, R. pmap(Fun,List) -> @@ -504,6 +517,17 @@ pmap(Fun,List) -> end end, Pids). + +do_cover_for_node(Node,CoverFunc) -> + %% In case a slave node is starting another slave node! I.e. this + %% function is executed on a slave node - then the cover function + %% must be executed on the master node. This is for instance the + %% case in test_server's own tests. + MainCoverNode = cover:get_main_node(), + Sticky = unstick_all_sticky(MainCoverNode,Node), + rpc:call(MainCoverNode,cover,CoverFunc,[Node]), + stick_all_sticky(Node,Sticky). + unstick_all_sticky(Node) -> unstick_all_sticky(node(),Node). unstick_all_sticky(MainCoverNode,Node) -> @@ -2581,10 +2605,7 @@ start_node(Name, Type, Options) -> net_adm:ping(Node), case Cover of true -> - MainCoverNode = cover:get_main_node(), - Sticky = unstick_all_sticky(MainCoverNode,Node), - rpc:call(MainCoverNode,cover,start,[Node]), - stick_all_sticky(Node,Sticky); + do_cover_for_node(Node,start); _ -> ok end, @@ -2625,10 +2646,7 @@ wait_for_node(Slave) -> net_adm:ping(Slave), case Cover of true -> - MainCoverNode = cover:get_main_node(), - Sticky = unstick_all_sticky(MainCoverNode,Slave), - rpc:call(MainCoverNode,cover,start,[Slave]), - stick_all_sticky(Slave,Sticky); + do_cover_for_node(Slave,start); _ -> ok end; @@ -2647,10 +2665,7 @@ stop_node(Slave) -> Nocover = is_shielded(Slave) orelse not same_version(Slave), case is_cover() of true when not Nocover -> - MainCoverNode = cover:get_main_node(), - Sticky = unstick_all_sticky(MainCoverNode,Slave), - rpc:call(MainCoverNode,cover,flush,[Slave]), - stick_all_sticky(Slave,Sticky); + do_cover_for_node(Slave,flush); _ -> ok end, diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index 08761988bc..50208410a9 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -165,7 +165,7 @@ -export([reject_io_reqs/1, get_levels/0, set_levels/3]). -export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]). -export([create_priv_dir/1]). --export([cover/2, cover/3, cover/7, +-export([cover/2, cover/3, cover/8, cross_cover_analyse/2, cross_cover_analyse/3, trc/1, stop_trace/0]). -export([testcase_callback/1]). -export([set_random_seed/1]). @@ -521,9 +521,9 @@ cover(App, Analyse) when is_atom(App) -> cover(CoverFile, Analyse) -> cover(none, CoverFile, Analyse). cover(App, CoverFile, Analyse) -> - controller_call({cover,{App,CoverFile},Analyse}). -cover(App, CoverFile, Exclude, Include, Cross, Export, Analyse) -> - controller_call({cover,{App,{CoverFile,Exclude,Include,Cross,Export}},Analyse}). + controller_call({cover,{App,CoverFile},Analyse,true}). +cover(App, CoverFile, Exclude, Include, Cross, Export, Analyse, Stop) -> + controller_call({cover,{App,{CoverFile,Exclude,Include,Cross,Export}},Analyse,Stop}). testcase_callback(ModFunc) -> controller_call({testcase_callback,ModFunc}). @@ -796,7 +796,7 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) -> ExtraTools = case State#state.cover of false -> []; - {App,Analyse} -> [{cover,App,Analyse}] + {App,Analyse,Stop} -> [{cover,App,Analyse,Stop}] end, ExtraTools1 = case State#state.random_seed of @@ -1052,13 +1052,13 @@ handle_call(stop_trace, _From, State) -> {reply,R,State#state{trc=false}}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% handle_call({cover,App,Analyse}, _, State) -> ok | {error,Reason} +%% handle_call({cover,App,Analyse,Stop}, _, State) -> ok | {error,Reason} %% %% All modules inn application App are cover compiled %% Analyse indicates on which level the coverage should be analysed -handle_call({cover,App,Analyse}, _From, State) -> - {reply,ok,State#state{cover={App,Analyse}}}; +handle_call({cover,App,Analyse,Stop}, _From, State) -> + {reply,ok,State#state{cover={App,Analyse,Stop}}}; %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% handle_call({create_priv_dir,Value}, _, State) -> ok | {error,Reason} @@ -1465,11 +1465,11 @@ elapsed_time(Before, After) -> start_extra_tools(ExtraTools) -> start_extra_tools(ExtraTools, []). -start_extra_tools([{cover,App,Analyse} | ExtraTools], Started) -> +start_extra_tools([{cover,App,Analyse,Stop} | ExtraTools], Started) -> case cover_compile(App) of {ok,AnalyseMods} -> start_extra_tools(ExtraTools, - [{cover,App,Analyse,AnalyseMods}|Started]); + [{cover,App,Analyse,AnalyseMods,Stop}|Started]); {error,_} -> start_extra_tools(ExtraTools, Started) end; @@ -1488,8 +1488,8 @@ stop_extra_tools(ExtraTools) -> end, stop_extra_tools(ExtraTools, TestDir). -stop_extra_tools([{cover,App,Analyse,AnalyseMods}|ExtraTools], TestDir) -> - cover_analyse(App, Analyse, AnalyseMods, TestDir), +stop_extra_tools([{cover,App,Analyse,AnalyseMods,Stop}|ExtraTools], TestDir) -> + cover_analyse(App, Analyse, AnalyseMods, Stop, TestDir), stop_extra_tools(ExtraTools, TestDir); %%stop_extra_tools([_ | ExtraTools], TestDir) -> %% stop_extra_tools(ExtraTools, TestDir); @@ -5448,7 +5448,7 @@ check_cover_file([], Exclude, Include) -> %% %% This per application analysis writes the file cover.html in the %% application's run. directory. -cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) -> +cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, Stop, TestDir) -> write_default_cross_coverlog(TestDir), {ok,CoverLog} = file:open(filename:join(TestDir, ?coverlog_name), [write]), @@ -5479,7 +5479,7 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) -> io:fwrite(CoverLog, "

Excluded module(s): ~p\n", [Excluded]), - Coverage = cover_analyse(Analyse, AnalyseMods), + Coverage = cover_analyse(Analyse, AnalyseMods, Stop), case lists:filter(fun({_M,{_,_,_}}) -> false; (_) -> true @@ -5496,17 +5496,17 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) -> file:write_file(filename:join(TestDir, ?cover_total), term_to_binary(TotPercent)). -cover_analyse(Analyse, AnalyseMods) -> +cover_analyse(Analyse, AnalyseMods, Stop) -> TestDir = get(test_server_log_dir_base), case get(test_server_ctrl_job_sock) of undefined -> %% local target - test_server:cover_analyse({Analyse,TestDir}, AnalyseMods); + test_server:cover_analyse({Analyse,TestDir}, AnalyseMods, Stop); JobSock -> %% remote target request(JobSock, {sync_apply,{test_server, cover_analyse, - [Analyse,AnalyseMods]}}), + [Analyse,AnalyseMods, Stop]}}), read_job_sock_loop(JobSock) end. diff --git a/lib/test_server/src/ts_run.erl b/lib/test_server/src/ts_run.erl index 57d1b8806e..741dd483f5 100644 --- a/lib/test_server/src/ts_run.erl +++ b/lib/test_server/src/ts_run.erl @@ -368,7 +368,7 @@ make_common_test_args(Args0, Options0, _Vars) -> io:format("No cover file found for ~p~n",[App]), []; {value,{cover,_App,File,_Analyse}} -> - [{cover,to_list(File)}]; + [{cover,to_list(File)},{cover_stop,false}]; false -> [] end, -- cgit v1.2.3 From 3d4ea1aa4226e10b6daab29ae34f60deb0ecd7a9 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 22 Oct 2012 16:13:50 +0200 Subject: [common_test, test_server] Don't flush cover if cover is not running ct_slave, ct_test_support and test_server_test_lib always called cover:flush/1 when stopping slave nodes. If cover was not running, this would cause cover_server to be started and thus test_server:is_cover/0 to return true afterwards. This has been corrected. --- lib/common_test/src/ct_slave.erl | 9 +++++++-- lib/common_test/test/ct_test_support.erl | 10 ++++++++-- lib/test_server/test/test_server_test_lib.erl | 7 ++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl index 6f7e83d852..cb05423497 100644 --- a/lib/common_test/src/ct_slave.erl +++ b/lib/common_test/src/ct_slave.erl @@ -430,8 +430,13 @@ wait_for_node_alive(Node, N) -> % call init:stop on a remote node do_stop(ENode) -> - MainCoverNode = cover:get_main_node(), - rpc:call(MainCoverNode,cover,flush,[ENode]), + case test_server:is_cover() of + true -> + MainCoverNode = cover:get_main_node(), + rpc:call(MainCoverNode,cover,flush,[ENode]); + false -> + ok + end, spawn(ENode, init, stop, []), wait_for_node_dead(ENode, 5). diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index c05b6d87c0..afc8dc4bb7 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -117,7 +117,10 @@ end_per_suite(Config) -> CTNode = proplists:get_value(ct_node, Config), PrivDir = proplists:get_value(priv_dir, Config), true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]), - cover:flush(CTNode), + case test_server:is_cover() of + true -> cover:flush(CTNode); + false -> ok + end, slave:stop(CTNode), ok. @@ -149,7 +152,10 @@ end_per_testcase(_TestCase, Config) -> case wait_for_ct_stop(CTNode) of %% Common test was not stopped to we restart node. false -> - cover:flush(CTNode), + case test_server:is_cover() of + true -> cover:flush(CTNode); + false -> ok + end, slave:stop(CTNode), start_slave(Config,proplists:get_value(trace_level,Config)), {fail, "Could not stop common_test"}; diff --git a/lib/test_server/test/test_server_test_lib.erl b/lib/test_server/test/test_server_test_lib.erl index 0dabc17173..4e89abf308 100644 --- a/lib/test_server/test/test_server_test_lib.erl +++ b/lib/test_server/test/test_server_test_lib.erl @@ -83,7 +83,12 @@ start_slave(Config,_Level) -> post_end_per_testcase(_TC, Config, Return, State) -> Node = proplists:get_value(node, Config), - cover:flush(Node), + case test_server:is_cover() of + true -> + cover:flush(Node); + false -> + ok + end, slave:stop(Node), {Return, State}. -- cgit v1.2.3 From 572d2497a4b9cbb2e5e158a303bfe593d7f03bab Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 22 Oct 2012 17:14:10 +0200 Subject: [common_test] Add test suite for code coverage support --- lib/common_test/test/Makefile | 3 +- lib/common_test/test/ct_cover_SUITE.erl | 249 +++++++++++++++++++++ .../test/ct_cover_SUITE_data/cover_SUITE.erl | 148 ++++++++++++ .../test/ct_cover_SUITE_data/cover_test_mod.erl | 4 + .../test/ct_cover_SUITE_data/test.cover | 1 + 5 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 lib/common_test/test/ct_cover_SUITE.erl create mode 100644 lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl create mode 100644 lib/common_test/test/ct_cover_SUITE_data/cover_test_mod.erl create mode 100644 lib/common_test/test/ct_cover_SUITE_data/test.cover diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile index 652a9f6efe..3de33688da 100644 --- a/lib/common_test/test/Makefile +++ b/lib/common_test/test/Makefile @@ -54,7 +54,8 @@ MODULES= \ ct_shell_SUITE \ ct_system_error_SUITE \ ct_snmp_SUITE \ - ct_group_leader_SUITE + ct_group_leader_SUITE \ + ct_cover_SUITE ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/common_test/test/ct_cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE.erl new file mode 100644 index 0000000000..55599950c9 --- /dev/null +++ b/lib/common_test/test/ct_cover_SUITE.erl @@ -0,0 +1,249 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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_cover_SUITE +%%% +%%% Description: +%%% Test code cover analysis support +%%% +%%%------------------------------------------------------------------- +-module(ct_cover_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("common_test/include/ct_event.hrl"). + +-define(eh, ct_test_support_eh). +-define(mod, cover_test_mod). +-define(cover_spec_file,"test.cover"). + +%%-------------------------------------------------------------------- +%% TEST SERVER CALLBACK FUNCTIONS +%%-------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Description: Since Common Test starts another Test Server +%% instance, the tests need to be performed on a separate node (or +%% there will be clashes with logging processes etc). +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + case test_server:is_cover() of + true -> + {skip,"Test server is running cover already - skipping"}; + false -> + ct_test_support:init_per_suite(Config) + end. + +end_per_suite(Config) -> + ct_test_support:end_per_suite(Config). + +init_per_testcase(TestCase, Config) -> + ct_test_support:init_per_testcase(TestCase, Config). + +end_per_testcase(TestCase, Config) -> + Node = fullname(existing_node), + case lists:member(Node,nodes()) of + true -> rpc:call(Node,erlang,halt,[]); + false -> ok + end, + ct_test_support:end_per_testcase(TestCase, Config). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [ + default, + cover_stop_true, + cover_stop_false, + slave, + slave_start_slave, + cover_node_option, + ct_cover_add_remove_nodes + ]. + +%%-------------------------------------------------------------------- +%% TEST CASES +%%-------------------------------------------------------------------- + +%% Check that cover is collected from test node +%% Also check that cover is by default stopped after test is completed +default(Config) -> + {ok,Events} = run_test(default,Config), + false = check_cover(Config), + check_calls(1,Events), + ok. + +%% Check that cover is stopped when cover_stop option is set to true +cover_stop_true(Config) -> + {ok,_Events} = run_test(cover_stop_true,[{cover_stop,true}],Config), + false = check_cover(Config). + +%% Check that cover is not stopped when cover_stop option is set to false +cover_stop_false(Config) -> + {ok,_Events} = run_test(cover_stop_false,[{cover_stop,false}],Config), + {true,[],[?mod]} = check_cover(Config), + CTNode = proplists:get_value(ct_node, Config), + ok = rpc:call(CTNode,cover,stop,[]), + false = check_cover(Config), + ok. + +%% Let test node start a slave node - check that cover is collected +%% from both nodes +slave(Config) -> + {ok,Events} = run_test(slave,slave,[],Config), + check_calls(2,Events), + ok. + +%% Let test node start a slave node which in turn starts another slave +%% node - check that cover is collected from all three nodes +slave_start_slave(Config) -> + {ok,Events} = run_test(slave_start_slave,slave_start_slave,[],Config), + check_calls(3,Events), + ok. + +%% Start a slave node before test starts - the node is listed in cover +%% spec file. +%% Check that cover is collected from test node and slave node. +cover_node_option(Config) -> + {ok, HostStr}=inet:gethostname(), + Host = list_to_atom(HostStr), + DataDir = ?config(data_dir,Config), + {ok,Node} = ct_slave:start(Host,existing_node, + [{erl_flags,"-pa " ++ DataDir}]), + false = check_cover(Node), + CoverFile = add_to_cover_file("node_opt.cover",[{nodes,[Node]}],Config), + {ok,Events} = run_test(cover_node_option,cover_node_option, + [{cover,CoverFile}],Config), + check_calls(2,Events), + {ok,Node} = ct_slave:stop(existing_node), + ok. + +%% Test ct_cover:add_nodes/1 and ct_cover:remove_nodes/1 +%% Check that cover is collected from added node +ct_cover_add_remove_nodes(Config) -> + {ok, HostStr}=inet:gethostname(), + Host = list_to_atom(HostStr), + DataDir = ?config(data_dir,Config), + {ok,Node} = ct_slave:start(Host,existing_node, + [{erl_flags,"-pa " ++ DataDir}]), + false = check_cover(Node), + {ok,Events} = run_test(ct_cover_add_remove_nodes,ct_cover_add_remove_nodes, + [],Config), + check_calls(2,Events), + {ok,Node} = ct_slave:stop(existing_node), + ok. + + +%%%----------------------------------------------------------------- +%%% HELP FUNCTIONS +%%%----------------------------------------------------------------- +run_test(Label,Config) -> + run_test(Label,[],Config). +run_test(Label,ExtraOpts,Config) -> + run_test(Label,default,ExtraOpts,Config). +run_test(Label,Testcase,ExtraOpts,Config) -> + DataDir = ?config(data_dir, Config), + Suite = filename:join(DataDir, "cover_SUITE"), + DefaultCover = filename:join(DataDir, ?cover_spec_file), + Cover = proplists:get_value(cover,ExtraOpts,DefaultCover), + RestOpts = lists:keydelete(cover,1,ExtraOpts), + {Opts,ERPid} = setup([{suite,Suite},{testcase,Testcase}, + {cover,Cover},{label,Label}] ++ RestOpts, Config), + execute(Label, Testcase, Opts, ERPid, Config). + +setup(Test, Config) -> + Opts0 = ct_test_support:get_opts(Config), + Level = ?config(trace_level, Config), + EvHArgs = [{cbm,ct_test_support},{trace_level,Level}], + Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test], + ERPid = ct_test_support:start_event_receiver(Config), + {Opts,ERPid}. + +execute(Name, Testcase, Opts, ERPid, Config) -> + ok = ct_test_support:run(Opts, Config), + Events = ct_test_support:get_events(ERPid, Config), + + ct_test_support:log_events(Name, + reformat(Events, ?eh), + ?config(priv_dir, Config), + Opts), + TestEvents = events_to_check(Testcase), + R = ct_test_support:verify_events(TestEvents, Events, Config), + {R,Events}. + +reformat(Events, EH) -> + ct_test_support:reformat(Events, EH). + +events_to_check(Testcase) -> + OneTest = + [{?eh,start_logging,{'DEF','RUNDIR'}}] ++ + [{?eh,tc_done,{cover_SUITE,Testcase,ok}}] ++ + [{?eh,stop_logging,[]}], + + %% 2 tests (ct:run_test + script_start) is default + OneTest ++ OneTest. + +check_cover(Config) when is_list(Config) -> + CTNode = proplists:get_value(ct_node, Config), + check_cover(CTNode); +check_cover(Node) when is_atom(Node) -> + case rpc:call(Node,test_server,is_cover,[]) of + true -> + {true, + rpc:call(Node,cover,which_nodes,[]), + rpc:call(Node,cover,modules,[])}; + false -> + false + end. + +%% Check that each coverlog includes N calls to ?mod:foo/0 +check_calls(N,Events) -> + CoverLogs = + [filename:join(filename:dirname(TCLog),"all.coverdata") || + {ct_test_support_eh, + {event,tc_logfile,ct@falco, + {{cover_SUITE,init_per_suite},TCLog}}} <- Events], + do_check_logs(N,CoverLogs). + +do_check_logs(N,[CoverLog|CoverLogs]) -> + {ok,_} = cover:start(), + ok = cover:import(CoverLog), + {ok,[{{?mod,foo,0},N}]} = cover:analyse(?mod,calls,function), + ok = cover:stop(), + do_check_logs(N,CoverLogs); +do_check_logs(_,[]) -> + ok. + +fullname(Name) -> + {ok,Host} = inet:gethostname(), + list_to_atom(atom_to_list(Name) ++ "@" ++ Host). + +add_to_cover_file(CoverFileName,Terms,Config) -> + DataDir = ?config(data_dir,Config), + DefaultFile = filename:join(DataDir,?cover_spec_file), + NewFile = filename:join(DataDir,CoverFileName), + {ok,_} = file:copy(DefaultFile,NewFile), + {ok,Fd} = file:open(NewFile,[append]), + lists:foreach(fun(Term) -> + file:write(Fd,io_lib:format("~p.~n",[Term])) + end,Terms), + ok = file:close(Fd), + NewFile. diff --git a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl new file mode 100644 index 0000000000..e84f545fe8 --- /dev/null +++ b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl @@ -0,0 +1,148 @@ +%%-------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2012. 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: cover_SUITE.erl +%% +%% Description: +%% This file contains the test cases for the code coverage support +%% +%% @author Support +%% @doc Test of code coverage support in common_test +%% @end +%%---------------------------------------------------------------------- +%%---------------------------------------------------------------------- +-module(cover_SUITE). +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). + +%% Default timetrap timeout (set in init_per_testcase). +-define(default_timeout, ?t:minutes(1)). + +suite() -> + []. + +all() -> + []. + +init_per_suite(Config) -> + Config. + +end_per_suite(Config) -> + Config. + +init_per_testcase(_Case, Config) -> + Dog = test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. + +end_per_testcase(Case, Config) -> + %% try apply(?MODULE,Case,[cleanup,Config]) + %% catch error:undef -> ok + %% end, + + kill_slaves(Case,nodes()), + Dog=?config(watchdog, Config), + test_server:timetrap_cancel(Dog), + ok. + +%%%----------------------------------------------------------------- +%%% Test cases +break(_Config) -> + test_server:break(""), + ok. + +default(Config) -> + cover_compiled = code:which(cover_test_mod), + cover_test_mod:foo(), + ok. + +slave(Config) -> + cover_compiled = code:which(cover_test_mod), + cover_test_mod:foo(), + N1 = nodename(slave,1), + {ok,Node} = ct_slave:start(N1), + cover_compiled = rpc:call(Node,code,which,[cover_test_mod]), + rpc:call(Node,cover_test_mod,foo,[]), + {ok,Node} = ct_slave:stop(N1), + ok. + +slave_start_slave(Config) -> + cover_compiled = code:which(cover_test_mod), + cover_test_mod:foo(), + N1 = nodename(slave_start_slave,1), + N2 = nodename(slave_start_slave,2), + {ok,Node} = ct_slave:start(N1), + cover_compiled = rpc:call(Node,code,which,[cover_test_mod]), + rpc:call(Node,cover_test_mod,foo,[]), + {ok,Node2} = rpc:call(Node,ct_slave,start,[N2]), + rpc:call(Node2,cover_test_mod,foo,[]), + {ok,Node2} = rpc:call(Node,ct_slave,stop,[N2]), + {ok,Node} = ct_slave:stop(N1), + ok. + +cover_node_option(Config) -> + cover_compiled = code:which(cover_test_mod), + cover_test_mod:foo(), + Node = fullname(existing_node), + cover_compiled = rpc:call(Node,code,which,[cover_test_mod]), + rpc:call(Node,cover_test_mod,foo,[]), + ok. + +ct_cover_add_remove_nodes(Config) -> + cover_compiled = code:which(cover_test_mod), + cover_test_mod:foo(), + Node = fullname(existing_node), + Beam = rpc:call(Node,code,which,[cover_test_mod]), + false = (Beam == cover_compiled), + + rpc:call(Node,cover_test_mod,foo,[]), % should not be collected + {ok,[Node]} = ct_cover:add_nodes([Node]), + cover_compiled = rpc:call(Node,code,which,[cover_test_mod]), + rpc:call(Node,cover_test_mod,foo,[]), % should be collected + ok = ct_cover:remove_nodes([Node]), + rpc:call(Node,cover_test_mod,foo,[]), % should not be collected + + Beam = rpc:call(Node,code,which,[cover_test_mod]), + + ok. + +%%%----------------------------------------------------------------- +%%% Internal +nodename(Case,N) -> + list_to_atom(nodeprefix(Case) ++ integer_to_list(N)). + +nodeprefix(Case) -> + atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Case) ++ "_node". + + +fullname(Name) -> + {ok,Host} = inet:gethostname(), + list_to_atom(atom_to_list(Name) ++ "@" ++ Host). + +kill_slaves(Case, [Node|Nodes]) -> + Prefix = nodeprefix(Case), + case lists:prefix(Prefix,atom_to_list(Node)) of + true -> + rpc:call(Node,erlang,halt,[]); + _ -> + ok + end, + kill_slaves(Case,Nodes); +kill_slaves(_,[]) -> + ok. diff --git a/lib/common_test/test/ct_cover_SUITE_data/cover_test_mod.erl b/lib/common_test/test/ct_cover_SUITE_data/cover_test_mod.erl new file mode 100644 index 0000000000..d4f69452c3 --- /dev/null +++ b/lib/common_test/test/ct_cover_SUITE_data/cover_test_mod.erl @@ -0,0 +1,4 @@ +-module(cover_test_mod). +-compile(export_all). +foo() -> + ok. diff --git a/lib/common_test/test/ct_cover_SUITE_data/test.cover b/lib/common_test/test/ct_cover_SUITE_data/test.cover new file mode 100644 index 0000000000..a1baa8531c --- /dev/null +++ b/lib/common_test/test/ct_cover_SUITE_data/test.cover @@ -0,0 +1 @@ +{incl_mods,[cover_test_mod]}. -- cgit v1.2.3 From ef640faa6cffeeb44e4f1e4973720c0abe470246 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 23 Oct 2012 17:50:43 +0200 Subject: Include all kernel modules in code coverage analysis --- lib/kernel/test/kernel.cover | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kernel/test/kernel.cover b/lib/kernel/test/kernel.cover index f6967ca651..af1dd7eaad 100644 --- a/lib/kernel/test/kernel.cover +++ b/lib/kernel/test/kernel.cover @@ -1,3 +1,3 @@ %% -*- erlang -*- -{incl_mods,[gen_udp,inet6_udp,inet_res,inet_dns]}. +{incl_app,kernel,details}. -- cgit v1.2.3 From 66a6e7caa4b7b64566d08fd5e93ce250773a50a7 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 24 Oct 2012 14:52:36 +0200 Subject: [common_test] Add test for OTP-9956 OTP-9956 Test that the test suite itself can be cover compiled, and that data_dir is still set correctly. --- lib/common_test/test/ct_cover_SUITE.erl | 76 ++++++++++++++-------- .../test/ct_cover_SUITE_data/cover_SUITE.erl | 8 +++ .../cover_SUITE_data/.gitignore | 0 .../test/ct_cover_SUITE_data/test.cover | 1 - 4 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 lib/common_test/test/ct_cover_SUITE_data/cover_SUITE_data/.gitignore delete mode 100644 lib/common_test/test/ct_cover_SUITE_data/test.cover diff --git a/lib/common_test/test/ct_cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE.erl index 55599950c9..bebfce70d0 100644 --- a/lib/common_test/test/ct_cover_SUITE.erl +++ b/lib/common_test/test/ct_cover_SUITE.erl @@ -32,8 +32,8 @@ -include_lib("common_test/include/ct_event.hrl"). -define(eh, ct_test_support_eh). +-define(suite, cover_SUITE). -define(mod, cover_test_mod). --define(cover_spec_file,"test.cover"). %%-------------------------------------------------------------------- %% TEST SERVER CALLBACK FUNCTIONS @@ -76,7 +76,8 @@ all() -> slave, slave_start_slave, cover_node_option, - ct_cover_add_remove_nodes + ct_cover_add_remove_nodes, + otp_9956 ]. %%-------------------------------------------------------------------- @@ -88,7 +89,7 @@ all() -> default(Config) -> {ok,Events} = run_test(default,Config), false = check_cover(Config), - check_calls(1,Events), + check_calls(Events,1), ok. %% Check that cover is stopped when cover_stop option is set to true @@ -109,14 +110,14 @@ cover_stop_false(Config) -> %% from both nodes slave(Config) -> {ok,Events} = run_test(slave,slave,[],Config), - check_calls(2,Events), + check_calls(Events,2), ok. %% Let test node start a slave node which in turn starts another slave %% node - check that cover is collected from all three nodes slave_start_slave(Config) -> {ok,Events} = run_test(slave_start_slave,slave_start_slave,[],Config), - check_calls(3,Events), + check_calls(Events,3), ok. %% Start a slave node before test starts - the node is listed in cover @@ -129,10 +130,11 @@ cover_node_option(Config) -> {ok,Node} = ct_slave:start(Host,existing_node, [{erl_flags,"-pa " ++ DataDir}]), false = check_cover(Node), - CoverFile = add_to_cover_file("node_opt.cover",[{nodes,[Node]}],Config), + CoverSpec = default_cover_file_content() ++ [{nodes,[Node]}], + CoverFile = create_cover_file(cover_node_option,CoverSpec,Config), {ok,Events} = run_test(cover_node_option,cover_node_option, [{cover,CoverFile}],Config), - check_calls(2,Events), + check_calls(Events,2), {ok,Node} = ct_slave:stop(existing_node), ok. @@ -147,10 +149,18 @@ ct_cover_add_remove_nodes(Config) -> false = check_cover(Node), {ok,Events} = run_test(ct_cover_add_remove_nodes,ct_cover_add_remove_nodes, [],Config), - check_calls(2,Events), + check_calls(Events,2), {ok,Node} = ct_slave:stop(existing_node), ok. +%% Test that the test suite itself can be cover compiled and that +%% data_dir is set correctly (OTP-9956) +otp_9956(Config) -> + CoverFile = create_cover_file(otp_9956,[{incl_mods,[?suite]}],Config), + {ok,Events} = run_test(otp_9956,otp_9956,[{cover,CoverFile}],Config), + check_calls(Events,{?suite,otp_9956,1},1), + ok. + %%%----------------------------------------------------------------- %%% HELP FUNCTIONS @@ -161,12 +171,17 @@ run_test(Label,ExtraOpts,Config) -> run_test(Label,default,ExtraOpts,Config). run_test(Label,Testcase,ExtraOpts,Config) -> DataDir = ?config(data_dir, Config), - Suite = filename:join(DataDir, "cover_SUITE"), - DefaultCover = filename:join(DataDir, ?cover_spec_file), - Cover = proplists:get_value(cover,ExtraOpts,DefaultCover), + Suite = filename:join(DataDir, ?suite), + CoverFile = + case proplists:get_value(cover,ExtraOpts) of + undefined -> + create_default_cover_file(Label,Config); + CF -> + CF + end, RestOpts = lists:keydelete(cover,1,ExtraOpts), {Opts,ERPid} = setup([{suite,Suite},{testcase,Testcase}, - {cover,Cover},{label,Label}] ++ RestOpts, Config), + {cover,CoverFile},{label,Label}] ++ RestOpts, Config), execute(Label, Testcase, Opts, ERPid, Config). setup(Test, Config) -> @@ -195,7 +210,7 @@ reformat(Events, EH) -> events_to_check(Testcase) -> OneTest = [{?eh,start_logging,{'DEF','RUNDIR'}}] ++ - [{?eh,tc_done,{cover_SUITE,Testcase,ok}}] ++ + [{?eh,tc_done,{?suite,Testcase,ok}}] ++ [{?eh,stop_logging,[]}], %% 2 tests (ct:run_test + script_start) is default @@ -215,35 +230,42 @@ check_cover(Node) when is_atom(Node) -> end. %% Check that each coverlog includes N calls to ?mod:foo/0 -check_calls(N,Events) -> +check_calls(Events,N) -> + check_calls(Events,{?mod,foo,0},N). +check_calls(Events,MFA,N) -> CoverLogs = [filename:join(filename:dirname(TCLog),"all.coverdata") || {ct_test_support_eh, {event,tc_logfile,ct@falco, - {{cover_SUITE,init_per_suite},TCLog}}} <- Events], - do_check_logs(N,CoverLogs). + {{?suite,init_per_suite},TCLog}}} <- Events], + do_check_logs(CoverLogs,MFA,N). -do_check_logs(N,[CoverLog|CoverLogs]) -> +do_check_logs([CoverLog|CoverLogs],{Mod,_,_} = MFA,N) -> {ok,_} = cover:start(), ok = cover:import(CoverLog), - {ok,[{{?mod,foo,0},N}]} = cover:analyse(?mod,calls,function), + {ok,Calls} = cover:analyse(Mod,calls,function), ok = cover:stop(), - do_check_logs(N,CoverLogs); -do_check_logs(_,[]) -> + {MFA,N} = lists:keyfind(MFA,1,Calls), + do_check_logs(CoverLogs,MFA,N); +do_check_logs([],_,_) -> ok. fullname(Name) -> {ok,Host} = inet:gethostname(), list_to_atom(atom_to_list(Name) ++ "@" ++ Host). -add_to_cover_file(CoverFileName,Terms,Config) -> - DataDir = ?config(data_dir,Config), - DefaultFile = filename:join(DataDir,?cover_spec_file), - NewFile = filename:join(DataDir,CoverFileName), - {ok,_} = file:copy(DefaultFile,NewFile), - {ok,Fd} = file:open(NewFile,[append]), +default_cover_file_content() -> + [{incl_mods,[?mod]}]. + +create_default_cover_file(Filename,Config) -> + create_cover_file(Filename,default_cover_file_content(),Config). + +create_cover_file(Filename,Terms,Config) -> + PrivDir = ?config(priv_dir,Config), + File = filename:join(PrivDir,Filename) ++ ".cover", + {ok,Fd} = file:open(File,[write]), lists:foreach(fun(Term) -> file:write(Fd,io_lib:format("~p.~n",[Term])) end,Terms), ok = file:close(Fd), - NewFile. + File. diff --git a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl index e84f545fe8..fdc3323f0a 100644 --- a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl +++ b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE.erl @@ -122,6 +122,14 @@ ct_cover_add_remove_nodes(Config) -> ok. +otp_9956(Config) -> + cover_compiled = code:which(?MODULE), + DataDir = ?config(data_dir,Config), + absolute = filename:pathtype(DataDir), + true = filelib:is_dir(DataDir), + ok. + + %%%----------------------------------------------------------------- %%% Internal nodename(Case,N) -> diff --git a/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE_data/.gitignore b/lib/common_test/test/ct_cover_SUITE_data/cover_SUITE_data/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/common_test/test/ct_cover_SUITE_data/test.cover b/lib/common_test/test/ct_cover_SUITE_data/test.cover deleted file mode 100644 index a1baa8531c..0000000000 --- a/lib/common_test/test/ct_cover_SUITE_data/test.cover +++ /dev/null @@ -1 +0,0 @@ -{incl_mods,[cover_test_mod]}. -- cgit v1.2.3 From b6fe934ea73f8eeaaa70b786f53ee6e8f2a52d2c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 24 Oct 2012 16:55:45 +0200 Subject: Use code:lib_dir instead of code:which to get application directory code_SUITE:ext_mod_dep used code:which(kernel) to get the path to the kernel ebin directory. This failed when kernel was cover compiled, since code:which then returns 'cover_compiled'. Using code:lib_dir instead solved the problem. --- lib/kernel/test/code_SUITE.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl index 5e0300639e..d7424c0c9a 100644 --- a/lib/kernel/test/code_SUITE.erl +++ b/lib/kernel/test/code_SUITE.erl @@ -684,8 +684,8 @@ ext_mod_dep(Config) when is_list(Config) -> xref:set_default(s, [{verbose,false},{warnings,false}, {builtins,true},{recurse,true}]), xref:set_library_path(s, code:get_path()), - xref:add_directory(s, filename:dirname(code:which(kernel))), - xref:add_directory(s, filename:dirname(code:which(lists))), + xref:add_directory(s, filename:join(code:lib_dir(kernel),"ebin")), + xref:add_directory(s, filename:join(code:lib_dir(stdlib),"ebin")), case catch ext_mod_dep2() of {'EXIT', Reason} -> xref:stop(s), -- cgit v1.2.3 From fe9ed2938a02869b1df9554b32e4c4c453e6995c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 24 Oct 2012 18:48:11 +0200 Subject: [test_server] Add option {start_cover,false} to test_server:start_node By default the test server will start cover on all nodes when the test is run with code coverage analysis. To make sure cover is not started on a new node, this option can be set. This can be useful if the test itself starts cover or if the new node for some reason can not run cover compiled code. --- lib/test_server/doc/src/test_server.xml | 14 +++++++++++++- lib/test_server/src/test_server.erl | 5 ++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/test_server/doc/src/test_server.xml b/lib/test_server/doc/src/test_server.xml index 5bfa42c36f..841cbfbe91 100644 --- a/lib/test_server/doc/src/test_server.xml +++ b/lib/test_server/doc/src/test_server.xml @@ -5,7 +5,7 @@

2007 - 2011 + 2012 Ericsson AB, All Rights Reserved @@ -529,6 +529,18 @@ Only valid for peer nodes. Note that slave nodes always analogy with os:getenv/1), which removes the environment variable. Only valid for peer nodes. Not available on VxWorks. + {start_cover, false} + By default the test server will start cover on all nodes + when the test is run with code coverage analysis. To make + sure cover is not started on a new node, set this option to + false. This can be necessary if the connection to + the node at some point will be broken but the node is + expected to stay alive. The reason is that a remote cover + node can not continue to run without its main node. Another + solution would be to explicitly stop cover on the node + before breaking the connection, but in some situations (if + old code resides in one or more processes) this is not + possible. diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl index 2b15d07990..b900c88db4 100644 --- a/lib/test_server/src/test_server.erl +++ b/lib/test_server/src/test_server.erl @@ -2597,7 +2597,10 @@ start_node(Name, Type, Options) -> %% by a shielded node. Cover = case is_cover() of true -> - not is_shielded(Name) andalso same_version(Node); + not is_shielded(Name) + andalso same_version(Node) + andalso proplists:get_value(start_cover,Options, + true); false -> false end, -- cgit v1.2.3 From f0010583fd53174b72341d64be3a481cccc4623c Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Fri, 26 Oct 2012 16:07:36 +0200 Subject: [cover] Don't kill remote nodes when connection to main node is lost OTP-10523 Since the will probably be cover compiled modules left in the remote node, we want to keep the cover_server and it's ets tables even if connection to remote node is lost. This way it will not crash (due to ets:insert in non existing table) if functions in cover compiled modules are executed. --- lib/tools/src/cover.erl | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index fb65206938..2154a21ed6 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -555,7 +555,10 @@ remote_call(Node,Request) -> Return = receive {'DOWN', Ref, _Type, _Object, _Info} -> - {error,node_dead}; + case Request of + {remote,stop} -> ok; + _ -> {error,node_dead} + end; {?SERVER,Reply} -> Reply end, @@ -583,7 +586,6 @@ init_main(Starter) -> ets:new(?BINARY_TABLE, [set, named_table]), ets:new(?COLLECTION_TABLE, [set, public, named_table]), ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, named_table]), - process_flag(trap_exit,true), Starter ! {?SERVER,started}, main_process_loop(#main_state{}). @@ -595,8 +597,8 @@ main_process_loop(State) -> lists:foldl( fun(Node,Acc) -> case rpc:call(Node,cover,remote_start,[ThisNode]) of - {ok,RPid} -> - link(RPid), + {ok,_RPid} -> + erlang:monitor(process,{?SERVER,Node}), [Node|Acc]; Error -> io:format("Could not start cover on ~w: ~p\n", @@ -721,8 +723,8 @@ main_process_loop(State) -> {From, {stop,Nodes}} -> remote_collect('_',Nodes,true), reply(From, ok), - State1 = State#main_state{nodes=State#main_state.nodes--Nodes}, - main_process_loop(State1); + Nodes1 = State#main_state.nodes--Nodes, + main_process_loop(State#main_state{nodes=Nodes1}); {From, {flush,Nodes}} -> remote_collect('_',Nodes,false), @@ -807,13 +809,10 @@ main_process_loop(State) -> end, main_process_loop(S); - {'EXIT',Pid,_Reason} -> - %% Exit is trapped on the main node only, so this will only happen - %% there. I assume that I'm only linked to cover_servers on remote - %% nodes, so this must be one of them crashing. - %% Remove node from list! - State1 = State#main_state{nodes=State#main_state.nodes--[node(Pid)]}, - main_process_loop(State1); + {'DOWN', _MRef, process, {?SERVER,Node}, _Info} -> + %% A remote cover_server is down, remove node from list. + Nodes1 = State#main_state.nodes--[Node], + main_process_loop(State#main_state{nodes=Nodes1}); {From, get_main_node} -> reply(From, node()), @@ -873,7 +872,7 @@ remote_process_loop(State) -> {remote,stop} -> reload_originals(State#remote_state.compiled), unregister(?SERVER), - remote_reply(State#remote_state.main_node, ok); + ok; % not replying since 'DOWN' message will be received anyway {From, get_main_node} -> reply(From, State#remote_state.main_node), @@ -1121,7 +1120,6 @@ remove_myself([Node|Nodes],Acc) -> remove_myself(Nodes,[Node|Acc]); remove_myself([],Acc) -> Acc. - %%%--Handling of modules state data-------------------------------------- @@ -2334,7 +2332,7 @@ pmap(Fun, [E | Rest], Pids, Limit, Cnt, Acc) when Cnt < Limit -> pmap(Fun, Rest, Pids ++ [Pid], Limit, Cnt + 1, Acc); pmap(Fun, List, [Pid | Pids], Limit, Cnt, Acc) -> receive - {'DOWN', _Ref, process, _, _} -> + {'DOWN', _Ref, process, X, _} when is_pid(X) -> pmap(Fun, List, [Pid | Pids], Limit, Cnt - 1, Acc); {res, Pid, Res} -> pmap(Fun, List, Pids, Limit, Cnt, [Res | Acc]) @@ -2343,6 +2341,6 @@ pmap(_Fun, [], [], _Limit, 0, Acc) -> lists:reverse(Acc); pmap(Fun, [], [], Limit, Cnt, Acc) -> receive - {'DOWN', _Ref, process, _, _} -> + {'DOWN', _Ref, process, X, _} when is_pid(X) -> pmap(Fun, [], [], Limit, Cnt - 1, Acc) end. -- cgit v1.2.3 From 0dbae9d6fcdaa9b699e85a00fe44560b89cc24df Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Mon, 29 Oct 2012 17:37:29 +0100 Subject: [cover] Allow reconnection if node has been disconnected or down OTP-10523 Earlier, if the connection to a remote cover node was lost, all cover data was lost and the cover_server on the remote node would die. This would cause problems if there were cover compiled modules that would still be executed since they would attempt to write to the no longer existing ets tables belonging to the cover_server. This commit changes this behavior so that the cover_server on the remote node will survive connection loss and continue collecting cover data. If the connection is re-established then the main node will sync with the remote node again and cover data will not be lost (unless the node was down). --- lib/tools/doc/src/cover.xml | 5 ++ lib/tools/src/cover.erl | 139 ++++++++++++++++++++++++---------- lib/tools/test/cover_SUITE.erl | 116 +++++++++++++++++++++++++--- lib/tools/test/cover_SUITE_data/f.erl | 11 ++- 4 files changed, 222 insertions(+), 49 deletions(-) diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml index be0fd5bd47..a2444ec947 100644 --- a/lib/tools/doc/src/cover.xml +++ b/lib/tools/doc/src/cover.xml @@ -106,6 +106,11 @@ from all nodes.

To only collect data from remote nodes without stopping cover on those nodes, use cover:flush/1

+

If the connection to a remote node goes down, the main node + will mark it as lost. If the node comes back it will be added + again. If the remote node was alive during the disconnected + periode, cover data from before and during this periode will be + included in the analysis.

diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 2154a21ed6..10f14b0a49 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -26,12 +26,17 @@ %% ARCHITECTURE %% The coverage tool consists of one process on each node involved in %% coverage analysis. The process is registered as 'cover_server' -%% (?SERVER). All cover_servers in the distributed system are linked -%% together. The cover_server on the 'main' node is in charge, and it -%% traps exits so it can detect nodedown or process crashes on the -%% remote nodes. This process is implemented by the functions -%% init_main/1 and main_process_loop/1. The cover_server on the remote -%% nodes are implemented by the functions init_remote/2 and +%% (?SERVER). The cover_server on the 'main' node is in charge, and +%% it monitors the cover_servers on all remote nodes. When it gets a +%% 'DOWN' message for another cover_server, it marks the node as +%% 'lost'. If a nodeup is received for a lost node the main node +%% ensures that the cover compiled modules are loaded again. If the +%% remote node was alive during the disconnected periode, cover data +%% for this periode will also be included in the analysis. +%% +%% The cover_server process on the main node is implemented by the +%% functions init_main/1 and main_process_loop/1. The cover_server on +%% the remote nodes are implemented by the functions init_remote/2 and %% remote_process_loop/1. %% %% TABLES @@ -90,7 +95,8 @@ -record(main_state, {compiled=[], % [{Module,File}] imported=[], % [{Module,File,ImportFile}] stopper, % undefined | pid() - nodes=[]}). % [Node] + nodes=[], % [Node] + lost_nodes=[]}). % [Node] -record(remote_state, {compiled=[], % [{Module,File}] main_node}). % atom() @@ -586,39 +592,14 @@ init_main(Starter) -> ets:new(?BINARY_TABLE, [set, named_table]), ets:new(?COLLECTION_TABLE, [set, public, named_table]), ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, named_table]), + net_kernel:monitor_nodes(true), Starter ! {?SERVER,started}, main_process_loop(#main_state{}). main_process_loop(State) -> receive {From, {start_nodes,Nodes}} -> - ThisNode = node(), - StartedNodes = - lists:foldl( - fun(Node,Acc) -> - case rpc:call(Node,cover,remote_start,[ThisNode]) of - {ok,_RPid} -> - erlang:monitor(process,{?SERVER,Node}), - [Node|Acc]; - Error -> - io:format("Could not start cover on ~w: ~p\n", - [Node,Error]), - Acc - end - end, - [], - Nodes), - - %% In case some of the compiled modules have been unloaded they - %% should not be loaded on the new node. - {_LoadedModules,Compiled} = - get_compiled_still_loaded(State#main_state.nodes, - State#main_state.compiled), - remote_load_compiled(StartedNodes,Compiled), - - State1 = - State#main_state{nodes = State#main_state.nodes ++ StartedNodes, - compiled = Compiled}, + {StartedNodes,State1} = do_start_nodes(Nodes, State), reply(From, {ok,StartedNodes}), main_process_loop(State1); @@ -810,9 +791,24 @@ main_process_loop(State) -> main_process_loop(S); {'DOWN', _MRef, process, {?SERVER,Node}, _Info} -> - %% A remote cover_server is down, remove node from list. - Nodes1 = State#main_state.nodes--[Node], - main_process_loop(State#main_state{nodes=Nodes1}); + %% A remote cover_server is down, mark as lost + Nodes = State#main_state.nodes--[Node], + Lost = [Node|State#main_state.lost_nodes], + main_process_loop(State#main_state{nodes=Nodes,lost_nodes=Lost}); + + {nodeup,Node} -> + State1 = + case lists:member(Node,State#main_state.lost_nodes) of + true -> + sync_compiled(Node,State); + false -> + State + end, + main_process_loop(State1); + + {nodedown,_} -> + %% Will be taken care of when 'DOWN' message arrives + main_process_loop(State); {From, get_main_node} -> reply(From, node()), @@ -874,8 +870,13 @@ remote_process_loop(State) -> unregister(?SERVER), ok; % not replying since 'DOWN' message will be received anyway + {remote,get_compiled} -> + remote_reply(State#remote_state.main_node, + State#remote_state.compiled), + remote_process_loop(State); + {From, get_main_node} -> - reply(From, State#remote_state.main_node), + remote_reply(From, State#remote_state.main_node), remote_process_loop(State); get_status -> @@ -987,6 +988,36 @@ unload([]) -> %%%--Handling of remote nodes-------------------------------------------- +do_start_nodes(Nodes, State) -> + ThisNode = node(), + StartedNodes = + lists:foldl( + fun(Node,Acc) -> + case rpc:call(Node,cover,remote_start,[ThisNode]) of + {ok,_RPid} -> + erlang:monitor(process,{?SERVER,Node}), + [Node|Acc]; + Error -> + io:format("Could not start cover on ~w: ~p\n", + [Node,Error]), + Acc + end + end, + [], + Nodes), + + %% In case some of the compiled modules have been unloaded they + %% should not be loaded on the new node. + {_LoadedModules,Compiled} = + get_compiled_still_loaded(State#main_state.nodes, + State#main_state.compiled), + remote_load_compiled(StartedNodes,Compiled), + + State1 = + State#main_state{nodes = State#main_state.nodes ++ StartedNodes, + compiled = Compiled}, + {StartedNodes, State1}. + %% start the cover_server on a remote node remote_start(MainNode) -> case whereis(?SERVER) of @@ -1010,6 +1041,30 @@ remote_start(MainNode) -> {error,{already_started,Pid}} end. +%% If a lost node comes back, ensure that main and remote node has the +%% same cover compiled modules. Note that no action is taken if the +%% same {Mod,File} eksists on both, i.e. code change is not handled! +sync_compiled(Node,State) -> + #main_state{compiled=Compiled0,nodes=Nodes,lost_nodes=Lost}=State, + State1 = + case remote_call(Node,{remote,get_compiled}) of + {error,node_dead} -> + {_,S} = do_start_nodes([Node],State), + S; + {error,_} -> + State; + RemoteCompiled -> + {_,Compiled} = get_compiled_still_loaded(Nodes,Compiled0), + Unload = [UM || {UM,_}=U <- RemoteCompiled, + false == lists:member(U,Compiled)], + remote_unload([Node],Unload), + Load = [L || L <- Compiled, + false == lists:member(L,RemoteCompiled)], + remote_load_compiled([Node],Load), + State#main_state{compiled=Compiled, nodes=[Node|Nodes]} + end, + State1#main_state{lost_nodes=Lost--[Node]}. + %% Load a set of cover compiled modules on remote nodes, %% We do it ?MAX_MODS modules at a time so that we don't %% run out of memory on the cover_server node. @@ -2279,7 +2334,13 @@ do_reset2([]) -> do_clear(Module) -> ets:match_delete(?COVER_CLAUSE_TABLE, {Module,'_'}), ets:match_delete(?COVER_TABLE, {#bump{module=Module},'_'}), - ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}). + case lists:member(?COLLECTION_TABLE, ets:all()) of + true -> + %% We're on the main node + ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}); + false -> + ok + end. not_loaded(Module, unloaded, State) -> do_clear(Module), diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index 950c4ddbcb..3bf1b44af8 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -23,7 +23,7 @@ init_per_group/2,end_per_group/2]). -export([start/1, compile/1, analyse/1, misc/1, stop/1, - distribution/1, export_import/1, + distribution/1, reconnect/1, die_and_reconnect/1, export_import/1, otp_5031/1, eif/1, otp_5305/1, otp_5418/1, otp_6115/1, otp_7095/1, otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1]). @@ -45,7 +45,8 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> case whereis(cover_server) of undefined -> - [start, compile, analyse, misc, stop, distribution, + [start, compile, analyse, misc, stop, + distribution, reconnect, die_and_reconnect, export_import, otp_5031, eif, otp_5305, otp_5418, otp_6115, otp_7095, otp_8188, otp_8270, otp_8273, otp_8340]; @@ -408,20 +409,117 @@ distribution(Config) when is_list(Config) -> ?line true = is_unloaded(LocalBeam), ?line true = is_unloaded(N2Beam), - %% Check that cover_server on remote node dies if main node dies + %% Check that cover_server on remote node does not die if main node dies ?line {ok,[N1]} = cover:start(N1), - ?line true = is_pid(rpc:call(N1,erlang,whereis,[cover_server])), + ?line true = is_pid(N1Server = rpc:call(N1,erlang,whereis,[cover_server])), ?line exit(whereis(cover_server),kill), - ?line timer:sleep(10), - ?line undefined = rpc:call(N1,erlang,whereis,[cover_server]), - + ?line timer:sleep(100), + ?line N1Server = rpc:call(N1,erlang,whereis,[cover_server]), + %% Cleanup ?line Files = lsfiles(), ?line remove(files(Files, ".beam")), ?line ?t:stop_node(N1), ?line ?t:stop_node(N2). - +%% Test that a lost node is reconnected +reconnect(Config) -> + DataDir = ?config(data_dir, Config), + ok = file:set_cwd(DataDir), + + {ok,a} = compile:file(a), + {ok,b} = compile:file(b), + {ok,f} = compile:file(f), + + {ok,N1} = ?t:start_node(cover_SUITE_reconnect,peer, + [{args," -pa " ++ DataDir},{start_cover,false}]), + {ok,a} = cover:compile(a), + {ok,f} = cover:compile(f), + {ok,[N1]} = cover:start(nodes()), + + %% Some calls to check later + rpc:call(N1,f,f1,[]), + cover:flush(N1), + rpc:call(N1,f,f1,[]), + + %% This will cause a call to f:f2() when nodes()==[] on N1 + rpc:cast(N1,f,call_f2_when_isolated,[]), + + %% Disconnect and check that node is removed from main cover node + net_kernel:disconnect(N1), + [] = cover:which_nodes(), + timer:sleep(500), % allow some time for the f:f2() call + + %% Do some add one module (b) and remove one module (a) + code:purge(a), + {module,a} = code:load_file(a), + {ok,b} = cover:compile(b), + cover_compiled = code:which(b), + + [] = cover:which_nodes(), + check_f_calls(1,0), % only the first call - before the flush + + %% Reconnect the node and check that b and f are cover compiled but not a + net_kernel:connect_node(N1), + timer:sleep(100), + [N1] = cover:which_nodes(), % we are reconnected + cover_compiled = rpc:call(N1,code,which,[b]), + cover_compiled = rpc:call(N1,code,which,[f]), + ABeam = rpc:call(N1,code,which,[a]), + false = (cover_compiled==ABeam), + + %% Ensure that we have: + %% * one f1 call from before the flush, + %% * one f1 call from after the flush but before disconnect + %% * one f2 call when disconnected + check_f_calls(2,1), + + cover:stop(), + ?t:stop_node(N1), + ok. + +%% Test that a lost node is reconnected - also if it has been dead +die_and_reconnect(Config) -> + DataDir = ?config(data_dir, Config), + ok = file:set_cwd(DataDir), + + {ok,f} = compile:file(f), + + NodeName = cover_SUITE_die_and_reconnect, + {ok,N1} = ?t:start_node(NodeName,peer, + [{args," -pa " ++ DataDir},{start_cover,false}]), + %% {ok,a} = cover:compile(a), + {ok,f} = cover:compile(f), + {ok,[N1]} = cover:start(nodes()), + + %% Some calls to check later + rpc:call(N1,f,f1,[]), + cover:flush(N1), + rpc:call(N1,f,f1,[]), + + %% Kill the node + rpc:call(N1,erlang,halt,[]), + [] = cover:which_nodes(), + + check_f_calls(1,0), % only the first call - before the flush + + %% Restart the node and check that cover reconnects + {ok,N1} = ?t:start_node(NodeName,peer, + [{args," -pa " ++ DataDir},{start_cover,false}]), + timer:sleep(100), + [N1] = cover:which_nodes(), % we are reconnected + cover_compiled = rpc:call(N1,code,which,[f]), + + %% One more call... + rpc:call(N1,f,f1,[]), + + %% Ensure that no more calls are counted + check_f_calls(2,0), + + cover:stop(), + ?t:stop_node(N1), + ok. + export_import(suite) -> []; export_import(Config) when is_list(Config) -> ?line DataDir = ?config(data_dir, Config), @@ -1253,4 +1351,4 @@ is_unloaded(What) -> end. check_f_calls(F1,F2) -> - {ok,[{{f,f1,0},F1},{{f,f2,0},F2}]} = cover:analyse(f,calls,function). + {ok,[{{f,f1,0},F1},{{f,f2,0},F2}|_]} = cover:analyse(f,calls,function). diff --git a/lib/tools/test/cover_SUITE_data/f.erl b/lib/tools/test/cover_SUITE_data/f.erl index 1ef8bbdb49..ce2963014a 100644 --- a/lib/tools/test/cover_SUITE_data/f.erl +++ b/lib/tools/test/cover_SUITE_data/f.erl @@ -1,5 +1,5 @@ -module(f). --export([f1/0,f2/0]). +-export([f1/0,f2/0,call_f2_when_isolated/0]). f1() -> f1_line1, @@ -8,3 +8,12 @@ f1() -> f2() -> f2_line1, f2_line2. + +call_f2_when_isolated() -> + case nodes() of + [] -> + f2(); + _ -> + timer:sleep(100), + call_f2_when_isolated() + end. -- cgit v1.2.3 From c3d34e536e9516ab54a9f5e9a30e21043eda565b Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 30 Oct 2012 11:11:26 +0100 Subject: [common_test] Extend timer for flushing error logger OTP-10040 If a busy test case generated lots of error messages, cth_log_redirect:post_end_per_testcase would crash with a timeout while waiting for the error logger to finish handling all error messages. The default timer is 5 seconds. This has now been extended to 5 minutes. --- lib/common_test/src/cth_log_redirect.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl index 77f57c6195..78ae70f37e 100644 --- a/lib/common_test/src/cth_log_redirect.erl +++ b/lib/common_test/src/cth_log_redirect.erl @@ -54,7 +54,7 @@ post_init_per_group(_Group, _Config, Result, State) -> post_end_per_testcase(_TC, _Config, Result, State) -> %% Make sure that the event queue is flushed %% before ending this test case. - gen_event:call(error_logger, ?MODULE, flush), + gen_event:call(error_logger, ?MODULE, flush, 300000), {Result, State}. pre_end_per_group(Group, Config, {ct_log, Group}) -> -- cgit v1.2.3