pidq_test.erl 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. -module(pidq_test).
  2. -include_lib("eunit/include/eunit.hrl").
  3. -compile([export_all]).
  4. % The `user' processes represent users of the pidq library. A user
  5. % process will take a pid, report details on the pid it has, release
  6. % and take a new pid, stop cleanly, and crash.
  7. start_user() ->
  8. TC = pidq:take_pid(),
  9. spawn(fun() -> user_loop(TC) end).
  10. user_id(Pid) ->
  11. Pid ! {get_tc_id, self()},
  12. receive
  13. {Type, Id} ->
  14. {Type, Id}
  15. end.
  16. user_new_tc(Pid) ->
  17. Pid ! new_tc.
  18. user_stop(Pid) ->
  19. Pid ! stop.
  20. user_crash(Pid) ->
  21. Pid ! crash.
  22. user_loop(MyTC) ->
  23. receive
  24. {get_tc_id, From} ->
  25. From ! get_tc_id(MyTC),
  26. user_loop(MyTC);
  27. new_tc ->
  28. pidq:return_pid(MyTC, ok),
  29. MyNewTC = pidq:take_pid(),
  30. user_loop(MyNewTC);
  31. stop ->
  32. pidq:return_pid(MyTC, ok),
  33. stopped;
  34. crash ->
  35. erlang:error({user_loop, kaboom})
  36. end.
  37. % The `tc' processes represent the pids tracked by pidq for testing.
  38. % They have a type and an ID and can report their type and ID and
  39. % stop.
  40. tc_loop({Type, Id}) ->
  41. receive
  42. {get_id, From} ->
  43. From ! {ok, Type, Id},
  44. tc_loop({Type, Id});
  45. stop -> stopped;
  46. crash ->
  47. erlang:error({tc_loop, kaboom})
  48. end.
  49. get_tc_id(Pid) ->
  50. Pid ! {get_id, self()},
  51. receive
  52. {ok, Type, Id} ->
  53. {Type, Id}
  54. after 200 ->
  55. timeout
  56. end.
  57. stop_tc(Pid) ->
  58. Pid ! stop.
  59. tc_starter(Type) ->
  60. Ref = make_ref(),
  61. spawn_link(fun() -> tc_loop({Type, Ref}) end).
  62. assert_tc_valid(Pid) ->
  63. ?assertMatch({_Type, _Ref}, get_tc_id(Pid)),
  64. ok.
  65. tc_sanity_test() ->
  66. Pid1 = tc_starter("1"),
  67. {"1", Id1} = get_tc_id(Pid1),
  68. Pid2 = tc_starter("1"),
  69. {"1", Id2} = get_tc_id(Pid2),
  70. ?assertNot(Id1 == Id2),
  71. stop_tc(Pid1),
  72. stop_tc(Pid2).
  73. user_sanity_test() ->
  74. Pid1 = tc_starter("1"),
  75. User = spawn(fun() -> user_loop(Pid1) end),
  76. ?assertMatch({"1", _Ref}, user_id(User)),
  77. user_crash(User),
  78. stop_tc(Pid1).
  79. pidq_basics_test_() ->
  80. {foreach,
  81. % setup
  82. fun() ->
  83. Pools = [[{name, "p1"},
  84. {max_pids, 3}, {min_free, 1},
  85. {init_size, 2}, {pid_starter_args, ["type-0"]}]],
  86. Config = [{pid_starter, {?MODULE, tc_starter}},
  87. {pid_stopper, {?MODULE, stop_tc}},
  88. {pools, Pools}],
  89. pidq:start(Config)
  90. end,
  91. fun(_X) ->
  92. pidq:stop()
  93. end,
  94. [
  95. {"take and return one",
  96. fun() ->
  97. P = pidq:take_pid(),
  98. ?assertMatch({"type-0", _Id}, get_tc_id(P)),
  99. ok = pidq:return_pid(P, ok)
  100. end},
  101. {"pids are created on demand until max",
  102. fun() ->
  103. Pids = [pidq:take_pid(), pidq:take_pid(), pidq:take_pid()],
  104. ?assertMatch(error_no_pids, pidq:take_pid()),
  105. ?assertMatch(error_no_pids, pidq:take_pid()),
  106. PRefs = [ R || {_T, R} <- [ get_tc_id(P) || P <- Pids ] ],
  107. ?assertEqual(3, length(lists:usort(PRefs)))
  108. end
  109. },
  110. {"pids are reused most recent return first",
  111. fun() ->
  112. P1 = pidq:take_pid(),
  113. P2 = pidq:take_pid(),
  114. ?assertNot(P1 == P2),
  115. ok = pidq:return_pid(P1, ok),
  116. ok = pidq:return_pid(P2, ok),
  117. % pids are reused most recent first
  118. ?assertEqual(P2, pidq:take_pid()),
  119. ?assertEqual(P1, pidq:take_pid())
  120. end},
  121. {"if a pid crashes it is replaced",
  122. fun() ->
  123. Pids0 = [pidq:take_pid(), pidq:take_pid(), pidq:take_pid()],
  124. Ids0 = [ get_tc_id(P) || P <- Pids0 ],
  125. % crash them all
  126. [ P ! crash || P <- Pids0 ],
  127. Pids1 = get_n_pids(3, []),
  128. Ids1 = [ get_tc_id(P) || P <- Pids1 ],
  129. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  130. end
  131. }
  132. % {"if a pid is returned with bad status it is replaced",
  133. % fun() ->
  134. % P1 = pidq:take_pid(),
  135. % P2 = pidq:take_pid(),
  136. % pidq:return_pid(P2, ok),
  137. % pidq:return_pid(P1, fail),
  138. % PN = pidq:take_pid(),
  139. % ?assertEqual(P2, pidq:take_pid()),
  140. % ?assertNot(PN == P1)
  141. % end
  142. % }
  143. ]}.
  144. pidq_integration_test_() ->
  145. {foreach,
  146. % setup
  147. fun() ->
  148. Pools = [[{name, "p1"},
  149. {max_pids, 20},
  150. {min_free, 3},
  151. {init_size, 10},
  152. {pid_starter_args, ["type-0"]}]],
  153. Config = [{pid_starter, {?MODULE, tc_starter}},
  154. {pid_stopper, {?MODULE, stop_tc}},
  155. {pools, Pools}],
  156. pidq:start(Config),
  157. Users = [ start_user() || _X <- lists:seq(1, 10) ],
  158. Users
  159. end,
  160. % cleanup
  161. fun(Users) ->
  162. [ user_stop(U) || U <- Users ],
  163. pidq:stop()
  164. end,
  165. %
  166. [
  167. fun(Users) ->
  168. fun() ->
  169. % each user has a different tc ID
  170. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  171. ?assertEqual(lists:usort(TcIds), TcIds)
  172. end
  173. end
  174. ]
  175. }.
  176. % fun(Users) ->
  177. % ]}
  178. % {"users still unique after a renew cycle",
  179. % fun() ->
  180. % Users = [ start_user() || _X <- lists:seq(1, 10) ],
  181. % % return and take new tc pids, expect unique
  182. % [ user_new_tc(UPid) || UPid <- Users ],
  183. % TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  184. % % each user has a different tc ID
  185. % ?assertEqual(lists:usort(TcIds), TcIds)
  186. % ]}.
  187. % % return and take new tc pids, still unique
  188. % [ user_new_tc(UPid) || UPid <- Users ],
  189. % TcIds2 = lists:sort([ user_id(UPid) || UPid <- Users ]),
  190. % ?assertEqual(lists:usort(TcIds2), TcIds2),
  191. % % if the users all crash...
  192. % [ user_crash(UPid) || UPid <- Users ],
  193. % Users2 = [ start_user() || _X <- lists:seq(1, 10) ],
  194. % TcIds3 = lists:sort([ user_id(UPid) || UPid <- Users ]),
  195. % ?assertEqual(lists:usort(TcIds3), TcIds3)
  196. % testing crash recovery means race conditions when either pids
  197. % haven't yet crashed or pidq hasn't recovered. So this helper loops
  198. % forver until N pids are obtained, ignoring error_no_pids.
  199. get_n_pids(0, Acc) ->
  200. Acc;
  201. get_n_pids(N, Acc) ->
  202. case pidq:take_pid() of
  203. error_no_pids ->
  204. get_n_pids(N, Acc);
  205. Pid ->
  206. get_n_pids(N - 1, [Pid|Acc])
  207. end.