pooler_tests.erl 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755
  1. -module(pooler_tests).
  2. -include_lib("eunit/include/eunit.hrl").
  3. -compile([export_all]).
  4. % The `user' processes represent users of the pooler 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. spawn(fun() -> user_loop(start) end).
  9. user_id(Pid) ->
  10. Pid ! {get_tc_id, self()},
  11. receive
  12. {Type, Id} ->
  13. {Type, Id}
  14. end.
  15. user_new_tc(Pid) ->
  16. Pid ! new_tc.
  17. user_stop(Pid) ->
  18. Pid ! stop.
  19. user_crash(Pid) ->
  20. Pid ! crash.
  21. user_loop(Atom) when Atom =:= error_no_members orelse Atom =:= start ->
  22. user_loop(pooler:take_member(test_pool_1));
  23. user_loop(MyTC) ->
  24. receive
  25. {get_tc_id, From} ->
  26. From ! pooled_gs:get_id(MyTC),
  27. user_loop(MyTC);
  28. {ping_tc, From} ->
  29. From ! pooled_gs:ping(MyTC),
  30. user_loop(MyTC);
  31. {ping_count, From} ->
  32. From ! pooled_gs:ping_count(MyTC),
  33. user_loop(MyTC);
  34. new_tc ->
  35. pooler:return_member(test_pool_1, MyTC, ok),
  36. MyNewTC = pooler:take_member(test_pool_1),
  37. user_loop(MyNewTC);
  38. stop ->
  39. pooler:return_member(test_pool_1, MyTC, ok),
  40. stopped;
  41. crash ->
  42. erlang:error({user_loop, kaboom})
  43. end.
  44. % The `tc' processes represent the pids tracked by pooler for testing.
  45. % They have a type and an ID and can report their type and ID and
  46. % stop.
  47. tc_loop({Type, Id}) ->
  48. receive
  49. {get_id, From} ->
  50. From ! {ok, Type, Id},
  51. tc_loop({Type, Id});
  52. stop -> stopped;
  53. crash ->
  54. erlang:error({tc_loop, kaboom})
  55. end.
  56. get_tc_id(Pid) ->
  57. Pid ! {get_id, self()},
  58. receive
  59. {ok, Type, Id} ->
  60. {Type, Id}
  61. after 200 ->
  62. timeout
  63. end.
  64. stop_tc(Pid) ->
  65. Pid ! stop.
  66. tc_starter(Type) ->
  67. Ref = make_ref(),
  68. spawn_link(fun() -> tc_loop({Type, Ref}) end).
  69. assert_tc_valid(Pid) ->
  70. ?assertMatch({_Type, _Ref}, get_tc_id(Pid)),
  71. ok.
  72. % tc_sanity_test() ->
  73. % Pid1 = tc_starter("1"),
  74. % {"1", Id1} = get_tc_id(Pid1),
  75. % Pid2 = tc_starter("1"),
  76. % {"1", Id2} = get_tc_id(Pid2),
  77. % ?assertNot(Id1 == Id2),
  78. % stop_tc(Pid1),
  79. % stop_tc(Pid2).
  80. % user_sanity_test() ->
  81. % Pid1 = tc_starter("1"),
  82. % User = spawn(fun() -> user_loop(Pid1) end),
  83. % ?assertMatch({"1", _Ref}, user_id(User)),
  84. % user_crash(User),
  85. % stop_tc(Pid1).
  86. pooler_basics_via_config_test_() ->
  87. {setup,
  88. fun() ->
  89. application:set_env(pooler, metrics_module, fake_metrics),
  90. fake_metrics:start_link()
  91. end,
  92. fun(_X) ->
  93. fake_metrics:stop()
  94. end,
  95. {foreach,
  96. % setup
  97. fun() ->
  98. Pools = [[{name, test_pool_1},
  99. {max_count, 3},
  100. {init_count, 2},
  101. {cull_interval, {0, min}},
  102. {start_mfa,
  103. {pooled_gs, start_link, [{"type-0"}]}}]],
  104. application:set_env(pooler, pools, Pools),
  105. error_logger:delete_report_handler(error_logger_tty_h),
  106. application:start(pooler)
  107. end,
  108. fun(_X) ->
  109. application:stop(pooler)
  110. end,
  111. basic_tests()}}.
  112. pooler_basics_dynamic_test_() ->
  113. {setup,
  114. fun() ->
  115. application:set_env(pooler, metrics_module, fake_metrics),
  116. fake_metrics:start_link()
  117. end,
  118. fun(_X) ->
  119. fake_metrics:stop()
  120. end,
  121. {foreach,
  122. % setup
  123. fun() ->
  124. Pool = [{name, test_pool_1},
  125. {max_count, 3},
  126. {init_count, 2},
  127. {start_mfa,
  128. {pooled_gs, start_link, [{"type-0"}]}}],
  129. application:unset_env(pooler, pools),
  130. error_logger:delete_report_handler(error_logger_tty_h),
  131. application:start(pooler),
  132. pooler:new_pool(Pool)
  133. end,
  134. fun(_X) ->
  135. application:stop(pooler)
  136. end,
  137. basic_tests()}}.
  138. pooler_basics_integration_to_other_supervisor_test_() ->
  139. {setup,
  140. fun() ->
  141. application:set_env(pooler, metrics_module, fake_metrics),
  142. fake_metrics:start_link()
  143. end,
  144. fun(_X) ->
  145. fake_metrics:stop()
  146. end,
  147. {foreach,
  148. % setup
  149. fun() ->
  150. Pool = [{name, test_pool_1},
  151. {max_count, 3},
  152. {init_count, 2},
  153. {start_mfa,
  154. {pooled_gs, start_link, [{"type-0"}]}}],
  155. application:unset_env(pooler, pools),
  156. error_logger:delete_report_handler(error_logger_tty_h),
  157. application:start(pooler),
  158. supervisor:start_link(fake_external_supervisor, Pool)
  159. end,
  160. fun({ok, SupPid}) ->
  161. exit(SupPid, normal),
  162. application:stop(pooler)
  163. end,
  164. basic_tests()}}.
  165. basic_tests() ->
  166. [
  167. {"there are init_count members at start",
  168. fun() ->
  169. Stats = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  170. ?assertEqual(2, length(Stats))
  171. end},
  172. {"take and return one",
  173. fun() ->
  174. P = pooler:take_member(test_pool_1),
  175. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  176. ok = pooler:return_member(test_pool_1, P, ok)
  177. end},
  178. {"take and return one, named pool",
  179. fun() ->
  180. P = pooler:take_member(test_pool_1),
  181. ?assertMatch({"type-0", _Id}, pooled_gs:get_id(P)),
  182. ok, pooler:return_member(test_pool_1, P)
  183. end},
  184. {"attempt to take form unknown pool",
  185. fun() ->
  186. %% since pools are now servers, an unknown pool will timeout
  187. ?assertExit({noproc, _}, pooler:take_member(bad_pool_name))
  188. end},
  189. {"members creation is triggered after pool exhaustion until max",
  190. fun() ->
  191. %% init count is 2
  192. Pids0 = [pooler:take_member(test_pool_1), pooler:take_member(test_pool_1)],
  193. %% since new member creation is async, can only assert
  194. %% that we will get a pid, but may not be first try.
  195. Pids = get_n_pids(1, Pids0),
  196. %% pool is at max now, requests should give error
  197. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  198. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  199. PRefs = [ R || {_T, R} <- [ pooled_gs:get_id(P) || P <- Pids ] ],
  200. % no duplicates
  201. ?assertEqual(length(PRefs), length(lists:usort(PRefs)))
  202. end
  203. },
  204. {"pids are reused most recent return first",
  205. fun() ->
  206. P1 = pooler:take_member(test_pool_1),
  207. P2 = pooler:take_member(test_pool_1),
  208. ?assertNot(P1 == P2),
  209. ok = pooler:return_member(test_pool_1, P1, ok),
  210. ok = pooler:return_member(test_pool_1, P2, ok),
  211. % pids are reused most recent first
  212. ?assertEqual(P2, pooler:take_member(test_pool_1)),
  213. ?assertEqual(P1, pooler:take_member(test_pool_1))
  214. end},
  215. {"if an in-use pid crashes it is replaced",
  216. fun() ->
  217. Pids0 = get_n_pids(3, []),
  218. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  219. % crash them all
  220. [ pooled_gs:crash(P) || P <- Pids0 ],
  221. Pids1 = get_n_pids(3, []),
  222. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  223. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  224. end
  225. },
  226. {"if a free pid crashes it is replaced",
  227. fun() ->
  228. FreePids = [ P || {P, {_, free, _}} <- pooler:pool_stats(test_pool_1) ],
  229. [ exit(P, kill) || P <- FreePids ],
  230. Pids1 = get_n_pids(3, []),
  231. ?assertEqual(3, length(Pids1))
  232. end},
  233. {"if a pid is returned with bad status it is replaced",
  234. fun() ->
  235. Pids0 = get_n_pids(3, []),
  236. Ids0 = [ pooled_gs:get_id(P) || P <- Pids0 ],
  237. % return them all marking as bad
  238. [ pooler:return_member(test_pool_1, P, fail) || P <- Pids0 ],
  239. Pids1 = get_n_pids(3, []),
  240. Ids1 = [ pooled_gs:get_id(P) || P <- Pids1 ],
  241. [ ?assertNot(lists:member(I, Ids0)) || I <- Ids1 ]
  242. end
  243. },
  244. {"if a consumer crashes, pid is replaced",
  245. fun() ->
  246. Consumer = start_user(),
  247. StartId = user_id(Consumer),
  248. user_crash(Consumer),
  249. NewPid = hd(get_n_pids(1, [])),
  250. NewId = pooled_gs:get_id(NewPid),
  251. ?assertNot(NewId == StartId)
  252. end
  253. },
  254. {"it is ok to return an unknown pid",
  255. fun() ->
  256. Bogus1 = spawn(fun() -> ok end),
  257. Bogus2 = spawn(fun() -> ok end),
  258. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus1, ok)),
  259. ?assertEqual(ok, pooler:return_member(test_pool_1, Bogus2, fail))
  260. end
  261. },
  262. {"calling return_member on error_no_members is ignored",
  263. fun() ->
  264. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members)),
  265. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, ok)),
  266. ?assertEqual(ok, pooler:return_member(test_pool_1, error_no_members, fail))
  267. end
  268. },
  269. {"dynamic pool creation",
  270. fun() ->
  271. PoolSpec = [{name, dyn_pool_1},
  272. {max_count, 3},
  273. {init_count, 2},
  274. {start_mfa,
  275. {pooled_gs, start_link, [{"dyn-0"}]}}],
  276. {ok, SupPid1} = pooler:new_pool(PoolSpec),
  277. ?assert(is_pid(SupPid1)),
  278. M = pooler:take_member(dyn_pool_1),
  279. ?assertMatch({"dyn-0", _Id}, pooled_gs:get_id(M)),
  280. ?assertEqual(ok, pooler:rm_pool(dyn_pool_1)),
  281. ?assertExit({noproc, _}, pooler:take_member(dyn_pool_1)),
  282. %% verify pool of same name can be created after removal
  283. {ok, SupPid2} = pooler:new_pool(PoolSpec),
  284. ?assert(is_pid(SupPid2)),
  285. %% remove non-existing pool
  286. ?assertEqual(ok, pooler:rm_pool(dyn_pool_X)),
  287. ?assertEqual(ok, pooler:rm_pool(dyn_pool_1))
  288. end},
  289. {"metrics have been called",
  290. fun() ->
  291. %% exercise the API to ensure we have certain keys reported as metrics
  292. fake_metrics:reset_metrics(),
  293. Pids = [ pooler:take_member(test_pool_1) || _I <- lists:seq(1, 10) ],
  294. [ pooler:return_member(test_pool_1, P) || P <- Pids ],
  295. catch pooler:take_member(bad_pool_name),
  296. %% kill and unused member
  297. exit(hd(Pids), kill),
  298. %% kill a used member
  299. KillMe = pooler:take_member(test_pool_1),
  300. exit(KillMe, kill),
  301. %% FIXME: We need to wait for pooler to process the
  302. %% exit message. This is ugly, will fix later.
  303. timer:sleep(200), % :(
  304. ExpectKeys = lists:sort([<<"pooler.test_pool_1.error_no_members_count">>,
  305. <<"pooler.test_pool_1.events">>,
  306. <<"pooler.test_pool_1.free_count">>,
  307. <<"pooler.test_pool_1.in_use_count">>,
  308. <<"pooler.test_pool_1.killed_free_count">>,
  309. <<"pooler.test_pool_1.killed_in_use_count">>,
  310. <<"pooler.test_pool_1.take_rate">>]),
  311. Metrics = fake_metrics:get_metrics(),
  312. GotKeys = lists:usort([ Name || {Name, _, _} <- Metrics ]),
  313. ?assertEqual(ExpectKeys, GotKeys)
  314. end},
  315. {"accept bad member is handled",
  316. fun() ->
  317. Bad = spawn(fun() -> ok end),
  318. Ref = erlang:make_ref(),
  319. ?assertEqual(ok, pooler:accept_member(test_pool_1, {Ref, Bad}))
  320. end}
  321. ].
  322. pooler_groups_test_() ->
  323. {setup,
  324. fun() ->
  325. application:set_env(pooler, metrics_module, fake_metrics),
  326. fake_metrics:start_link()
  327. end,
  328. fun(_X) ->
  329. fake_metrics:stop()
  330. end,
  331. {foreach,
  332. % setup
  333. fun() ->
  334. Pools = [[{name, test_pool_1},
  335. {group, group_1},
  336. {max_count, 3},
  337. {init_count, 2},
  338. {start_mfa,
  339. {pooled_gs, start_link, [{"type-1-1"}]}}],
  340. [{name, test_pool_2},
  341. {group, group_1},
  342. {max_count, 3},
  343. {init_count, 2},
  344. {start_mfa,
  345. {pooled_gs, start_link, [{"type-1-2"}]}}],
  346. %% test_pool_3 not part of the group
  347. [{name, test_pool_3},
  348. {group, undefined},
  349. {max_count, 3},
  350. {init_count, 2},
  351. {start_mfa,
  352. {pooled_gs, start_link, [{"type-3"}]}}]
  353. ],
  354. application:set_env(pooler, pools, Pools),
  355. %% error_logger:delete_report_handler(error_logger_tty_h),
  356. pg2:start(),
  357. application:start(pooler)
  358. end,
  359. fun(_X) ->
  360. application:stop(pooler),
  361. application:stop(pg2)
  362. end,
  363. [
  364. {"take and return one group member (repeated)",
  365. fun() ->
  366. Types = [ begin
  367. Pid = pooler:take_group_member(group_1),
  368. {Type, _} = pooled_gs:get_id(Pid),
  369. ?assertMatch("type-1" ++ _, Type),
  370. ok = pooler:return_group_member(group_1, Pid, ok),
  371. Type
  372. end
  373. || _I <- lists:seq(1, 50) ],
  374. Type_1_1 = [ X || "type-1-1" = X <- Types ],
  375. Type_1_2 = [ X || "type-1-2" = X <- Types ],
  376. ?assert(length(Type_1_1) > 0),
  377. ?assert(length(Type_1_2) > 0)
  378. end},
  379. {"take member from unknown group",
  380. fun() ->
  381. ?assertEqual({error_no_group, not_a_group},
  382. pooler:take_group_member(not_a_group))
  383. end},
  384. {"return member to unknown group",
  385. fun() ->
  386. Pid = pooler:take_group_member(group_1),
  387. ?assertEqual(ok, pooler:return_group_member(no_such_group, Pid))
  388. end},
  389. {"return member to wrong group",
  390. fun() ->
  391. Pid = pooler:take_member(test_pool_3),
  392. ?assertEqual(ok, pooler:return_group_member(group_1, Pid))
  393. end},
  394. {"take member from empty group",
  395. fun() ->
  396. %% artificially empty group member list
  397. [ pg2:leave(group_1, M) || M <- pg2:get_members(group_1) ],
  398. ?assertEqual(error_no_members, pooler:take_group_member(group_1))
  399. end},
  400. {"return member to group, implied ok",
  401. fun() ->
  402. Pid = pooler:take_group_member(group_1),
  403. ?assertEqual(ok, pooler:return_group_member(group_1, Pid))
  404. end},
  405. {"return error_no_member to group",
  406. fun() ->
  407. ?assertEqual(ok, pooler:return_group_member(group_1, error_no_members))
  408. end},
  409. {"exhaust pools in group",
  410. fun() ->
  411. Pids = get_n_pids_group(group_1, 6, []),
  412. %% they should all be pids
  413. [ begin
  414. {Type, _} = pooled_gs:get_id(P),
  415. ?assertMatch("type-1" ++ _, Type),
  416. ok
  417. end || P <- Pids ],
  418. %% further attempts should be error
  419. [error_no_members,
  420. error_no_members,
  421. error_no_members] = [ pooler:take_group_member(group_1)
  422. || _I <- lists:seq(1, 3) ]
  423. end},
  424. {"rm_group with nonexisting group",
  425. fun() ->
  426. ?assertEqual(ok, pooler:rm_group(i_dont_exist))
  427. end},
  428. {"rm_group with existing empty group",
  429. fun() ->
  430. ?assertEqual(ok, pooler:rm_pool(test_pool_1)),
  431. ?assertEqual(ok, pooler:rm_pool(test_pool_2)),
  432. ?assertEqual(error_no_members, pooler:take_group_member(group_1)),
  433. ?assertEqual(ok, pooler:rm_group(group_1)),
  434. ?assertExit({noproc, _}, pooler:take_member(test_pool_1)),
  435. ?assertExit({noproc, _}, pooler:take_member(test_pool_2)),
  436. ?assertEqual({error_no_group, group_1},
  437. pooler:take_group_member(group_1))
  438. end},
  439. {"rm_group with existing non-empty group",
  440. fun() ->
  441. %% Verify that group members exist
  442. MemberPid = pooler:take_group_member(group_1),
  443. ?assert(is_pid(MemberPid)),
  444. pooler:return_group_member(group_1, MemberPid),
  445. Pool1Pid = pooler:take_member(test_pool_1),
  446. ?assert(is_pid(Pool1Pid)),
  447. pooler:return_member(test_pool_1, Pool1Pid),
  448. Pool2Pid = pooler:take_member(test_pool_2),
  449. ?assert(is_pid(Pool2Pid)),
  450. pooler:return_member(test_pool_2, Pool2Pid),
  451. %% Delete and verify that group and pools are destroyed
  452. ?assertEqual(ok, pooler:rm_group(group_1)),
  453. ?assertExit({noproc, _}, pooler:take_member(test_pool_1)),
  454. ?assertExit({noproc, _}, pooler:take_member(test_pool_2)),
  455. ?assertEqual({error_no_group, group_1},
  456. pooler:take_group_member(group_1))
  457. end}
  458. ]}}.
  459. pooler_limit_failed_adds_test_() ->
  460. %% verify that pooler crashes completely if too many failures are
  461. %% encountered while trying to add pids.
  462. {setup,
  463. fun() ->
  464. Pools = [[{name, test_pool_1},
  465. {max_count, 10},
  466. {init_count, 10},
  467. {start_mfa,
  468. {pooled_gs, start_link, [crash]}}]],
  469. application:set_env(pooler, pools, Pools)
  470. end,
  471. fun(_) ->
  472. application:stop(pooler)
  473. end,
  474. fun() ->
  475. application:start(pooler),
  476. ?assertEqual(error_no_members, pooler:take_member(test_pool_1)),
  477. ?assertEqual(error_no_members, pooler:take_member(test_pool_1))
  478. end}.
  479. pooler_scheduled_cull_test_() ->
  480. {setup,
  481. fun() ->
  482. application:set_env(pooler, metrics_module, fake_metrics),
  483. fake_metrics:start_link(),
  484. Pools = [[{name, test_pool_1},
  485. {max_count, 10},
  486. {init_count, 2},
  487. {start_mfa, {pooled_gs, start_link, [{"type-0"}]}},
  488. {cull_interval, {200, ms}},
  489. {max_age, {0, min}}]],
  490. application:set_env(pooler, pools, Pools),
  491. %% error_logger:delete_report_handler(error_logger_tty_h),
  492. application:start(pooler)
  493. end,
  494. fun(_X) ->
  495. fake_metrics:stop(),
  496. application:stop(pooler)
  497. end,
  498. [{"excess members are culled repeatedly",
  499. fun() ->
  500. %% take all members
  501. Pids1 = get_n_pids(test_pool_1, 10, []),
  502. %% return all
  503. [ pooler:return_member(test_pool_1, P) || P <- Pids1 ],
  504. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  505. %% wait for longer than cull delay
  506. timer:sleep(250),
  507. ?assertEqual(2, length(pooler:pool_stats(test_pool_1))),
  508. %% repeat the test to verify that culling gets rescheduled.
  509. Pids2 = get_n_pids(test_pool_1, 10, []),
  510. %% return all
  511. [ pooler:return_member(test_pool_1, P) || P <- Pids2 ],
  512. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  513. %% wait for longer than cull delay
  514. timer:sleep(250),
  515. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  516. end
  517. },
  518. {"non-excess members are not culled",
  519. fun() ->
  520. [P1, P2] = [pooler:take_member(test_pool_1) || _X <- [1, 2] ],
  521. [pooler:return_member(test_pool_1, P) || P <- [P1, P2] ],
  522. ?assertEqual(2, length(pooler:pool_stats(test_pool_1))),
  523. timer:sleep(250),
  524. ?assertEqual(2, length(pooler:pool_stats(test_pool_1)))
  525. end
  526. },
  527. {"in-use members are not culled",
  528. fun() ->
  529. %% take all members
  530. Pids = get_n_pids(test_pool_1, 10, []),
  531. %% don't return any
  532. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  533. %% wait for longer than cull delay
  534. timer:sleep(250),
  535. ?assertEqual(10, length(pooler:pool_stats(test_pool_1))),
  536. [ pooler:return_member(test_pool_1, P) || P <- Pids ]
  537. end},
  538. {"no cull when init_count matches max_count",
  539. %% not sure how to verify this. But this test at least
  540. %% exercises the code path.
  541. fun() ->
  542. Config = [{name, test_static_pool_1},
  543. {max_count, 2},
  544. {init_count, 2},
  545. {start_mfa, {pooled_gs, start_link, [{"static-0"}]}},
  546. {cull_interval, {200, ms}}], % ignored
  547. pooler:new_pool(Config),
  548. P = pooler:take_member(test_static_pool_1),
  549. ?assertMatch({"static-0", _}, pooled_gs:get_id(P)),
  550. pooler:return_member(test_static_pool_1, P),
  551. ok
  552. end}
  553. ]}.
  554. random_message_test_() ->
  555. {setup,
  556. fun() ->
  557. Pools = [[{name, test_pool_1},
  558. {max_count, 2},
  559. {init_count, 1},
  560. {start_mfa,
  561. {pooled_gs, start_link, [{"type-0"}]}}]],
  562. application:set_env(pooler, pools, Pools),
  563. error_logger:delete_report_handler(error_logger_tty_h),
  564. application:start(pooler),
  565. %% now send some bogus messages
  566. %% do the call in a throw-away process to avoid timeout error
  567. spawn(fun() -> catch gen_server:call(test_pool_1, {unexpected_garbage_msg, 5}) end),
  568. gen_server:cast(test_pool_1, {unexpected_garbage_msg, 6}),
  569. whereis(test_pool_1) ! {unexpected_garbage_msg, 7},
  570. ok
  571. end,
  572. fun(_) ->
  573. application:stop(pooler)
  574. end,
  575. [
  576. fun() ->
  577. Pid = spawn(fun() -> ok end),
  578. MonMsg = {'DOWN', erlang:make_ref(), process, Pid, because},
  579. test_pool_1 ! MonMsg
  580. end,
  581. fun() ->
  582. Pid = pooler:take_member(test_pool_1),
  583. {Type, _} = pooled_gs:get_id(Pid),
  584. ?assertEqual("type-0", Type)
  585. end,
  586. fun() ->
  587. RawPool = gen_server:call(test_pool_1, dump_pool),
  588. ?assertEqual(pool, element(1, RawPool))
  589. end
  590. ]}.
  591. pooler_integration_test_() ->
  592. {foreach,
  593. % setup
  594. fun() ->
  595. Pools = [[{name, test_pool_1},
  596. {max_count, 10},
  597. {init_count, 10},
  598. {start_mfa,
  599. {pooled_gs, start_link, [{"type-0"}]}}]],
  600. application:set_env(pooler, pools, Pools),
  601. error_logger:delete_report_handler(error_logger_tty_h),
  602. application:start(pooler),
  603. Users = [ start_user() || _X <- lists:seq(1, 10) ],
  604. Users
  605. end,
  606. % cleanup
  607. fun(Users) ->
  608. [ user_stop(U) || U <- Users ],
  609. application:stop(pooler)
  610. end,
  611. %
  612. [
  613. fun(Users) ->
  614. fun() ->
  615. % each user has a different tc ID
  616. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  617. ?assertEqual(lists:usort(TcIds), TcIds)
  618. end
  619. end
  620. ,
  621. fun(Users) ->
  622. fun() ->
  623. % users still unique after a renew cycle
  624. [ user_new_tc(UPid) || UPid <- Users ],
  625. TcIds = lists:sort([ user_id(UPid) || UPid <- Users ]),
  626. ?assertEqual(lists:usort(TcIds), TcIds)
  627. end
  628. end
  629. ,
  630. fun(Users) ->
  631. fun() ->
  632. % all users crash, pids are replaced
  633. TcIds1 = lists:sort([ user_id(UPid) || UPid <- Users ]),
  634. [ user_crash(UPid) || UPid <- Users ],
  635. Seq = lists:seq(1, 5),
  636. Users2 = [ start_user() || _X <- Seq ],
  637. TcIds2 = lists:sort([ user_id(UPid) || UPid <- Users2 ]),
  638. Both =
  639. sets:to_list(sets:intersection([sets:from_list(TcIds1),
  640. sets:from_list(TcIds2)])),
  641. ?assertEqual([], Both)
  642. end
  643. end
  644. ]
  645. }.
  646. time_as_millis_test_() ->
  647. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  648. Ones = [{{1, min}, 60000},
  649. {{1, sec}, 1000},
  650. {{1, ms}, 1},
  651. {{1, mu}, 0}],
  652. Misc = [{{3000, mu}, 3}],
  653. Tests = Zeros ++ Ones ++ Misc,
  654. [ ?_assertEqual(E, pooler:time_as_millis(I)) || {I, E} <- Tests ].
  655. time_as_micros_test_() ->
  656. Zeros = [ {{0, U}, 0} || U <- [min, sec, ms, mu] ],
  657. Ones = [{{1, min}, 60000000},
  658. {{1, sec}, 1000000},
  659. {{1, ms}, 1000},
  660. {{1, mu}, 1}],
  661. Misc = [{{3000, mu}, 3000}],
  662. Tests = Zeros ++ Ones ++ Misc,
  663. [ ?_assertEqual(E, pooler:time_as_micros(I)) || {I, E} <- Tests ].
  664. % testing crash recovery means race conditions when either pids
  665. % haven't yet crashed or pooler hasn't recovered. So this helper loops
  666. % forver until N pids are obtained, ignoring error_no_members.
  667. get_n_pids(N, Acc) ->
  668. get_n_pids(test_pool_1, N, Acc).
  669. get_n_pids(_Pool, 0, Acc) ->
  670. Acc;
  671. get_n_pids(Pool, N, Acc) ->
  672. case pooler:take_member(Pool) of
  673. error_no_members ->
  674. get_n_pids(Pool, N, Acc);
  675. Pid ->
  676. get_n_pids(Pool, N - 1, [Pid|Acc])
  677. end.
  678. get_n_pids_group(_Group, 0, Acc) ->
  679. Acc;
  680. get_n_pids_group(Group, N, Acc) ->
  681. case pooler:take_group_member(Group) of
  682. error_no_members ->
  683. get_n_pids_group(Group, N, Acc);
  684. Pid ->
  685. get_n_pids_group(Group, N - 1, [Pid|Acc])
  686. end.