Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
6 7u83 1
-module(poolboy_eqc).
2
-compile([export_all, nowarn_export_all]).
3
 
4
-ifdef(TEST).
5
-ifdef(EQC).
6
-include_lib("eqc/include/eqc.hrl").
7
-include_lib("eqc/include/eqc_statem.hrl").
8
 
9
-include_lib("eunit/include/eunit.hrl").
10
 
11
poolboy_test_() ->
12
    {timeout, 20,
13
        fun() ->
14
                ?assert(eqc:quickcheck(eqc:testing_time(4,
15
                            poolboy_eqc:prop_sequential()))),
16
                ?assert(eqc:quickcheck(eqc:testing_time(4,
17
                            poolboy_eqc:prop_parallel())))
18
        end
19
    }.
20
 
21
-record(state,
22
    {
23
        pid,
24
        size,
25
        max_overflow,
26
        checked_out = []
27
    }).
28
 
29
initial_state() ->
30
    #state{}.
31
 
32
command(S) ->
33
    oneof(
34
        [{call, ?MODULE, start_poolboy, make_args(S, nat(), nat())} || S#state.pid == undefined] ++
35
            [{call, ?MODULE, stop_poolboy, [S#state.pid]} || S#state.pid /= undefined] ++
36
            [{call, ?MODULE, checkout_nonblock, [S#state.pid]} || S#state.pid /= undefined] ++
37
            %% checkout shrinks to checkout_nonblock so we can simplify counterexamples
38
            [{call, ?MODULE, ?SHRINK(checkout_block, [checkout_nonblock]), [S#state.pid]} || S#state.pid /= undefined] ++
39
            [{call, ?MODULE, checkin, [S#state.pid, fault({call, ?MODULE, spawn_process, []}, elements(S#state.checked_out))]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
40
            [{call, ?MODULE, kill_worker, [elements(S#state.checked_out)]} || S#state.pid /= undefined, S#state.checked_out /= []] ++
41
            [{call, ?MODULE, kill_idle_worker, [S#state.pid]} || S#state.pid /= undefined] ++
42
            [{call, ?MODULE, spurious_exit, [S#state.pid]} || S#state.pid /= undefined]
43
    ).
44
 
45
make_args(_S, Size, Overflow) ->
46
    [[{size, Size}, {max_overflow, Overflow}, {worker_module, poolboy_test_worker}, {name, {local, poolboy_eqc}}]].
47
 
48
spawn_process() ->
49
    {spawn(fun() ->
50
                timer:sleep(5000)
51
        end), self()}.
52
 
53
spawn_linked_process(Pool) ->
54
    Parent = self(),
55
    Pid = spawn(fun() ->
56
                    link(Pool),
57
                    Parent ! {linked, self()},
58
                    timer:sleep(5000)
59
            end),
60
    receive
61
        {linked, Pid} ->
62
            Pid
63
    end.
64
 
65
start_poolboy(Args) ->
66
    {ok, Pid} = poolboy:start_link(Args),
67
    Pid.
68
 
69
stop_poolboy(Pid) ->
70
    gen_server:call(Pid, stop),
71
    timer:sleep(1).
72
 
73
checkout_nonblock(Pool) ->
74
    {poolboy:checkout(Pool, false), self()}.
75
 
76
checkout_block(Pool) ->
77
    {catch(poolboy:checkout(Pool, true, 100)), self()}.
78
 
79
checkin(Pool, {Worker, _}) ->
80
    Res = poolboy:checkin(Pool, Worker),
81
    gen_server:call(Pool, get_avail_workers),
82
    Res.
83
 
84
kill_worker({Worker, _}) ->
85
    exit(Worker, kill),
86
    timer:sleep(1),
87
    Worker.
88
 
89
kill_idle_worker(Pool) ->
90
    Pid = poolboy:checkout(Pool, false),
91
    case Pid of
92
        _ when is_pid(Pid) ->
93
            poolboy:checkin(Pool, Pid),
94
            kill_worker({Pid, self()});
95
        _ ->
96
            timer:sleep(1),
97
            kill_idle_worker(Pool)
98
    end.
99
 
100
spurious_exit(Pool) ->
101
    Pid = spawn_linked_process(Pool),
102
    exit(Pid, kill).
103
 
104
precondition(S,{call,_,start_poolboy,_}) ->
105
    %% only start new pool when old one is stopped
106
  S#state.pid == undefined;
107
precondition(S,_) when S#state.pid == undefined ->
108
    %% all other states need a running pool
109
    false;
110
precondition(S, {call, _, kill_worker, [Pid]}) ->
111
    lists:member(Pid, S#state.checked_out);
112
precondition(S,{call,_,kill_idle_worker,[_Pool]}) ->
113
    length(S#state.checked_out) < S#state.size;
114
precondition(S,{call,_,checkin,[_Pool, Pid]}) ->
115
    lists:member(Pid, S#state.checked_out);
116
precondition(_S,{call,_,_,_}) ->
117
    true.
118
 
119
%% check model state against internal state, only used in sequential tests
120
invariant(S = #state{pid=Pid},_) when Pid /= undefined ->
121
    State = if length(S#state.checked_out) == S#state.size + S#state.max_overflow ->
122
            full;
123
        length(S#state.checked_out) >= S#state.size ->
124
            overflow;
125
        true ->
126
            ready
127
    end,
128
 
129
    Workers = max(0, S#state.size - length(S#state.checked_out)),
130
    OverFlow = max(0, length(S#state.checked_out) - S#state.size),
131
    Monitors = length(S#state.checked_out),
132
 
133
    RealStatus = gen_server:call(Pid, status),
134
    case RealStatus == {State, Workers, OverFlow, Monitors} of
135
        true ->
136
            true;
137
        _ ->
138
            {wrong_state, RealStatus, {State, Workers, OverFlow, Monitors}}
139
    end;
140
invariant(_,_) ->
141
    true.
142
 
143
%% what states block
144
blocking(S, {call, _, checkout_block, _}) ->
145
    %% blocking checkout can block if we expect a checkout to fail
146
    not checkout_ok(S);
147
blocking(_, _) ->
148
    false.
149
 
150
postcondition(S,{call,_,checkout_block,[_Pool]},R) ->
151
    case R of
152
        {{'EXIT', {timeout, _}}, _} ->
153
            case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of
154
                true ->
155
                    true;
156
                _ ->
157
                    {checkout_block, R}
158
            end;
159
        _ ->
160
            case length(S#state.checked_out) < S#state.size + S#state.max_overflow of
161
                true ->
162
                    true;
163
                _ ->
164
                    {checkout_block, R}
165
            end
166
    end;
167
postcondition(S,{call,_,checkout_nonblock,[_Pool]},R) ->
168
    case R of
169
        {full, _} ->
170
            case length(S#state.checked_out) >= S#state.size + S#state.max_overflow of
171
                true ->
172
                    true;
173
                _ ->
174
                    {checkout_nonblock, R}
175
            end;
176
        _ ->
177
            case length(S#state.checked_out) < S#state.size + S#state.max_overflow of
178
                true ->
179
                    true;
180
                _ ->
181
                    {checkout_block, R}
182
            end
183
    end;
184
postcondition(_S, {call,_,checkin,_}, R) ->
185
    case R of
186
        ok ->
187
            true;
188
        _ ->
189
            {checkin, R}
190
    end;
191
postcondition(_S,{call,_,_,_},_R) ->
192
    true.
193
 
194
next_state(S,V,{call,_,start_poolboy, [Args]}) ->
195
    S#state{pid=V,
196
        size=proplists:get_value(size, Args),
197
        max_overflow=proplists:get_value(max_overflow, Args)
198
    };
199
next_state(S,_V,{call,_,stop_poolboy, [_Args]}) ->
200
    S#state{pid=undefined, checked_out=[]};
201
next_state(S,V,{call,_,checkout_block,_}) ->
202
    %% if the model says the checkout worked, store the result
203
    case checkout_ok(S) of
204
        false ->
205
            S;
206
        _ ->
207
            S#state{checked_out=S#state.checked_out++[V]}
208
    end;
209
next_state(S,V,{call,_,checkout_nonblock,_}) ->
210
    %% if the model says the checkout worked, store the result
211
    case checkout_ok(S) of
212
        false ->
213
            S;
214
        _ ->
215
            S#state{checked_out=S#state.checked_out++[V]}
216
    end;
217
next_state(S,_V,{call, _, checkin, [_Pool, Worker]}) ->
218
    S#state{checked_out=S#state.checked_out -- [Worker]};
219
next_state(S,_V,{call, _, kill_worker, [Worker]}) ->
220
    S#state{checked_out=S#state.checked_out -- [Worker]};
221
next_state(S,_V,{call, _, kill_idle_worker, [_Pool]}) ->
222
    S;
223
next_state(S,_V,{call, _, spurious_exit, [_Pool]}) ->
224
    S;
225
next_state(S,V,{call, erlang, self, []}) ->
226
    %% added after test generation, values are never symbolic
227
    S#state{checked_out=[{Worker, Pid} || {Worker, Pid} <- S#state.checked_out, Pid /= V]}.
228
 
229
 
230
prop_sequential() ->
231
    fault_rate(1, 10,
232
        ?FORALL(Cmds,commands(?MODULE),
233
            ?TRAPEXIT(
234
                aggregate(command_names(Cmds),
235
                    begin
236
                            {H,S,Res} = run_commands(?MODULE,Cmds),
237
                            catch(stop_poolboy(whereis(poolboy_eqc))),
238
                            ?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n~p\n",
239
                                    [H,S,Res, zip(Cmds, [Y || {_, Y} <- H])]),
240
                                Res == ok)
241
                    end)))).
242
 
243
prop_parallel() ->
244
    fault_rate(1, 10,
245
        ?FORALL(Cmds={Seq,Par},parallel_commands(?MODULE),
246
            ?TRAPEXIT(
247
                aggregate(command_names(Cmds),
248
                    begin
249
                            NewPar = [P ++ [{set, {var, 0}, {call, erlang, self, []}}] || P <- Par],
250
                            {H,S,Res} = run_parallel_commands(?MODULE,{Seq,NewPar}),
251
                            catch(stop_poolboy(whereis(poolboy_eqc))),
252
                            ?WHENFAIL(io:format("History: ~p\nState: ~p\nRes: ~p\n",
253
                                    [H,S,Res]),
254
                                Res == ok)
255
                    end)))).
256
 
257
 
258
checkout_ok(S) ->
259
    length(S#state.checked_out) < S#state.size + S#state.max_overflow.
260
 
261
-endif.
262
-endif.