aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src
diff options
context:
space:
mode:
authorLukas Larsson <[email protected]>2010-12-01 15:44:17 +0100
committerLukas Larsson <[email protected]>2010-12-08 18:07:54 +0100
commit963d9f96452243a31cdcaa793a4bbe221a30a6f5 (patch)
tree52a7b44a706251aaa4967f93172555cf51c9acc7 /lib/common_test/src
parent96f93c7e2a157442f60bb1b16ab0ce76d8b9dbca (diff)
downloadotp-963d9f96452243a31cdcaa793a4bbe221a30a6f5.tar.gz
otp-963d9f96452243a31cdcaa793a4bbe221a30a6f5.tar.bz2
otp-963d9f96452243a31cdcaa793a4bbe221a30a6f5.zip
Add locking mechanism for scb state when using parallel groups
Diffstat (limited to 'lib/common_test/src')
-rw-r--r--lib/common_test/src/Makefile3
-rw-r--r--lib/common_test/src/ct_suite_callback.erl52
-rw-r--r--lib/common_test/src/ct_suite_callback_lock.erl131
3 files changed, 177 insertions, 9 deletions
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index 14a0a27051..abf7816a91 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -68,7 +68,8 @@ MODULES= \
ct_config_plain \
ct_config_xml \
ct_slave \
- ct_suite_callback
+ ct_suite_callback\
+ ct_suite_callback_lock
TARGET_MODULES= $(MODULES:%=$(EBIN)/%)
diff --git a/lib/common_test/src/ct_suite_callback.erl b/lib/common_test/src/ct_suite_callback.erl
index 4973ed685c..46bc250106 100644
--- a/lib/common_test/src/ct_suite_callback.erl
+++ b/lib/common_test/src/ct_suite_callback.erl
@@ -34,6 +34,8 @@
-type proplist() :: [{atom(),term()}].
-define(config_name, suite_callbacks).
+-define(LOCK_STATE_TIMEOUT, 500).
+-define(LOCK_NAME, '$ct_suite_callback_lock').
%% -------------------------------------------------------------------------
%% API Functions
@@ -69,7 +71,8 @@ init_tc(Mod, init_per_suite, Config) ->
call(fun call_generic/3, Config, [pre_init_per_suite, Mod]);
init_tc(Mod, end_per_suite, Config) ->
call(fun call_generic/3, Config, [pre_end_per_suite, Mod]);
-init_tc(_Mod, {init_per_group, GroupName, _}, Config) ->
+init_tc(Mod, {init_per_group, GroupName, Opts}, Config) ->
+ maybe_start_locker(Mod, GroupName, Opts),
call(fun call_generic/3, Config, [pre_init_per_group, GroupName]);
init_tc(_Mod, {end_per_group, GroupName, _}, Config) ->
call(fun call_generic/3, Config, [pre_end_per_group, GroupName]);
@@ -91,7 +94,7 @@ init_tc(_Mod, TC, Config) ->
end_tc(ct_framework, _Func, _Args, Result, _Return) ->
Result;
-end_tc(Mod, init_per_suite, Config, Result, Return) ->
+end_tc(Mod, init_per_suite, Config, _Result, Return) ->
call(fun call_generic/3, Return, [post_init_per_suite, Mod, Config],
'$ct_no_change');
@@ -99,13 +102,15 @@ end_tc(Mod, end_per_suite, Config, Result, _Return) ->
call(fun call_generic/3, Result, [post_end_per_suite, Mod, Config],
'$ct_no_change');
-end_tc(_Mod, {init_per_group, GroupName, _}, Config, Result, Return) ->
+end_tc(_Mod, {init_per_group, GroupName, _}, Config, _Result, Return) ->
call(fun call_generic/3, Return, [post_init_per_group, GroupName, Config],
'$ct_no_change');
-end_tc(_Mod, {end_per_group, GroupName, _}, Config, Result, _Return) ->
- call(fun call_generic/3, Result, [post_end_per_group, GroupName, Config],
- '$ct_no_change');
+end_tc(Mod, {end_per_group, GroupName, Opts}, Config, Result, _Return) ->
+ Res = call(fun call_generic/3, Result,
+ [post_end_per_group, GroupName, Config], '$ct_no_change'),
+ maybe_stop_locker(Mod, GroupName,Opts),
+ Res;
end_tc(_Mod, TC, Config, Result, _Return) ->
call(fun call_generic/3, Result, [post_end_per_testcase, TC, Config],
@@ -142,9 +147,13 @@ call_generic({Mod, State}, Value, [Function | Args]) ->
%% Generic call function
call(Fun, Config, Meta) ->
+ maybe_lock(),
CBs = get_callbacks(),
- call([{CBId,Fun} || {CBId,_, _} <- CBs] ++ get_new_callbacks(Config, Fun),
- remove(?config_name,Config), Meta, CBs).
+ Res = call([{CBId,Fun} || {CBId,_, _} <- CBs] ++
+ get_new_callbacks(Config, Fun),
+ remove(?config_name,Config), Meta, CBs),
+ maybe_unlock(),
+ Res.
call(Fun, Config, Meta, NoChangeRet) when is_function(Fun) ->
case call(Fun,Config,Meta) of
@@ -255,3 +264,30 @@ catch_apply(M,F,A, Default) ->
[M,F,length(A)]))})
end
end.
+
+
+%% We need to lock around the state for parallel groups only. This is because
+%% we will get several processes reading and writing the state for a single
+%% scb at the same time.
+maybe_start_locker(Mod,GroupName,Opts) ->
+ case lists:member(parallel,Opts) of
+ true ->
+ {ok, _Pid} = ct_suite_callback_lock:start({Mod,GroupName});
+ false ->
+ ok
+ end.
+
+maybe_stop_locker(Mod,GroupName,Opts) ->
+ case lists:member(parallel,Opts) of
+ true ->
+ stopped = ct_suite_callback_lock:stop({Mod,GroupName});
+ false ->
+ ok
+ end.
+
+
+maybe_lock() ->
+ locked = ct_suite_callback_lock:request().
+
+maybe_unlock() ->
+ unlocked = ct_suite_callback_lock:release().
diff --git a/lib/common_test/src/ct_suite_callback_lock.erl b/lib/common_test/src/ct_suite_callback_lock.erl
new file mode 100644
index 0000000000..84dafd1e42
--- /dev/null
+++ b/lib/common_test/src/ct_suite_callback_lock.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%% @doc Common Test Framework test execution control module.
+%%%
+%%% <p>This module is a proxy for calling and handling locks in suite callbacks.</p>
+
+-module(ct_suite_callback_lock).
+
+-behaviour(gen_server).
+
+%% API
+-export([start/1, stop/1, request/0, release/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, { id, locked = false, requests = [] }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+%% @doc Starts the server
+start(Id) ->
+ case gen_server:start({local, ?SERVER}, ?MODULE, Id, []) of
+ {error,{already_started, Pid}} ->
+ {ok,Pid};
+ Else ->
+ Else
+ end.
+
+stop(Id) ->
+ try
+ gen_server:call(?SERVER, {stop,Id})
+ catch exit:{noproc,_} ->
+ stopped
+ end.
+
+request() ->
+ try
+ gen_server:call(?SERVER,{request,self()},infinity)
+ catch exit:{noproc,_} ->
+ locked
+ end.
+
+release() ->
+ try
+ gen_server:call(?SERVER,{release,self()})
+ catch exit:{noproc,_} ->
+ unlocked
+ end.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%% @doc Initiates the server
+init(Id) ->
+ {ok, #state{ id = Id }}.
+
+%% @doc Handling call messages
+handle_call({stop,Id}, _From, #state{ id = Id, requests = Reqs } = State) ->
+ [gen_server:reply(Req, locker_stopped) || {Req,_ReqId} <- Reqs],
+ {stop, normal, stopped, State};
+handle_call({stop,_Id}, _From, State) ->
+ {reply, stopped, State};
+handle_call({request, Pid}, _From, #state{ locked = false,
+ requests = [] } = State) ->
+ Ref = monitor(process, Pid),
+ {reply, locked, State#state{ locked = {true, Pid, Ref}} };
+handle_call({request, Pid}, From, #state{ requests = Reqs } = State) ->
+ {noreply, State#state{ requests = Reqs ++ [{From,Pid}] }};
+handle_call({release, Pid}, _From, #state{ locked = {true, Pid, Ref},
+ requests = []} = State) ->
+ demonitor(Ref,[flush]),
+ {reply, unlocked, State#state{ locked = false }};
+handle_call({release, Pid}, _From,
+ #state{ locked = {true, Pid, Ref},
+ requests = [{NextFrom,NextPid}|Rest]} = State) ->
+ demonitor(Ref,[flush]),
+ gen_server:reply(NextFrom,locked),
+ NextRef = monitor(process, NextPid),
+ {reply,unlocked,State#state{ locked = {true, NextPid, NextRef},
+ requests = Rest } };
+handle_call({release, _Pid}, _From, State) ->
+ {reply, not_locked, State}.
+
+%% @doc Handling cast messages
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%% @doc Handling all non call/cast messages
+handle_info({'DOWN',Ref,process,Pid,_},
+ #state{ locked = {true, Pid, Ref},
+ requests = [{NextFrom,NextPid}|Rest] } = State) ->
+ gen_server:reply(NextFrom, locked),
+ NextRef = monitor(process, NextPid),
+ {noreply,State#state{ locked = {true, NextPid, NextRef},
+ requests = Rest } }.
+
+%% @doc This function is called by a gen_server when it is about to terminate.
+terminate(_Reason, _State) ->
+ ok.
+
+%% @doc Convert process state when code is changed
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% -------------------------------------------------------------------------
+%% Internal Functions
+%% -------------------------------------------------------------------------