gproc_eqc.erl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. %%% File : gproc_eqc.erl
  2. %%% Author : <norton@alum.mit.edu>
  3. %%% : <Ulf.Wiger@erlang-consulting.com>
  4. %%% : <John.Hughes@quviq.com>
  5. %%% Description : QuickCheck test model for gproc
  6. %%% Created : 11 Dec 2008 by <John Hughes@JTABLET2007>
  7. -module(gproc_eqc).
  8. -include_lib("eqc/include/eqc.hrl").
  9. -include_lib("eqc/include/eqc_statem.hrl").
  10. -compile(export_all).
  11. %%
  12. %% QUESTIONS:
  13. %%
  14. %% - does set_value for Class==a make sense?
  15. %% - shouldn't mreg return {true, keys()} rather than the {true,
  16. %% objects()} upon success?
  17. %%
  18. %% TODO:
  19. %%
  20. %% - implement mreg
  21. %% - implement send
  22. %% - implement info
  23. %% - implement select
  24. %% - implement first/next/prev/last
  25. %% - implement table
  26. %%
  27. %% records
  28. -record(key,
  29. {class %% class()
  30. , scope %% scope()
  31. , name %% name()
  32. }).
  33. -record(reg,
  34. {pid %% pid()
  35. , key %% key()
  36. , value %% int()
  37. }).
  38. -record(state,
  39. {pids=[] %% [pid()]
  40. , killed=[] %% [pid()]
  41. , regs=[] %% [reg()]
  42. }).
  43. %% external API
  44. start_test() ->
  45. eqc:module({numtests, 500}, ?MODULE).
  46. run() ->
  47. run(500).
  48. run(Num) ->
  49. eqc:quickcheck(eqc:numtests(Num, prop_gproc())).
  50. %% Command generator, S is the state
  51. command(S) ->
  52. oneof(
  53. %% spawn
  54. [ {call,?MODULE,spawn, []} ]
  55. %% where
  56. ++ [ {call,?MODULE,where, [key()]} ]
  57. %% kill
  58. ++ [ oneof([
  59. %% kill
  60. {call,?MODULE,kill, [elements(S#state.pids)]}
  61. %% register
  62. , {call,?MODULE,reg, ?LET(Key, key(),[elements(S#state.pids), Key, reg_value(Key)])}
  63. %% unregister
  64. , {call,?MODULE,unreg, [elements(S#state.pids), key()]}
  65. %% many register
  66. %%, {call,?MODULE,mreg, [elements(S#state.pids), class(), scope()
  67. %% , list({name(), value()})]}
  68. %% set_value
  69. , {call,?MODULE,set_value, ?LET(Key, key(),[elements(S#state.pids), Key, reg_value(Key)])}
  70. %% update_counter
  71. , {call,?MODULE,update_counter, [elements(S#state.pids), key(), value()]}
  72. %% get_value
  73. , {call,?MODULE,get_value, [elements(S#state.pids), key()]}
  74. %% lookup_pid
  75. , {call,?MODULE,lookup_pid, [key()]}
  76. %% lookup_pids
  77. , {call,?MODULE,lookup_pids, [key()]}
  78. ])
  79. || S#state.pids/=[] ]
  80. ).
  81. %% generator class
  82. class() -> elements([n,p,c,a]).
  83. %% generator scope
  84. scope() -> l.
  85. %% generator name
  86. name() -> elements([x,y,z,w]).
  87. %% generator key
  88. key() -> #key{class=class(), scope=scope(), name=name()}.
  89. %% generator value
  90. value() -> frequency([{8, int()}, {1, undefined}, {1, make_ref()}]).
  91. %% value for reg and set_value
  92. %% 'a' and 'c' should only have integers as values (reg: value is ignored for 'a')
  93. reg_value(#key{class=C}) when C == a; C == c -> int();
  94. reg_value(_) -> value().
  95. %% helpers
  96. is_register_ok(_S,_Pid,#key{class=c},Value) when not is_integer(Value) ->
  97. false;
  98. is_register_ok(_S,_Pid,#key{class=a},Value) when not is_integer(Value) ->
  99. false;
  100. is_register_ok(S,Pid,Key,_Value) ->
  101. [] == [ Pid1 || #reg{pid=Pid1,key=Key1}
  102. <- S#state.regs, is_register_eq(Pid,Key,Pid1,Key1) ].
  103. is_register_eq(_PidA,#key{class=Class}=KeyA,_PidB,KeyB)
  104. when Class == n; Class ==a ->
  105. KeyA==KeyB;
  106. is_register_eq(PidA,KeyA,PidB,KeyB) ->
  107. PidA==PidB andalso KeyA==KeyB.
  108. is_unregister_ok(S,Pid,Key) ->
  109. [] /= [ Pid1 || #reg{pid=Pid1,key=Key1}
  110. <- S#state.regs, is_unregister_eq(Pid,Key,Pid1,Key1) ].
  111. is_unregister_eq(PidA,KeyA,PidB,KeyB) ->
  112. KeyA==KeyB andalso PidA==PidB.
  113. is_registered_and_alive(S,Pid,Key) ->
  114. is_unregister_ok(S,Pid,Key)
  115. andalso lists:member(Pid,S#state.pids).
  116. %% Initialize the state
  117. initial_state() ->
  118. #state{}.
  119. %% Next state transformation, S is the current state
  120. %% spawn
  121. next_state(S,V,{call,_,spawn,_}) ->
  122. S#state{pids=[V|S#state.pids]};
  123. %% kill
  124. next_state(S,_V,{call,_,kill,[Pid]}) ->
  125. S#state{pids=S#state.pids -- [Pid]
  126. , killed=[Pid|S#state.killed]
  127. , regs=[ X || #reg{pid=Pid1}=X <- S#state.regs, Pid/=Pid1 ]
  128. };
  129. %% reg
  130. next_state(S,_V,{call,_,reg,[Pid,Key,Value]}) ->
  131. case is_register_ok(S,Pid,Key,Value) of
  132. false ->
  133. S;
  134. true ->
  135. case Key of
  136. #key{class=a,name=Name} ->
  137. %% initialize aggr counter
  138. FunC = fun(#reg{key=#key{class=Class1,name=Name1}}) -> (Class1 == c andalso Name==Name1) end,
  139. {Regs, _Others} = lists:partition(FunC, S#state.regs),
  140. InitialValue = lists:sum([ V || #reg{value=V} <- Regs ]),
  141. S#state{regs=[#reg{pid=Pid,key=Key,value=InitialValue}|S#state.regs]};
  142. #key{class=c,name=Name} ->
  143. S1 = S#state{regs=[#reg{pid=Pid,key=Key,value=Value}|S#state.regs]},
  144. %% update aggr counter
  145. FunA = fun(#reg{key=#key{class=Class1,name=Name1}}) -> (Class1 == a andalso Name==Name1) end,
  146. case lists:partition(FunA, S1#state.regs) of
  147. {[Reg], Others} ->
  148. S1#state{regs=[Reg#reg{value=Reg#reg.value+Value}|Others]};
  149. {[], _Others} ->
  150. S1
  151. end;
  152. _ ->
  153. S#state{regs=[#reg{pid=Pid,key=Key,value=Value}|S#state.regs]}
  154. end
  155. end;
  156. %% unreg
  157. next_state(S,_V,{call,_,unreg,[Pid,Key]}) ->
  158. case is_unregister_ok(S,Pid,Key) of
  159. false ->
  160. S;
  161. true ->
  162. FunC = fun(#reg{pid=Pid1,key=Key1}) -> (Pid==Pid1 andalso Key==Key1) end,
  163. case lists:partition(FunC, S#state.regs) of
  164. {[#reg{value=Value}], Others} ->
  165. S1 = S#state{regs=Others},
  166. case Key of
  167. #key{class=c,name=Name} ->
  168. %% update aggr counter
  169. FunA = fun(#reg{key=#key{class=Class1,name=Name1}}) -> (Class1 == a andalso Name==Name1) end,
  170. case lists:partition(FunA, S1#state.regs) of
  171. {[], _Others1} ->
  172. S1;
  173. {[Reg], Others1} ->
  174. S1#state{regs=[Reg#reg{value=Reg#reg.value-Value}|Others1]}
  175. end;
  176. _ ->
  177. S1
  178. end
  179. end
  180. end;
  181. %% set_value
  182. next_state(S,_V,{call,_,set_value,[Pid,Key,Value]}) ->
  183. case is_registered_and_alive(S,Pid,Key) of
  184. false ->
  185. S;
  186. true ->
  187. FunC = fun(#reg{pid=Pid1,key=Key1}) -> (Pid==Pid1 andalso Key==Key1) end,
  188. case lists:partition(FunC, S#state.regs) of
  189. {[#reg{value=OldValue}=OldReg], Others} ->
  190. S1 = S#state{regs=[OldReg#reg{value=Value}|Others]},
  191. case Key of
  192. #key{class=c,name=Name} ->
  193. %% aggr counter update
  194. FunA = fun(#reg{key=#key{class=Class1,name=Name1}}) -> (Class1 == a andalso Name==Name1) end,
  195. case lists:partition(FunA, S1#state.regs) of
  196. {[], _Others1} ->
  197. S1;
  198. {[Reg], Others1} ->
  199. S1#state{regs=[Reg#reg{value=Reg#reg.value-OldValue+Value}|Others1]}
  200. end;
  201. _ ->
  202. S1
  203. end
  204. end
  205. end;
  206. %% update_counter
  207. next_state(S,_V,{call,_,update_counter,[Pid,#key{class=Class}=Key,Incr]})
  208. when Class == c ->
  209. case is_registered_and_alive(S,Pid,Key) of
  210. false ->
  211. S;
  212. true ->
  213. FunC = fun(#reg{pid=Pid1,key=Key1}) -> (Pid==Pid1 andalso Key==Key1) end,
  214. case lists:partition(FunC, S#state.regs) of
  215. {[#reg{value=OldValue}=OldReg], Others} ->
  216. S1 = S#state{regs=[OldReg#reg{value=OldValue+Incr}|Others]},
  217. case Key of
  218. #key{class=c,name=Name} ->
  219. %% aggr counter update
  220. FunA = fun(#reg{key=#key{class=Class1,name=Name1}}) -> (Class1 == a andalso Name==Name1) end,
  221. case lists:partition(FunA, S1#state.regs) of
  222. {[], _Others1} ->
  223. S1;
  224. {[Reg], Others1} ->
  225. S1#state{regs=[Reg#reg{value=Reg#reg.value+Incr}|Others1]}
  226. end;
  227. _ ->
  228. S1
  229. end
  230. end
  231. end;
  232. %% otherwise
  233. next_state(S,_V,{call,_,_,_}) ->
  234. S.
  235. %% Precondition, checked before command is added to the command
  236. %% sequence
  237. precondition(_S,{call,_,_,_}) ->
  238. true.
  239. %% Postcondition, checked after command has been evaluated. S is the
  240. %% state before next_state(S,_,<command>)
  241. %% spawn
  242. postcondition(_S,{call,_,spawn,_},_Res) ->
  243. true;
  244. %% kill
  245. postcondition(_S,{call,_,kill,_},_Res) ->
  246. true;
  247. %% where
  248. postcondition(S,{call,_,where,[#key{class=Class}=Key]},Res) ->
  249. if Class == n orelse Class == a ->
  250. case lists:keysearch(Key,#reg.key,S#state.regs) of
  251. {value, #reg{pid=Pid}} ->
  252. Res == Pid;
  253. false ->
  254. Res == undefined
  255. end;
  256. true ->
  257. case Res of
  258. {'EXIT', {badarg, _}} ->
  259. true;
  260. _ ->
  261. false
  262. end
  263. end;
  264. %% reg
  265. postcondition(S,{call,_,reg,[Pid,Key,Value]},Res) ->
  266. case Res of
  267. true ->
  268. is_register_ok(S,Pid,Key,Value);
  269. {'EXIT', {badarg, _}} ->
  270. is_unregister_ok(S,Pid,Key)
  271. orelse not is_register_ok(S,Pid,Key,Value)
  272. end;
  273. %% unreg
  274. postcondition(S,{call,_,unreg,[Pid,Key]},Res) ->
  275. case Res of
  276. true ->
  277. is_unregister_ok(S,Pid,Key);
  278. {'EXIT', {badarg, _}} ->
  279. not is_unregister_ok(S,Pid,Key)
  280. end;
  281. %% set_value
  282. postcondition(S,{call,_,set_value,[Pid,Key,_Value]},Res) ->
  283. case Res of
  284. true ->
  285. is_registered_and_alive(S,Pid,Key);
  286. {'EXIT', {badarg, _}} ->
  287. not is_registered_and_alive(S,Pid,Key)
  288. orelse (Key#key.class == c
  289. andalso is_registered_and_alive(S, Pid, Key))
  290. end;
  291. %% update_counter
  292. postcondition(S,{call,_,update_counter,[Pid,#key{class=Class}=Key,Incr]},Res)
  293. when Class == c ->
  294. case [ Value1 || #reg{pid=Pid1,key=Key1,value=Value1} <- S#state.regs
  295. , (Pid==Pid1 andalso Key==Key1) ] of
  296. [] ->
  297. case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  298. [Value] ->
  299. Res == Value+Incr
  300. end;
  301. postcondition(_S,{call,_,update_counter,[_Pid,_Key,_Incr]},Res) ->
  302. case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  303. %% get_value
  304. postcondition(S,{call,_,get_value,[Pid,Key]},Res) ->
  305. case [ Value1 || #reg{pid=Pid1,key=Key1,value=Value1} <- S#state.regs
  306. , (Pid==Pid1 andalso Key==Key1) ] of
  307. [] ->
  308. case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  309. [Value] ->
  310. Res == Value
  311. end;
  312. %% lookup_pid
  313. postcondition(S,{call,_,lookup_pid,[#key{class=Class}=Key]},Res)
  314. when Class == n; Class == a ->
  315. case [ Pid1 || #reg{pid=Pid1,key=Key1} <- S#state.regs
  316. , Key==Key1 ] of
  317. [] ->
  318. case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  319. [Pid] ->
  320. Res == Pid
  321. end;
  322. postcondition(_S,{call,_,lookup_pid,[_Key]},Res) ->
  323. case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  324. %% lookup_pids
  325. postcondition(S,{call,_,lookup_pids,[#key{class=Class}=Key]},Res)
  326. when Class == n; Class == a; Class == c; Class == p ->
  327. Pids = [ Pid1 || #reg{pid=Pid1,key=Key1} <- S#state.regs
  328. , Key==Key1 ],
  329. lists:sort(Res) == lists:sort(Pids);
  330. %% postcondition(_S,{call,_,lookup_pids,[_Key]},Res) ->
  331. %% case Res of {'EXIT', {badarg, _}} -> true; _ -> false end;
  332. %% otherwise
  333. postcondition(_S,{call,_,_,_},_Res) ->
  334. false.
  335. %% property
  336. prop_gproc() ->
  337. ?FORALL(Cmds,commands(?MODULE),
  338. ?TRAPEXIT(
  339. begin
  340. ok = stop_app(),
  341. ok = start_app(),
  342. {H,S,Res} = run_commands(?MODULE,Cmds),
  343. kill_all_pids({H,S}),
  344. %% whenfail
  345. ?WHENFAIL(
  346. begin
  347. io:format("~nHISTORY:"),
  348. if
  349. length(H) < 1 ->
  350. io:format(" none~n");
  351. true ->
  352. CmdsH = eqc_statem:zip(Cmds,H),
  353. [ begin
  354. {Cmd,{State,Reply}} = lists:nth(N,CmdsH),
  355. io:format("~n #~p:~n\tCmd: ~p~n\tReply: ~p~n\tState: ~p~n",
  356. [N,Cmd,Reply,State])
  357. end
  358. || N <- lists:seq(1,length(CmdsH)) ]
  359. end,
  360. io:format("~nRESULT:~n\t~p~n",[Res]),
  361. io:format("~nSTATE:~n\t~p~n",[S])
  362. end,
  363. Res == ok)
  364. end)).
  365. %% helpers
  366. start_app() ->
  367. application:start(sasl),
  368. case application:start(gproc) of
  369. {error, {already_started,_}} ->
  370. stop_app(),
  371. ok = application:start(gproc);
  372. ok ->
  373. ok
  374. end.
  375. stop_app() ->
  376. case application:stop(gproc) of
  377. {error, {not_started,_}} ->
  378. ok;
  379. ok ->
  380. ok
  381. end.
  382. %% If using the scheduler... This code needs to run in a separate
  383. %% module, so it can be compiled without instrumentation.
  384. kill_all_pids(Pid) when is_pid(Pid) ->
  385. case is_process_alive(Pid) of
  386. true ->
  387. exit(Pid,kill);
  388. false ->
  389. ok
  390. end;
  391. kill_all_pids(Tup) when is_tuple(Tup) ->
  392. kill_all_pids(tuple_to_list(Tup));
  393. kill_all_pids([H|T]) ->
  394. kill_all_pids(H),
  395. kill_all_pids(T);
  396. kill_all_pids(_) ->
  397. ok.
  398. %% spawn
  399. spawn() ->
  400. spawn(fun() -> loop() end).
  401. loop() ->
  402. receive
  403. {From, Ref, F} ->
  404. From ! {Ref, catch F()},
  405. loop();
  406. stop -> ok
  407. end.
  408. %% kill
  409. kill(Pid) ->
  410. exit(Pid,foo),
  411. timer:sleep(10).
  412. %% where
  413. where(#key{class=Class,scope=Scope,name=Name}) ->
  414. catch gproc:where({Class,Scope,Name}).
  415. %% reg
  416. reg(Pid,#key{class=Class,scope=Scope,name=Name},Value) ->
  417. do(Pid, fun() -> catch gproc:reg({Class,Scope,Name},Value) end).
  418. %% unreg
  419. unreg(Pid,#key{class=Class,scope=Scope,name=Name}) ->
  420. do(Pid, fun() -> catch gproc:unreg({Class,Scope,Name}) end).
  421. %% set_value
  422. set_value(Pid,#key{class=Class,scope=Scope,name=Name},Value) ->
  423. do(Pid, fun() -> catch gproc:set_value({Class,Scope,Name},Value) end).
  424. %% update_counter
  425. update_counter(Pid, #key{class=Class,scope=Scope,name=Name},Incr) ->
  426. do(Pid, fun() -> catch gproc:update_counter({Class,Scope,Name},Incr) end).
  427. %% get_value
  428. get_value(Pid,#key{class=Class,scope=Scope,name=Name}) ->
  429. do(Pid, fun() -> catch gproc:get_value({Class,Scope,Name}) end).
  430. %% lookup_pid
  431. lookup_pid(#key{class=Class,scope=Scope,name=Name}) ->
  432. catch gproc:lookup_pid({Class,Scope,Name}).
  433. %% lookup_pids
  434. lookup_pids(#key{class=Class,scope=Scope,name=Name}) ->
  435. catch gproc:lookup_pids({Class,Scope,Name}).
  436. %% do
  437. do(Pid, F) ->
  438. Ref = erlang:monitor(process, Pid),
  439. Pid ! {self(), Ref, F},
  440. receive
  441. {'DOWN', Ref, process, Pid, Reason} ->
  442. {'EXIT', {'DOWN', Reason}};
  443. {Ref, Result} ->
  444. erlang:demonitor(Ref),
  445. Result
  446. after 3000 ->
  447. {'EXIT', timeout}
  448. end.