%% SPDX-License-Identifier: AGPL-3.0-or-later -module(process_registry_tests). -typing([eqwalizer]). -include_lib("eunit/include/eunit.hrl"). -define(REGISTRY_TABLE, process_registry_table). build_process_key_integer_atom_test() -> ?assertEqual({guild, 123456}, process_registry:build_process_key(guild, 123456)), ?assertEqual({channel, 0}, process_registry:build_process_key(channel, 0)), ?assertEqual({voice, 999}, process_registry:build_process_key(voice, 999)). build_process_key_integer_string_test() -> ?assertEqual({channel, 999}, process_registry:build_process_key("channel", 999)), ?assertEqual({guild, 12345}, process_registry:build_process_key("guild", 12345)), ?assertEqual({voice, 0}, process_registry:build_process_key("voice", 0)). build_process_key_binary_atom_test() -> ?assertEqual( {guild, <<"123456">>}, process_registry:build_process_key(guild, <<"123456">>) ), ?assertEqual({voice, <<"789">>}, process_registry:build_process_key(voice, <<"789">>)), ?assertEqual({channel, <<"abc">>}, process_registry:build_process_key(channel, <<"abc">>)). build_process_key_binary_string_test() -> ?assertEqual({voice, <<"789">>}, process_registry:build_process_key("voice", <<"789">>)), ?assertEqual({guild, <<"test">>}, process_registry:build_process_key("guild", <<"test">>)), ?assertEqual( {channel, <<"123">>}, process_registry:build_process_key("channel", <<"123">>) ). build_process_key_string_atom_test() -> ?assertEqual({guild, <<"123456">>}, process_registry:build_process_key(guild, "123456")), ?assertEqual({channel, <<"abc">>}, process_registry:build_process_key(channel, "abc")), ?assertEqual({voice, <<"xyz">>}, process_registry:build_process_key(voice, "xyz")). build_process_key_string_string_test() -> ?assertEqual({channel, <<"abc">>}, process_registry:build_process_key("channel", "abc")), ?assertEqual({guild, <<"test">>}, process_registry:build_process_key("guild", "test")), ?assertEqual({voice, <<"123">>}, process_registry:build_process_key("voice", "123")). build_process_key_special_chars_test() -> ?assertEqual({guild, <<"123_456">>}, process_registry:build_process_key(guild, "123_456")), ?assertEqual( {channel, <<"test-channel">>}, process_registry:build_process_key(channel, "test-channel") ). build_process_name_is_alias_test() -> ?assertEqual( process_registry:build_process_key(guild, 123), process_registry:build_process_name(guild, 123) ), ?assertEqual( process_registry:build_process_key(session, <<"abc">>), process_registry:build_process_name(session, <<"abc">>) ). register_and_monitor_success_test() -> process_registry:init(), Key = {test_reg, 1001}, ProcessMap = #{}, Pid = spawn_sleep(100), Result = process_registry:register_and_monitor(Key, Pid, ProcessMap), ?assertMatch({ok, Pid, _Ref, _NewMap}, Result), {ok, ReturnedPid, Ref, NewMap} = Result, ?assertEqual(Pid, ReturnedPid), ?assertEqual(Pid, process_registry:registry_whereis(Key)), ?assertEqual(1, maps:size(NewMap)), ?assertEqual({Pid, Ref}, maps:get(Key, NewMap)), ?assert(is_reference(Ref)), process_registry:registry_unregister(Key). register_and_monitor_existing_map_test() -> process_registry:init(), Key = {test_reg, 1002}, ExistingPid = list_to_pid("<0.100.0>"), ExistingRef = make_ref(), ProcessMap = #{other_process => {ExistingPid, ExistingRef}}, Pid = spawn_sleep(100), {ok, _ReturnedPid, _Ref, NewMap} = process_registry:register_and_monitor( Key, Pid, ProcessMap ), ?assertEqual(2, maps:size(NewMap)), ?assert(maps:is_key(other_process, NewMap)), ?assert(maps:is_key(Key, NewMap)), process_registry:registry_unregister(Key). register_and_monitor_race_condition_test() -> process_registry:init(), Key = {test_reg, 1003}, ProcessMap = #{}, WinnerPid = spawn_sleep(200), ets:insert(?REGISTRY_TABLE, {Key, WinnerPid}), LoserPid = spawn_sleep(100), Result = process_registry:register_and_monitor(Key, LoserPid, ProcessMap), ?assertMatch({ok, WinnerPid, _Ref, _NewMap}, Result), {ok, ReturnedPid, Ref, NewMap} = Result, ?assertEqual(WinnerPid, ReturnedPid), ?assertEqual(WinnerPid, process_registry:registry_whereis(Key)), timer:sleep(50), ?assertEqual(false, process_liveness:is_alive(LoserPid)), ?assertEqual({WinnerPid, Ref}, maps:get(Key, NewMap)), process_registry:registry_unregister(Key). register_and_monitor_race_dead_test() -> process_registry:init(), Key = {test_reg, 1004}, ProcessMap = #{}, DeadPid = spawn(fun() -> ok end), timer:sleep(10), ?assertEqual(false, process_liveness:is_alive(DeadPid)), NewPid = spawn_sleep(100), Result = process_registry:register_and_monitor(Key, NewPid, ProcessMap), ?assertMatch({ok, NewPid, _Ref, _NewMap}, Result), process_registry:registry_unregister(Key). register_and_monitor_dead_process_test() -> process_registry:init(), Key = {test_reg, 1005}, ProcessMap = #{}, DeadPid = spawn(fun() -> exit(normal) end), timer:sleep(10), ?assertEqual(false, process_liveness:is_alive(DeadPid)), Result = process_registry:register_and_monitor(Key, DeadPid, ProcessMap), case Result of {ok, DeadPid, _Ref, _NewMap} -> ok; {error, _} -> ok end, process_registry:registry_unregister(Key). register_and_monitor_concurrent_test_() -> {timeout, 10, fun() -> process_registry:init(), Key = {test_reg, 1006}, Parent = self(), Pids = [spawn_register_worker(Parent, Key) || _ <- lists:seq(1, 5)], Results = [ receive {P, R} -> R after 2000 -> timeout end || P <- Pids ], ?assertEqual(5, length(Results)), SuccessResults = [R || R <- Results, is_tuple(R), element(1, R) =:= ok], RaceErrors = [R || R <- Results, R =:= {error, registration_race_condition}], Timeouts = [R || R <- Results, R =:= timeout], ?assertEqual(0, length(Timeouts)), ?assertMatch([_ | _], SuccessResults), ?assertEqual(5, length(SuccessResults) + length(RaceErrors)), assert_success_results_share_pid(SuccessResults), process_registry:registry_unregister(Key) end}. registry_whereis_alive_test() -> process_registry:init(), Key = {test_whereis, 2001}, Pid = spawn_sleep(200), ets:insert(?REGISTRY_TABLE, {Key, Pid}), ?assertEqual(Pid, process_registry:registry_whereis(Key)), process_registry:registry_unregister(Key). registry_whereis_dead_test() -> process_registry:init(), Key = {test_whereis, 2002}, Pid = spawn(fun() -> ok end), timer:sleep(10), ets:insert(?REGISTRY_TABLE, {Key, Pid}), ?assertEqual(undefined, process_registry:registry_whereis(Key)). registry_whereis_not_found_test() -> process_registry:init(), ?assertEqual(undefined, process_registry:registry_whereis({test_whereis, 2003})). spawn_sleep(Milliseconds) -> spawn(fun() -> timer:sleep(Milliseconds) end). spawn_register_worker(Parent, Key) -> spawn(fun() -> Pid = spawn_sleep(200), Result = process_registry:register_and_monitor(Key, Pid, #{}), Parent ! {self(), Result} end). assert_success_results_share_pid([{ok, FirstPid, _, _} | RestResults]) -> AllSamePid = lists:all( fun(Result) -> success_pid_matches(FirstPid, Result) end, RestResults ), ?assert(AllSamePid). success_pid_matches(FirstPid, {ok, Pid, _, _}) -> Pid =:= FirstPid; success_pid_matches(_FirstPid, _Result) -> false.