Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 988 lines (889 sloc) 40.26 kb
6d826f9 @manopapad Fix copyright notices
authored
1 %%% Copyright 2010-2011 Manolis Papadakis <manopapad@gmail.com>,
2 %%% Eirini Arvaniti <eirinibob@gmail.com>
3 %%% and Kostis Sagonas <kostis@cs.ntua.gr>
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
4 %%%
5 %%% This file is part of PropEr.
6 %%%
7 %%% PropEr is free software: you can redistribute it and/or modify
8 %%% it under the terms of the GNU General Public License as published by
9 %%% the Free Software Foundation, either version 3 of the License, or
10 %%% (at your option) any later version.
11 %%%
12 %%% PropEr is distributed in the hope that it will be useful,
13 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
14 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 %%% GNU General Public License for more details.
16 %%%
17 %%% You should have received a copy of the GNU General Public License
18 %%% along with PropEr. If not, see <http://www.gnu.org/licenses/>.
19
b36c594 @manopapad Remove e-mail addresses from EDoc output
authored
20 %%% @copyright 2010-2011 Manolis Papadakis, Eirini Arvaniti and Kostis Sagonas
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
21 %%% @version {@version}
b36c594 @manopapad Remove e-mail addresses from EDoc output
authored
22 %%% @author Eirini Arvaniti
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
23
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
24 %%% @doc This module defines the `proper_statem' behaviour, useful for testing
25 %%% stateful reactive systems whose internal state and side-effects are
26 %%% specified via an abstract state machine. Given a callback module
27 %%% implementing the `proper_statem' behaviour (i.e. defining an abstract state
28 %%% machine of the system under test), PropEr can generate random symbolic
29 %%% sequences of calls to that system.
30 %%% As a next step, generated symbolic calls are actually performed, while
31 %%% monitoring the system's responses to ensure it behaves as expected. Upon
32 %%% failure, the shrinking mechanism attempts to find a minimal sequence of
33 %%% calls provoking the same error.
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
34 %%%
35 %%% When including the <code>"proper/include/proper.hrl"</code> header file,
36 %%% all <a href="#index">API functions </a> of {@module} are automatically
37 %%% imported, unless `PROPER_NO_IMPORTS' is defined.
38 %%%
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
39 %%% === The role of commands ===
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
40 %%% Testcases generated for testing a stateful system are lists of symbolic API
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
41 %%% calls to that system. Symbolic representation has several benefits, which
42 %%% are listed here in increasing order of importance:
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
43 %%% <ul>
44 %%% <li>Generated testcases are easier to read and understand.</li>
45 %%% <li>Failing testcases are easier to shrink.</li>
46 %%% <li>The generation phase is side-effect free and this results in
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
47 %%% repeatable testcases, which is essential for correct shrinking.</li>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
48 %%% </ul>
49 %%% Since the actual results of symbolic calls are not known at generation time,
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
50 %%% we use symbolic variables ({@type symb_var()}) to refer to them.
51 %%% A command ({@type command()}) is a symbolic term, used to bind a symbolic
52 %%% variable to the result of a symbolic call. For example:
53 %%%
54 %%% ```[{set, {var,1}, {call,erlang,put,[a,42]}},
55 %%% {set, {var,2}, {call,erlang,erase,[a]}},
56 %%% {set, {var,3}, {call,erlang,put,[b,{var,2}]}}]'''
57 %%%
58 %%% is a command sequence that could be used to test the process dictionary.
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
59 %%% In this example, the first call stores the pair `{a,42}' in the process
60 %%% dictionary, while the second one deletes it. Then, a new pair `{b,{var,2}}'
61 %%% is stored. `{var,2}' is a symbolic variable bound to the result of
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
62 %%% `erlang:erase/1'. This result is not known at generation time, since none of
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
63 %%% these operations is performed at that time. After evaluating the command
64 %%% sequence at runtime, the process dictionary will eventually contain the
65 %%% pair `{b,42}'.
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
66 %%%
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
67 %%% === The abstract model-state ===
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
68 %%% In order to be able to test impure code, we need a way to track its
69 %%% internal state (at least the useful part of it). To this end, we use an
70 %%% abstract state machine representing the possible configurations of the
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
71 %%% system under test. When referring to the <i>model state</i>, we mean the
72 %%% state of the abstract state machine. The <i>model state</i> can be either
73 %%% symbolic or dynamic:
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
74 %%% <ul>
75 %%% <li>During command generation, we use symbolic variables to bind the
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
76 %%% results of symbolic calls. Therefore, the model state might
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
77 %%% (and usually does) contain symbolic variables and/or symbolic calls, which
78 %%% are necessary to operate on symbolic variables. Thus, we refer to it as
79 %%% symbolic state. For example, assuming that the internal state of the
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
80 %%% process dictionary is modeled as a proplist, the model state after
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
81 %%% generating the previous command sequence will be `[{b,{var,2}}]'.</li>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
82 %%% <li>During runtime, symbolic calls are evaluated and symbolic variables are
83 %%% replaced by their corresponding real values. Now we refer to the state as
84 %%% dynamic state. After running the previous command sequence, the model state
85 %%% will be `[{b,42}]'.</li>
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
86 %%% </ul>
87 %%%
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
88 %%% === The callback functions ===
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
89 %%% The following functions must be exported from the callback module
90 %%% implementing the abstract state machine:
91 %%% <ul>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
92 %%% <li>`initial_state() ::' {@type symbolic_state()}
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
93 %%% <p>Specifies the symbolic initial state of the state machine. This state
94 %%% will be evaluated at command execution time to produce the actual initial
95 %%% state. The function is not only called at command generation time, but
96 %%% also in order to initialize the state every time the command sequence is
97 %%% run (i.e. during normal execution, while shrinking and when checking a
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
98 %%% counterexample). For this reason, it should be deterministic and
99 %%% self-contained.</p></li>
100 %%% <li>`command(S::'{@type symbolic_state()}`) ::' {@type proper_types:type()}
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
101 %%% <p>Generates a symbolic call to be included in the command sequence,
102 %%% given the current state `S' of the abstract state machine. However,
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
103 %%% before the call is actually included, a precondition is checked. This
104 %%% function will be repeatedly called to produce the next call to be
105 %%% included in the test case.</p></li>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
106 %%% <li>`precondition(S::'{@type symbolic_state()}`,
107 %%% Call::'{@type symb_call()}`) :: boolean()'
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
108 %%% <p>Specifies the precondition that should hold so that `Call' can be
109 %%% included in the command sequence, given the current state `S' of the
110 %%% abstract state machine. In case precondition doesn't hold, a new call is
111 %%% chosen using the `command/1' generator. If preconditions are very strict,
112 %%% it will take a lot of tries for PropEr to randomly choose a valid command.
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
113 %%% Testing will be stopped in case the `constraint_tries' limit is reached
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
114 %%% (see the 'Options' section in the {@link proper} module documentation).
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
115 %%% Preconditions are also important for correct shrinking of failing
116 %%% testcases. When shrinking command sequences, we try to eliminate commands
117 %%% that do not contribute to failure, ensuring that all preconditions still
118 %%% hold. Validating preconditions is necessary because during shrinking we
119 %%% usually attempt to perform a call with the system being in a state
120 %%% different from the state it was when initially running the test.</p></li>
121 %%% <li>`postcondition(S::'{@type dynamic_state()}`,
122 %%% Call::'{@type symbolic_call()}`,
123 %%% Res::term()) :: boolean()'
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
124 %%% <p>Specifies the postcondition that should hold about the result `Res' of
125 %%% performing `Call', given the dynamic state `S' of the abstract state
126 %%% machine prior to command execution. This function is called during
127 %%% runtime, this is why the state is dynamic.</p></li>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
128 %%% <li>`next_state(S::'{@type symbolic_state()} `|' {@type dynamic_state()}`,
129 %%% Res::term(),
130 %%% Call::'{@type symbolic_call()}`) ::'
131 %%% {@type symbolic_state()} `|' {@type dynamic_state()}
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
132 %%% <p>Specifies the next state of the abstract state machine, given the
133 %%% current state `S', the symbolic `Call' chosen and its result `Res'. This
134 %%% function is called both at command generation and command execution time
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
135 %%% in order to update the model state, therefore the state `S' and the
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
136 %%% result `Res' can be either symbolic or dynamic.</p></li>
137 %%% </ul>
138 %%%
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
139 %%% === The property used ===
140 %%% Each test consists of two phases:
141 %%% <ul>
142 %%% <li>As a first step, PropEr generates random symbolic command sequences
143 %%% deriving information from the callback module implementing the abstract
144 %%% state machine. This is the role of {@link commands/1} generator.</li>
145 %%% <li>As a second step, command sequences are executed so as to check that
146 %%% the system behaves as expected. This is the role of
147 %%% {@link run_commands/2}, a function that evaluates a symbolic command
148 %%% sequence according to an abstract state machine specification.</li>
149 %%% </ul>
150 %%%
151 %%% These two phases are encapsulated in the following property, which can be
152 %%% used for testing the process dictionary:
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
153 %%%
154 %%% ```prop_pdict() ->
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
155 %%% ?FORALL(Cmds, proper_statem:commands(?MODULE),
156 %%% begin
157 %%% {_History, _State, Result} = proper_statem:run_commands(?MODULE, Cmds),
158 %%% cleanup(),
159 %%% Result =:= ok
160 %%% end).'''
161 %%%
162 %%% When testing impure code, it is very important to keep each test
163 %%% self-contained. For this reason, almost every property for testing stateful
164 %%% systems contains some clean-up code. Such code is necessary to put the
165 %%% system in a known state, so that the next test can be executed
166 %%% independently from previous ones.
167 %%%
168 %%% == Parallel testing ==
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
169 %%% After ensuring that a system's behaviour can be described via an abstract
170 %%% state machine when commands are executed sequentially, it is possible to
171 %%% move to parallel testing. The same state machine can be used to generate
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
172 %%% command sequences that will be executed in parallel to test for race
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
173 %%% conditions. A parallel testcase ({@type parallel_testcase()}) consists of
174 %%% a sequential and a parallel component. The sequential component is a
175 %%% command sequence that is run first to put the system in a random state.
9777cfa @EiriniArvaniti small fix in parallel statem testing
EiriniArvaniti authored
176 %%% The parallel component is a list containing 2 command sequences to be
177 %%% executed in parallel, each of them in a separate newly-spawned process.
178 %%%
179 %%% Generating parallel test cases involves the following actions. Initially,
180 %%% we generate a command sequence deriving information from the abstract
181 %%% state machine specification, as in the case of sequential statem testing.
182 %%% Then, we parallelize a random suffix (up to 12 commands) of the initial
183 %%% sequence by splitting it into 2 subsequences that will be executed
184 %%% concurrently. Limitations arise from the fact that each subsequence should
7fabfaa @puzza007 spelling
puzza007 authored
185 %%% be a <i>valid</i> command sequence (i.e. all commands should satisfy
9777cfa @EiriniArvaniti small fix in parallel statem testing
EiriniArvaniti authored
186 %%% preconditions and use only symbolic variables bound to the results of
187 %%% preceding calls in the same sequence). Furthermore, we apply an additional
188 %%% check: we have to ensure that preconditions are satisfied in all possible
189 %%% interleavings of the concurrent tasks. Otherwise, an exception might be
190 %%% raised during parallel execution and lead to unexpected (and unwanted) test
191 %%% failure. In case these constraints cannot be satisfied for a specific test
192 %%% case, the test case will be executed sequentially. Then an `f' is printed
193 %%% on screen to inform the user. This usually means that preconditions need
194 %%% to become less strict for parallel testing to work.
195 %%%
196 %%% After running a parallel testcase, PropEr uses the state machine
197 %%% specification to check if the results observed could have been produced by
198 %%% a possible serialization of the parallel component. If no such serialization
199 %%% is possible, then an atomicity violation has been detected. In this case,
200 %%% the shrinking mechanism attempts to produce a counterexample that is minimal
201 %%% in terms of concurrent operations. Properties for parallel testing are very
202 %%% similar to those used for sequential testing.
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
203 %%%
204 %%% ```prop_parallel_testing() ->
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
205 %%% ?FORALL(Testcase, proper_statem:parallel_commands(?MODULE),
206 %%% begin
207 %%% {_Sequential, _Parallel, Result} = proper_statem:run_parallel_commands(?MODULE, Testcase),
208 %%% cleanup(),
209 %%% Result =:= ok
210 %%% end).'''
211 %%%
9777cfa @EiriniArvaniti small fix in parallel statem testing
EiriniArvaniti authored
212 %%% Please note that the actual interleaving of commands of the parallel
c4a7774 @EiriniArvaniti corrections in documentation of the stateful modules
EiriniArvaniti authored
213 %%% component depends on the Erlang scheduler, which is too deterministic.
214 %%% For PropEr to be able to detect race conditions, the code of the system
215 %%% under test should be instrumented with `erlang:yield/0' calls to the
216 %%% scheduler.
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
217 %%% @end
55f207a @EiriniArvaniti first commit of statem branch
EiriniArvaniti authored
218
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
219 -module(proper_statem).
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
220 -export([behaviour_info/1]).
fb6773e @EiriniArvaniti moved command generators to proper_statem.erl
EiriniArvaniti authored
221 -export([commands/1, commands/2, parallel_commands/1, parallel_commands/2,
222 more_commands/2]).
07d7fb8 @EiriniArvaniti fix in command execution
EiriniArvaniti authored
223 -export([run_commands/2, run_commands/3, run_parallel_commands/2,
224 run_parallel_commands/3]).
225 -export([state_after/2, command_names/1, zip/2]).
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
226
55f207a @EiriniArvaniti first commit of statem branch
EiriniArvaniti authored
227 -include("proper_internal.hrl").
0d7d57a @kostis Tidier struct cleanup
kostis authored
228
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
229 -define(WORKERS, 2).
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
230 -define(LIMIT, 12).
d6db210 @EiriniArvaniti statem shrinking,validating and exception handling fixes
EiriniArvaniti authored
231
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
232
233 %% -----------------------------------------------------------------------------
234 %% Exported only for testing purposes
235 %% -----------------------------------------------------------------------------
236
237 -export([index/2, all_insertions/3, insert_all/2]).
c337159 @EiriniArvaniti discarding invalid commands when shrinking
EiriniArvaniti authored
238 -export([is_valid/4, args_defined/2]).
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
239 -export([get_next/6, mk_first_comb/3, fix_parallel/8, mk_dict/2]).
240 -export([execute/4, check/6, run/3, get_initial_state/2]).
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
241
242
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
243 %% -----------------------------------------------------------------------------
244 %% Type declarations
245 %% -----------------------------------------------------------------------------
246
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
247 %% @type symbolic_state()
248 -type symbolic_state() :: term().
249 %% @type dynamic_state()
250 -type dynamic_state() :: term().
251 -type symb_var() :: {'var',pos_integer()}.
252 -type symb_call() :: {'call',mod_name(),fun_name(),[term()]}.
253 -type command() :: {'set',symb_var(),symb_call()}
254 | {'init',symbolic_state()}.
255 -type command_list() :: [command()].
256 -type parallel_testcase() :: {command_list(),[command_list()]}.
257 -type parallel_history() :: [{command(),term()}].
258 -type history() :: [{dynamic_state(),term()}].
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
259 -type statem_result() :: 'ok'
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
260 | 'initialization_error'
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
261 | {'precondition', 'false' | proper:exception()}
262 | {'postcondition', 'false' | proper:exception()}
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
263 | proper:exception()
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
264 | 'no_possible_interleaving'.
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
265 -type indices() :: [pos_integer()].
266 -type combination() :: [{pos_integer(),indices()}].
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
267 -type lookup() :: orddict:orddict().
8562087 @EiriniArvaniti some tests added
EiriniArvaniti authored
268
67e86e2 @EiriniArvaniti Merge branch 'fsm' of github.com:manopapad/proper into fsm
EiriniArvaniti authored
269 -export_type([symb_var/0, symb_call/0, statem_result/0]).
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
270
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
271
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
272 %% -----------------------------------------------------------------------------
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
273 %% Proper_statem behaviour
274 %% ----------------------------------------------------------------------------
275
276 %% @doc Specifies the callback functions that should be exported from a module
277 %% implementing the `proper_statem' behaviour.
278
279 -spec behaviour_info('callbacks') -> [{fun_name(),arity()}].
280 behaviour_info(callbacks) ->
281 [{initial_state,0},
282 {command,1},
283 {precondition,2},
284 {postcondition,3},
285 {next_state,3}];
286 behaviour_info(_Attribute) ->
287 undefined.
288
289
290 %% -----------------------------------------------------------------------------
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
291 %% Sequential command generation
292 %% -----------------------------------------------------------------------------
293
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
294 %% @doc A special PropEr type which generates random command sequences,
295 %% according to an absract state machine specification. The function takes as
296 %% input the name of a callback module, which contains the state machine
297 %% specification. The initial state is computed by `Mod:initial_state/0'.
298
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
299 -spec commands(mod_name()) -> proper_types:type().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
300 commands(Mod) ->
301 ?LET(InitialState, ?LAZY(Mod:initial_state()),
302 ?SUCHTHAT(
303 Cmds,
304 ?LET(List,
305 ?SIZED(Size,
306 proper_types:noshrink(
307 commands(Size, Mod, InitialState, 1))),
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
308 proper_types:shrink_list(List)),
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
309 is_valid(Mod, InitialState, Cmds, []))).
310
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
311 %% @doc Similar to {@link commands/1}, but generated command sequences always
312 %% start at a given state. In this case, the first command is always
313 %% `{init,InitialState}' and is used to correctly initialize the state
314 %% every time the command sequence is run (i.e. during normal execution,
315 %% while shrinking and when checking a counterexample). In this case,
316 %% `Mod:initial_state/0' is never called.
fb6773e @EiriniArvaniti moved command generators to proper_statem.erl
EiriniArvaniti authored
317
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
318 -spec commands(mod_name(), symbolic_state()) -> proper_types:type().
319 commands(Mod, InitialState) ->
320 ?SUCHTHAT(
321 Cmds,
322 ?LET(CmdTail,
323 ?LET(List,
324 ?SIZED(Size,
325 proper_types:noshrink(
326 commands(Size, Mod, InitialState, 1))),
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
327 proper_types:shrink_list(List)),
328 [{init,InitialState}|CmdTail]),
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
329 is_valid(Mod, InitialState, Cmds, [])).
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
330
331 -spec commands(size(), mod_name(), symbolic_state(), pos_integer()) ->
332 proper_types:type().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
333 commands(Size, Mod, State, Count) ->
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
334 ?LAZY(
335 proper_types:frequency(
336 [{1, []},
337 {Size, ?LET(Call,
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
338 ?SUCHTHAT(X, Mod:command(State),
339 Mod:precondition(State, X)),
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
340 begin
341 Var = {var,Count},
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
342 NextState = Mod:next_state(State, Var, Call),
343 ?LET(
344 Cmds,
345 commands(Size-1, Mod, NextState, Count+1),
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
346 [{set,Var,Call}|Cmds])
347 end)}])).
ecce5f5 @kostis More cleanups
kostis authored
348
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
349 %% @doc Increases the expected length of command sequences generated from
350 %% `CmdType' by a factor `N'.
351
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
352 -spec more_commands(pos_integer(), proper_types:type()) -> proper_types:type().
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
353 more_commands(N, CmdType) ->
354 ?SIZED(Size, proper_types:resize(Size * N, CmdType)).
fb6773e @EiriniArvaniti moved command generators to proper_statem.erl
EiriniArvaniti authored
355
c92bc38 @EiriniArvaniti code refactorings in sequential command generation
EiriniArvaniti authored
356
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
357 %% -----------------------------------------------------------------------------
358 %% Parallel command generation
359 %% -----------------------------------------------------------------------------
360
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
361 %% @doc A special PropEr type which generates parallel testcases,
362 %% according to an absract state machine specification. The function takes as
363 %% input the name of a callback module, which contains the state machine
364 %% specification. The initial state is computed by `Mod:initial_state/0'.
365
6d826f9 @manopapad Fix copyright notices
authored
366 -spec parallel_commands(mod_name()) -> proper_types:type().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
367 parallel_commands(Mod) ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
368 ?LET({ShrunkSeq, ShrunkPar},
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
369 ?LET({Seq, Par},
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
370 proper_types:noshrink(parallel_gen(Mod)),
371 parallel_shrinker(Mod, Seq, Par)),
372 move_shrinker(ShrunkSeq, ShrunkPar, ?WORKERS)).
fb6773e @EiriniArvaniti moved command generators to proper_statem.erl
EiriniArvaniti authored
373
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
374 %% @doc Similar to {@link parallel_commands/1}, but generated command sequences
375 %% always start at a given state.
376
6d826f9 @manopapad Fix copyright notices
authored
377 -spec parallel_commands(mod_name(), symbolic_state()) -> proper_types:type().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
378 parallel_commands(Mod, InitialState) ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
379 ?LET({ShrunkSeq, ShrunkPar},
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
380 ?LET({Seq, Par},
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
381 proper_types:noshrink(parallel_gen(Mod, InitialState)),
382 parallel_shrinker(Mod, Seq, Par)),
383 move_shrinker(ShrunkSeq, ShrunkPar, ?WORKERS)).
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
384
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
385 -spec parallel_gen(mod_name()) -> proper_types:type().
386 parallel_gen(Mod) ->
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
387 ?LET(Seq,
388 commands(Mod),
389 mk_parallel_testcase(Mod, Seq)).
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
390
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
391 -spec parallel_gen(mod_name(), symbolic_state()) -> proper_types:type().
392 parallel_gen(Mod, InitialState) ->
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
393 ?LET(Seq,
394 commands(Mod, InitialState),
395 mk_parallel_testcase(Mod, Seq)).
396
397 -spec mk_parallel_testcase(mod_name(), command_list()) -> proper_types:type().
398 mk_parallel_testcase(Mod, Seq) ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
399 {State, SymbEnv} = state_env_after(Mod, Seq),
400 Count = case SymbEnv of
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
401 [] -> 1;
402 [{var,N}|_] -> N + 1
403 end,
404 ?LET(Parallel,
405 ?SUCHTHAT(C, commands(?LIMIT, Mod, State, Count),
406 length(C) > ?WORKERS),
407 begin
408 LenPar = length(Parallel),
409 Len = LenPar div ?WORKERS,
410 Comb = mk_first_comb(LenPar, Len, ?WORKERS),
411 LookUp = orddict:from_list(mk_dict(Parallel, 1)),
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
412 {Seq, fix_parallel(LenPar, Len, Comb, LookUp, Mod,
413 State, SymbEnv, ?WORKERS)}
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
414 end).
fddd18f @EiriniArvaniti re-writing statem to use only PropEr public API
EiriniArvaniti authored
415
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
416 -spec parallel_shrinker(mod_name(), command_list(), [command_list()]) ->
417 proper_types:type().
418 parallel_shrinker(Mod, [{init,I} = Init|Seq], Parallel) ->
419 ?SUCHTHAT({Seq1, Parallel1},
420 ?LET(ParInstances,
421 [proper_types:shrink_list(P) || P <- Parallel],
422 ?LET(SeqInstance,
423 proper_types:shrink_list(Seq),
424 {[Init|SeqInstance], ParInstances})),
425 lists:all(
426 fun(P) -> is_valid(Mod, I, Seq1 ++ P, []) end,
427 Parallel1));
428 parallel_shrinker(Mod, Seq, Parallel) ->
429 I= Mod:initial_state(),
430 ?SUCHTHAT({Seq1, Parallel1},
431 ?LET(ParInstances,
432 [proper_types:shrink_list(P) || P <- Parallel],
433 ?LET(SeqInstance,
434 proper_types:shrink_list(Seq),
435 {SeqInstance, ParInstances})),
436 lists:all(
437 fun(P) -> is_valid(Mod, I, Seq1 ++ P, []) end,
438 Parallel1)).
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
439
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
440 -spec move_shrinker(command_list(), [command_list()], pos_integer()) ->
441 proper_types:type().
442 move_shrinker(Seq, Par, 1) ->
443 ?SHRINK({Seq, Par},
444 [{Seq ++ Slice, remove_slice(1, Slice, Par)}
445 || Slice <- get_slices(lists:nth(1, Par))]);
446 move_shrinker(Seq, Par, I) ->
447 ?LET({NewSeq, NewPar},
448 ?SHRINK({Seq, Par},
449 [{Seq ++ Slice, remove_slice(I, Slice, Par)}
450 || Slice <- get_slices(lists:nth(I, Par))]),
451 move_shrinker(NewSeq, NewPar, I-1)).
ecce5f5 @kostis More cleanups
kostis authored
452
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
453
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
454 %% -----------------------------------------------------------------------------
455 %% Sequential command execution
456 %% -----------------------------------------------------------------------------
457
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
458 %% @doc Evaluates a given symbolic command sequence `Cmds' according to the
459 %% state machine specified in `Mod'. The result is a triple of the form<br/>
460 %% `{History, DynamicState, Result}', where:
461 %% <ul>
462 %% <li>`History' contains the execution history of all commands that were
463 %% executed without raising an exception. It contains tuples of the form
464 %% {{@type dynamic_state()}, {@type term()}}, specifying the state prior to
465 %% command execution and the actual result of the command.</li>
466 %% <li>`DynamicState' contains the state of the abstract state machine at
467 %% the moment when execution stopped.</li>
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
468 %% <li>`Result' specifies the outcome of command execution. It can be
469 %% classified in one of the following categories:
470 %% <ul>
471 %% <li><b>ok</b>
472 %% <p>All commands were successfully run and all postconditions were true.
473 %% </p></li>
474 %% <li><b>initialization error</b>
475 %% <p>There was an error while evaluating the initial state.</p></li>
476 %% <li><b>postcondition error</b>
477 %% <p>A postcondition was false or raised an exception.</p></li>
478 %% <li><b>precondition error</b>
479 %% <p>A precondition was false or raised an exception.</p></li>
480 %% <li><b>exception</b>
481 %% <p>An exception was raised while running a command.</p></li>
482 %% </ul></li>
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
483 %% </ul>
484
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
485 -spec run_commands(mod_name(), command_list()) ->
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
486 {history(),dynamic_state(),statem_result()}.
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
487 run_commands(Mod, Cmds) ->
488 run_commands(Mod, Cmds, []).
55f207a @EiriniArvaniti first commit of statem branch
EiriniArvaniti authored
489
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
490 %% @doc Similar to {@link run_commands/2}, but also accepts an environment,
491 %% used for symbolic variable evaluation during command execution. The
492 %% environment consists of `{Key::atom(), Value::term()}' pairs. Keys may be
493 %% used in symbolic variables (i.e. `{var,Key}') whithin the command sequence
494 %% `Cmds'. These symbolic variables will be replaced by their corresponding
495 %% `Value' during command execution.
55f207a @EiriniArvaniti first commit of statem branch
EiriniArvaniti authored
496
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
497 -spec run_commands(mod_name(), command_list(), proper_symb:var_values()) ->
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
498 {history(),dynamic_state(),statem_result()}.
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
499 run_commands(Mod, Cmds, Env) ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
500 element(1, run(Mod, Cmds, Env)).
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
501
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
502 %% @private
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
503 -spec run(mod_name(), command_list(), proper_symb:var_values()) ->
504 {{history(),dynamic_state(),statem_result()}, proper_symb:var_values()}.
505 run(Mod, Cmds, Env) ->
506 InitialState = get_initial_state(Mod, Cmds),
507 try proper_symb:eval(Env, InitialState) of
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
508 DynState ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
509 run_commands(Cmds, Env, Mod, [], DynState)
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
510 catch
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
511 _Exc:_Reason ->
512 {{[], undefined, initialization_error}, []}
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
513 end.
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
514
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
515 -spec run_commands(command_list(), proper_symb:var_values(), mod_name(),
516 history(), dynamic_state()) ->
517 {{history(),dynamic_state(),statem_result()}, proper_symb:var_values()}.
518 run_commands(Cmds, Env, Mod, History, State) ->
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
519 case Cmds of
6d826f9 @manopapad Fix copyright notices
authored
520 [] ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
521 {{lists:reverse(History), State, ok}, Env};
4dd4f80 @EiriniArvaniti store initial state in process dictionnary
EiriniArvaniti authored
522 [{init,_S}|Rest] ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
523 run_commands(Rest, Env, Mod, History, State);
4f6aa21 @EiriniArvaniti fixed bug in handling false preconditions
EiriniArvaniti authored
524 [{set, {var,V}, {call,M,F,A}}|Rest] ->
6d826f9 @manopapad Fix copyright notices
authored
525 M2 = proper_symb:eval(Env, M),
526 F2 = proper_symb:eval(Env, F),
0d7d57a @kostis Tidier struct cleanup
kostis authored
527 A2 = proper_symb:eval(Env, A),
4f6aa21 @EiriniArvaniti fixed bug in handling false preconditions
EiriniArvaniti authored
528 Call = {call,M2,F2,A2},
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
529 case check_precondition(Mod, State, Call) of
07d7fb8 @EiriniArvaniti fix in command execution
EiriniArvaniti authored
530 true ->
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
531 case safe_apply(M2, F2, A2) of
532 {ok,Res} ->
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
533 Env2 = [{V,Res}|Env],
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
534 State2 = proper_symb:eval(
535 Env2, Mod:next_state(State, Res, Call)),
4f6aa21 @EiriniArvaniti fixed bug in handling false preconditions
EiriniArvaniti authored
536 History2 = [{State,Res}|History],
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
537 case check_postcondition(Mod, State, Call, Res) of
d6db210 @EiriniArvaniti statem shrinking,validating and exception handling fixes
EiriniArvaniti authored
538 true ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
539 run_commands(Rest, Env2, Mod, History2,
540 State2);
07d7fb8 @EiriniArvaniti fix in command execution
EiriniArvaniti authored
541 false ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
542 {{lists:reverse(History2), State2,
543 {postcondition,false}}, []};
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
544 {exception,_,_,_} = Exception ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
545 {{lists:reverse(History2), State2,
546 {postcondition,Exception}}, []}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
547 end;
548 {error,Exception} ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
549 {{lists:reverse(History), State, Exception}, []}
07d7fb8 @EiriniArvaniti fix in command execution
EiriniArvaniti authored
550 end;
551 false ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
552 {{lists:reverse(History), State, {precondition,false}}, []};
553 {exception,_,_,_} = Exc ->
554 {{lists:reverse(History), State, {precondition,Exc}}, []}
55f207a @EiriniArvaniti first commit of statem branch
EiriniArvaniti authored
555 end
556 end.
557
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
558 -spec check_precondition(mod_name(), dynamic_state(), symb_call()) ->
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
559 boolean() | proper:exception().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
560 check_precondition(Mod, State, Call) ->
561 try Mod:precondition(State, Call)
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
562 catch
563 Kind:Reason ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
564 {exception, Kind, Reason, erlang:get_stacktrace()}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
565 end.
566
567 -spec check_postcondition(mod_name(), dynamic_state(), symb_call(), term()) ->
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
568 boolean() | proper:exception().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
569 check_postcondition(Mod, State, Call, Res) ->
570 try Mod:postcondition(State, Call, Res)
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
571 catch
572 Kind:Reason ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
573 {exception, Kind, Reason, erlang:get_stacktrace()}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
574 end.
07d7fb8 @EiriniArvaniti fix in command execution
EiriniArvaniti authored
575
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
576 -spec safe_apply(mod_name(), fun_name(), [term()]) ->
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
577 {'ok', term()} | {'error', proper:exception()}.
6d826f9 @manopapad Fix copyright notices
authored
578 safe_apply(M, F, A) ->
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
579 try apply(M, F, A) of
b08079d @kostis Tidier cleanups
kostis authored
580 Result -> {ok, Result}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
581 catch
582 Kind:Reason ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
583 {error, {exception, Kind, Reason, erlang:get_stacktrace()}}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
584 end.
585
b141829 @EiriniArvaniti documentation for statem
EiriniArvaniti authored
586
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
587 %% -----------------------------------------------------------------------------
588 %% Parallel command execution
589 %% -----------------------------------------------------------------------------
590
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
591 %% @doc Runs a given parallel testcase according to the state machine
592 %% specified in `Mod'. The result is a triple of the form<br/>
593 %% `@{Sequential_history, Parallel_history, Result@}', where:
594 %% <ul>
595 %% <li>`Sequential_history' contains the execution history of the
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
596 %% sequential component.</li>
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
597 %% <li>`Parallel_history' contains the execution history of each of the
598 %% concurrent tasks.</li>
599 %% <li>`Result' specifies the outcome of the attemp to serialize command
600 %% execution, based on the results observed. It can be one of the following:
601 %% <ul><li> `ok' </li><li> `no_possible_interleaving' </li></ul> </li>
602 %% </ul>
603
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
604 -spec run_parallel_commands(mod_name(), parallel_testcase()) ->
605 {history(),[parallel_history()],statem_result()}.
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
606 run_parallel_commands(Mod, {_Sequential, _Parallel} = Testcase) ->
607 run_parallel_commands(Mod, Testcase, []).
608
609 %% @doc Similar to {@link run_parallel_commands/2}, but also accepts an
610 %% environment used for symbolic variable evaluation, exactly as described in
611 %% {@link run_commands/3}.
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
612
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
613 -spec run_parallel_commands(mod_name(), parallel_testcase(),
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
614 proper_symb:var_values()) ->
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
615 {history(),[parallel_history()],statem_result()}.
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
616 run_parallel_commands(Mod, {Sequential, Parallel}, Env) ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
617 case run(Mod, Sequential, Env) of
618 {{Seq_history, State, ok}, SeqEnv} ->
619 F = fun(T) -> execute(T, SeqEnv, Mod, []) end,
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
620 Parallel_history = pmap(F, Parallel),
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
621 case check(Mod, State, SeqEnv, false, [], Parallel_history) of
6d826f9 @manopapad Fix copyright notices
authored
622 true ->
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
623 {Seq_history, Parallel_history, ok};
624 false ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
625 {Seq_history, Parallel_history, no_possible_interleaving}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
626 end;
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
627 {{Seq_history, _, Res}, _} ->
628 {Seq_history, [], Res}
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
629 end.
630
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
631 %% @private
632 -spec execute(command_list(), proper_symb:var_values(), mod_name(),
633 parallel_history()) -> parallel_history().
634 execute(Cmds, Env, Mod, History) ->
635 case Cmds of
636 [] ->
637 lists:reverse(History);
638 [{set, {var,V}, {call,M,F,A}} = Cmd|Rest] ->
639 M2 = proper_symb:eval(Env, M),
640 F2 = proper_symb:eval(Env, F),
641 A2 = proper_symb:eval(Env, A),
642 Res = apply(M2, F2, A2),
643 Env2 = [{V,Res}|Env],
644 History2 = [{Cmd,Res}|History],
645 execute(Rest, Env2, Mod, History2)
646 end.
647
648 -spec pmap(fun((command_list()) -> parallel_history()), [command_list()]) ->
649 [parallel_history()].
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
650 pmap(F, L) ->
651 await(lists:reverse(spawn_jobs(F,L))).
652
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
653 -spec spawn_jobs(fun((command_list()) -> parallel_history()),
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
654 [command_list()]) -> [pid()].
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
655 spawn_jobs(F, L) ->
656 Parent = self(),
9777cfa @EiriniArvaniti small fix in parallel statem testing
EiriniArvaniti authored
657 [spawn_link_cp(fun() -> Parent ! {self(),catch {ok,F(X)}} end)
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
658 || X <- L].
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
659
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
660 -spec await([pid()]) -> [parallel_history()].
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
661 await(Pids) ->
662 await_tr(Pids, []).
663
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
664 -spec await_tr([pid()], [parallel_history()]) -> [parallel_history()].
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
665 await_tr([], Acc) -> Acc;
666 await_tr([H|T], Acc) ->
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
667 receive
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
668 {H, {ok, Res}} ->
669 await_tr(T, [Res|Acc]);
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
670 {H, {'EXIT',_} = Err} ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
671 _ = [exit(Pid, kill) || Pid <- T],
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
672 _ = [receive {P,_} -> d_ after 0 -> i_ end || P <- T],
673 erlang:error(Err)
674 end.
675
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
676 %% @private
6d826f9 @manopapad Fix copyright notices
authored
677 -spec check(mod_name(), dynamic_state(), proper_symb:var_values(),
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
678 boolean(), [parallel_history()], [parallel_history()]) -> boolean().
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
679 check(_Mod, _State, _Env, _Changed, [], []) ->
0d7d57a @kostis Tidier struct cleanup
kostis authored
680 true;
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
681 check(_Mod, _State, _Env, false, _Tried, []) ->
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
682 false;
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
683 check(Mod, State, Env, true, Tried, []) ->
684 check(Mod, State, Env, false, [], Tried);
685 check(Mod, State, Env, Changed, Tried, [P|ToTry]) ->
6d826f9 @manopapad Fix copyright notices
authored
686 case P of
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
687 [] ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
688 check(Mod, State, Env, Changed, Tried, ToTry);
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
689 [H|Tail] ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
690 {{set, {var,N}, {call,M,F,A}}, Res} = H,
691 M_ = proper_symb:eval(Env, M),
692 F_ = proper_symb:eval(Env, F),
693 A_ = proper_symb:eval(Env, A),
694 Call = {call,M_,F_,A_},
695 case Mod:postcondition(State, Call, Res) of
6d826f9 @manopapad Fix copyright notices
authored
696 true ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
697 Env2 = [{N, Res}|Env],
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
698 NextState = proper_symb:eval(
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
699 Env2, Mod:next_state(State, Res, Call)),
700 check(Mod, NextState, Env2, true, Tried, [Tail|ToTry])
701 orelse check(Mod, State, Env, Changed,
702 [P|Tried], ToTry);
8c08e0d @EiriniArvaniti added parallel_command shrinkers
EiriniArvaniti authored
703 false ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
704 check(Mod, State, Env, Changed, [P|Tried], ToTry)
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
705 end
8c08e0d @EiriniArvaniti added parallel_command shrinkers
EiriniArvaniti authored
706 end.
0d7d57a @kostis Tidier struct cleanup
kostis authored
707
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
708
709 %% -----------------------------------------------------------------------------
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
710 %% Other API functions
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
711 %% -----------------------------------------------------------------------------
712
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
713 %% @doc Extracts the names of the commands from a given command sequence, in
714 %% the form of MFAs. It is useful in combination with functions such as
715 %% {@link proper:aggregate/2} in order to collect statistics about command
716 %% execution.
717
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
718 -spec command_names(command_list()) -> [mfa()].
719 command_names(Cmds) ->
720 [{M, F, length(Args)} || {set, _Var, {call,M,F,Args}} <- Cmds].
721
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
722 %% @doc Returns the symbolic state after running a given command sequence,
723 %% according to the state machine specification found in `Mod'. The commands
724 %% are not actually executed.
725
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
726 -spec state_after(mod_name(), command_list()) -> symbolic_state().
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
727 state_after(Mod, Cmds) ->
728 element(1, state_env_after(Mod, Cmds)).
729
730 -spec state_env_after(mod_name(), command_list()) ->
731 {symbolic_state(), [symb_var()]}.
732 state_env_after(Mod, Cmds) ->
733 lists:foldl(fun({init,S}, _) ->
734 {S, []};
735 ({set,Var,Call}, {S,Vars}) ->
736 {Mod:next_state(S, Var, Call), [Var|Vars]}
737 end,
738 {get_initial_state(Mod, Cmds), []},
739 Cmds).
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
740
5d18293 @EiriniArvaniti some documentation added
EiriniArvaniti authored
741 %% @doc Behaves like `lists:zip/2', but the input lists do no not necessarily
742 %% have equal length. Zipping stops when the shortest list stops. This is
743 %% useful for zipping a command sequence with its (failing) execution history.
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
744
745 -spec zip([A], [B]) -> [{A,B}].
746 zip(X, Y) ->
747 zip(X, Y, []).
748
749 -spec zip([A], [B], [{A,B}]) -> [{A,B}].
750 zip([], _, Accum) -> lists:reverse(Accum);
751 zip(_, [], Accum) -> lists:reverse(Accum);
752 zip([X|Tail1], [Y|Tail2], Accum) ->
753 zip(Tail1, Tail2, [{X,Y}|Accum]).
754
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
755
756 %% -----------------------------------------------------------------------------
757 %% Utility functions
758 %% -----------------------------------------------------------------------------
759
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
760 %% @private
23a64b6 @EiriniArvaniti some changes
EiriniArvaniti authored
761 -spec is_valid(mod_name(), symbolic_state(), command_list(), [symb_var()]) ->
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
762 boolean().
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
763 is_valid(_Mod, _State, [], _SymbEnv) -> true;
764 is_valid(Mod, _State, [{init,S}|Cmds], _SymbEnv) ->
765 is_valid(Mod, S, Cmds, _SymbEnv);
766 is_valid(Mod, State, [{set, Var, {call,_M,_F,A} = Call}|Cmds], SymbEnv) ->
767 args_defined(A, SymbEnv) andalso Mod:precondition(State, Call)
768 andalso is_valid(Mod, Mod:next_state(State, Var, Call), Cmds,
769 [Var|SymbEnv]).
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
770
771 %% @private
495d6ba @EiriniArvaniti refactored proper_statem:args_defined/2
EiriniArvaniti authored
772 -spec args_defined([term()], [symb_var()]) -> boolean().
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
773 args_defined(List, SymbEnv) ->
774 lists:all(fun (A) -> arg_defined(A, SymbEnv) end, List).
495d6ba @EiriniArvaniti refactored proper_statem:args_defined/2
EiriniArvaniti authored
775
776 -spec arg_defined(term(), [symb_var()]) -> boolean().
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
777 arg_defined({var,I} = V, SymbEnv) when is_integer(I) ->
778 lists:member(V, SymbEnv);
779 arg_defined(Tuple, SymbEnv) when is_tuple(Tuple) ->
780 args_defined(tuple_to_list(Tuple), SymbEnv);
781 arg_defined(List, SymbEnv) when is_list(List) ->
782 args_defined(List, SymbEnv);
495d6ba @EiriniArvaniti refactored proper_statem:args_defined/2
EiriniArvaniti authored
783 arg_defined(_, _) ->
c337159 @EiriniArvaniti discarding invalid commands when shrinking
EiriniArvaniti authored
784 true.
d6db210 @EiriniArvaniti statem shrinking,validating and exception handling fixes
EiriniArvaniti authored
785
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
786 %% @private
787 -spec get_initial_state(mod_name(), command_list()) -> symbolic_state().
788 get_initial_state(_, [{init,S}|_]) -> S;
789 get_initial_state(Mod, Cmds) when is_list(Cmds) ->
790 Mod:initial_state().
c135eab @EiriniArvaniti Fixes in parallel command handling
EiriniArvaniti authored
791
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
792 %% @private
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
793 -spec fix_parallel(pos_integer(), non_neg_integer(), combination() | 'done',
794 lookup(), mod_name(), symbolic_state(), [symb_var()],
795 pos_integer()) -> [command_list()].
796 fix_parallel(_, 0, done, _, _, _, _, _) ->
797 exit(error); %% not supposed to reach here
798 fix_parallel(MaxIndex, Len, done, LookUp, Mod, State, SymbEnv, W) ->
799 Comb = mk_first_comb(MaxIndex, Len-1, W),
800 case Len of
801 1 -> io:format("f");
802 _ -> ok
803 end,
804 fix_parallel(MaxIndex, Len-1, Comb , LookUp, Mod, State, SymbEnv, W);
805 fix_parallel(MaxIndex, Len, Comb, LookUp, Mod, State, SymbEnv, W) ->
806 CmdLists = lookup_cmd_lists(Comb, LookUp),
807 case can_parallelize(CmdLists, Mod, State, SymbEnv) of
808 true ->
809 lists:reverse(CmdLists);
810 false ->
811 C1 = proplists:get_value(1, Comb),
812 C2 = proplists:get_value(2, Comb),
813 Next = get_next(Comb, Len, MaxIndex, lists:sort(C1 ++ C2), W, 2),
814 fix_parallel(MaxIndex, Len, Next, LookUp, Mod, State, SymbEnv, W)
815 end.
816
817 -spec can_parallelize([command_list()], mod_name(), symbolic_state(),
818 [symb_var()]) -> boolean().
819 can_parallelize(CmdLists, Mod, State, SymbEnv) ->
820 lists:all(fun(C) -> is_valid(Mod, State, C, SymbEnv) end, CmdLists)
821 andalso lists:all(fun(C) -> is_valid(Mod, State, C, SymbEnv) end,
822 possible_interleavings(CmdLists)).
823
824 %% @private
ae1efa8 @EiriniArvaniti more changes to proper_statem.erl
EiriniArvaniti authored
825 -spec possible_interleavings([command_list()]) -> [command_list()].
826 possible_interleavings([P1,P2]) ->
827 insert_all(P1, P2);
828 possible_interleavings([P1|Rest]) ->
829 [I || L <- possible_interleavings(Rest),
830 I <- insert_all(P1, L)].
831
3f6ace3 @EiriniArvaniti proper_statem, proper_fsm behaviours introduced
EiriniArvaniti authored
832 %% @private
82576a1 @kostis Shut off a dialyzer warning and cosmetic cleanups
kostis authored
833 %% Returns all possible insertions of the elements of the first list,
834 %% preserving their order, inside the second list, i.e. all possible
ae1efa8 @EiriniArvaniti more changes to proper_statem.erl
EiriniArvaniti authored
835 %% command interleavings between two parallel processes
ecce5f5 @kostis More cleanups
kostis authored
836 -spec insert_all([term()], [term()]) -> [[term()]].
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
837 insert_all([], List) ->
838 [List];
839 insert_all([X], List) ->
e30fc9a @kostis More cleanups
kostis authored
840 all_insertions(X, length(List) + 1, List);
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
841
842 insert_all([X|[Y|Rest]], List) ->
6d826f9 @manopapad Fix copyright notices
authored
843 [L2 || L1 <- insert_all([Y|Rest], List),
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
844 L2 <- all_insertions(X, index(Y, L1), L1)].
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
845
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
846 %% @private
ecce5f5 @kostis More cleanups
kostis authored
847 -spec all_insertions(term(), pos_integer(), [term()]) -> [[term()]].
848 all_insertions(X, Limit, List) ->
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
849 all_insertions_tr(X, Limit, 0, [], List, []).
850
e30fc9a @kostis More cleanups
kostis authored
851 -spec all_insertions_tr(term(), pos_integer(), non_neg_integer(),
852 [term()], [term()], [[term()]]) -> [[term()]].
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
853 all_insertions_tr(X, Limit, LengthFront, Front, [], Acc) ->
854 case LengthFront < Limit of
855 true ->
856 [Front ++ [X] | Acc];
857 false ->
858 Acc
859 end;
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
860 all_insertions_tr(X, Limit, LengthFront, Front, Back = [BackH|BackT], Acc) ->
ecce5f5 @kostis More cleanups
kostis authored
861 case LengthFront < Limit of
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
862 true ->
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
863 all_insertions_tr(X, Limit, LengthFront+1, Front ++ [BackH],
864 BackT, [Front ++ [X] ++ Back | Acc]);
6d826f9 @manopapad Fix copyright notices
authored
865 false -> Acc
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
866 end.
867
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
868 %% @private
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
869 -spec index(term(), [term(),...]) -> pos_integer().
ecce5f5 @kostis More cleanups
kostis authored
870 index(X, List) ->
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
871 index(X, List, 1).
872
873 -spec index(term(), [term(),...], pos_integer()) -> pos_integer().
874 index(X, [X|_], N) -> N;
875 index(X, [_|Rest], N) -> index(X, Rest, N+1).
876
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
877 %% @private
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
878 -spec mk_dict(command_list(), pos_integer()) -> [{pos_integer(), command()}].
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
879 mk_dict([], _) -> [];
880 mk_dict([{init,_}|T], N) -> mk_dict(T, N);
881 mk_dict([H|T], N) -> [{N,H}|mk_dict(T, N+1)].
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
882
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
883 %% @private
6d826f9 @manopapad Fix copyright notices
authored
884 -spec mk_first_comb(pos_integer(), non_neg_integer(), pos_integer()) ->
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
885 combination().
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
886 mk_first_comb(N, Len, W) ->
887 mk_first_comb_tr(1, N, Len, [], W).
888
889 -spec mk_first_comb_tr(pos_integer(), pos_integer(), non_neg_integer(),
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
890 combination(), pos_integer()) -> combination().
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
891 mk_first_comb_tr(Start, N, _Len, Accum, 1) ->
892 [{1,lists:seq(Start, N)}|Accum];
893 mk_first_comb_tr(Start, N, Len, Accum, W) ->
894 K = Start + Len,
895 mk_first_comb_tr(K, N, Len, [{W,lists:seq(Start, K-1)}|Accum], W-1).
896
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
897 -spec lookup_cmds(indices(), lookup()) -> command_list().
898 lookup_cmds(Indices, LookUp) ->
b08079d @kostis Tidier cleanups
kostis authored
899 [orddict:fetch(Index, LookUp) || Index <- Indices].
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
900
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
901 -spec lookup_cmd_lists(combination(), lookup()) -> [command_list()].
902 lookup_cmd_lists(Combination, LookUp) ->
903 [lookup_cmds(Indices, LookUp) || {_, Indices} <- Combination].
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
904
7a261a8 @EiriniArvaniti small change in computation of initial state + cleanups
EiriniArvaniti authored
905 %% @private
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
906 -spec get_next(combination(), non_neg_integer(), pos_integer(), indices(),
907 pos_integer(), pos_integer()) -> combination() | 'done'.
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
908 get_next(L, _Len, _MaxIndex, Available, _Workers, 1) ->
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
909 [{1,Available}|proplists:delete(1, L)];
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
910 get_next(L, Len, MaxIndex, Available, Workers, N) ->
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
911 C = case proplists:is_defined(N, L) of
912 true ->
913 next_comb(MaxIndex, proplists:get_value(N, L), Available);
914 false ->
915 lists:sublist(Available, Len)
916 end,
917 case C of
6d826f9 @manopapad Fix copyright notices
authored
918 done ->
919 if N =:= Workers ->
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
920 done;
6d826f9 @manopapad Fix copyright notices
authored
921 N =/= Workers ->
922 C2 = proplists:get_value(N+1, L),
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
923 NewList = [E || {M,_}=E <- L, M > N],
6d826f9 @manopapad Fix copyright notices
authored
924 get_next(NewList, Len, MaxIndex,
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
925 lists:sort(C2 ++ Available), Workers, N+1)
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
926 end;
927 _ ->
6d826f9 @manopapad Fix copyright notices
authored
928 get_next([{N,C}|proplists:delete(N, L)],
6002de8 @EiriniArvaniti code refactoring in parallel command execution
EiriniArvaniti authored
929 Len, MaxIndex, Available -- C, Workers, N-1)
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
930 end.
931
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
932 -spec next_comb(pos_integer(), indices(), indices()) -> indices() | 'done'.
933 next_comb(MaxIndex, Indices, Available) ->
934 Res = next_comb_tr(MaxIndex, lists:reverse(Indices), []),
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
935 case is_well_defined(Res, Available) of
936 true -> Res;
937 false -> next_comb(MaxIndex, Res, Available)
938 end.
939
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
940 -spec is_well_defined(indices() | 'done', indices()) -> boolean().
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
941 is_well_defined(done, _) -> true;
6d826f9 @manopapad Fix copyright notices
authored
942 is_well_defined(Comb, Available) ->
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
943 lists:usort(Comb) =:= Comb andalso
944 lists:all(fun(X) -> lists:member(X, Available) end, Comb).
945
e6eb6da @EiriniArvaniti cleanups in statem
EiriniArvaniti authored
946 -spec next_comb_tr(pos_integer(), indices(), indices()) -> indices() | 'done'.
688899b @EiriniArvaniti more efficient parallel command generation
EiriniArvaniti authored
947 next_comb_tr(_MaxIndex, [], _Acc) ->
948 done;
949 next_comb_tr(MaxIndex, [MaxIndex | Rest], Acc) ->
950 next_comb_tr(MaxIndex, Rest, [1 | Acc]);
951 next_comb_tr(_MaxIndex, [X | Rest], Acc) ->
6d826f9 @manopapad Fix copyright notices
authored
952 lists:reverse(Rest) ++ [X+1] ++ Acc.
cd5581a @EiriniArvaniti parallel commands added
EiriniArvaniti authored
953
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
954 -spec remove_slice(pos_integer(), command_list(), [command_list(),...]) ->
7e33e46 @EiriniArvaniti clean-ups
EiriniArvaniti authored
955 [command_list(),...].
ddd3d60 @EiriniArvaniti move_shrinker for parallel testcases + shrink_list type
EiriniArvaniti authored
956 remove_slice(Index, Slice, List) ->
957 remove_slice_tr(Index, Slice, List, [], 1).
958
959 -spec remove_slice_tr(pos_integer(), command_list(), [command_list(),...],
960 [command_list()], pos_integer()) -> [command_list(),...].
961 remove_slice_tr(Index, Slice, [H|T], Acc, Index) ->
962 lists:reverse(Acc) ++ [H -- Slice] ++ T;
963 remove_slice_tr(Index, Slice, [H|T], Acc, N) ->
964 remove_slice_tr(Index, Slice, T, [H|Acc], N+1).
965
966 -spec get_slices(command_list()) -> [command_list()].
967 get_slices(List) ->
968 get_slices_tr(List, List, 1, []).
969
970 -spec get_slices_tr(command_list(), command_list(), pos_integer(),
971 [command_list()]) -> [command_list()].
972 get_slices_tr([], _, _, Acc) -> Acc;
973 get_slices_tr([_|Tail], List, N, Acc) ->
974 get_slices_tr(Tail, List, N+1, [lists:sublist(List, N)|Acc]).
9777cfa @EiriniArvaniti small fix in parallel statem testing
EiriniArvaniti authored
975
976 -spec spawn_link_cp(fun(() -> _)) -> pid().
977 spawn_link_cp(ActualFun) ->
978 PDictStuff = [Pair || {K,_V} = Pair <- get(),
979 is_atom(K),
980 re:run(atom_to_list(K), ["^[$]"],
981 [{capture,none}]) =:= match],
982 Fun = fun() ->
983 lists:foreach(fun({K,V}) -> put(K,V) end, PDictStuff),
984 proper_arith:rand_reseed(),
985 ActualFun()
986 end,
987 spawn_link(Fun).
Something went wrong with that request. Please try again.