Files
fluxer/fluxer_gateway/test/guild_manager_tests.erl
T

171 lines
5.8 KiB
Erlang

%% SPDX-License-Identifier: AGPL-3.0-or-later
-module(guild_manager_tests).
-typing([eqwalizer]).
-include_lib("eunit/include/eunit.hrl").
default_shard_count_positive_test() ->
Count = guild_manager_shards:default_shard_count(),
?assert(Count >= 1).
select_shard_deterministic_test() ->
GuildId = 12345,
ShardCount = 8,
Shard1 = guild_manager_shards:select_shard(GuildId, ShardCount),
Shard2 = guild_manager_shards:select_shard(GuildId, ShardCount),
?assertEqual(Shard1, Shard2).
select_shard_in_range_test() ->
ShardCount = 8,
lists:foreach(
fun(GuildId) ->
Shard = guild_manager_shards:select_shard(GuildId, ShardCount),
?assert(Shard >= 0 andalso Shard < ShardCount)
end,
lists:seq(1, 100)
).
group_ids_by_shard_test() ->
GuildIds = [1, 2, 3, 4, 5],
ShardCount = 2,
Groups = guild_manager_shards:group_ids_by_shard(GuildIds, ShardCount),
AllIds = lists:flatten([Ids || {_Index, Ids} <- Groups]),
?assertEqual(lists:sort(GuildIds), lists:sort(AllIds)).
resolve_handoff_target_skips_local_owner_test() ->
LocalNode = '[email protected]',
?assertEqual(
skip,
guild_manager_handoff:resolve_handoff_target(10, LocalNode, fun(_GuildId) ->
LocalNode
end)
).
resolve_handoff_target_handoffs_remote_owner_test() ->
LocalNode = '[email protected]',
RemoteNode = '[email protected]',
?assertEqual(
{handoff, RemoteNode},
guild_manager_handoff:resolve_handoff_target(10, LocalNode, fun(_GuildId) ->
RemoteNode
end)
).
handoff_guild_ids_counts_attempts_and_successes_test() ->
LocalNode = '[email protected]',
OwnerResolver = fun
(1) -> LocalNode;
(2) -> '[email protected]';
(3) -> '[email protected]'
end,
HandoffFun = fun
(2, '[email protected]', AccState) -> {true, AccState#{shard_count := 2}};
(3, '[email protected]', AccState) -> {false, AccState#{shard_count := 3}}
end,
{Result, FinalState} = guild_manager_handoff:handoff_guild_ids(
[1, 2, 3], LocalNode, OwnerResolver, HandoffFun, empty_handoff_state()
),
?assertEqual(#{attempted => 2, handed_off => 1}, Result),
?assertEqual(3, maps:get(shard_count, FinalState)).
find_shard_by_ref_found_test() ->
Ref = make_ref(),
Shards = #{0 => #{pid => self(), ref => Ref}},
?assertMatch({ok, 0}, guild_manager_shards:find_shard_by_ref(Ref, Shards)).
find_shard_by_ref_not_found_test() ->
Shards = #{0 => #{pid => self(), ref => make_ref()}},
?assertEqual(not_found, guild_manager_shards:find_shard_by_ref(make_ref(), Shards)).
find_shard_by_pid_found_test() ->
Pid = self(),
Shards = #{0 => #{pid => Pid, ref => make_ref()}},
?assertMatch({ok, 0}, guild_manager_shards:find_shard_by_pid(Pid, Shards)).
empty_handoff_state() ->
#{shards => #{}, shard_count => 1}.
forward_call_to_shard_async_replies_test_() ->
{timeout, 15, fun() ->
delete_table(guild_pid_cache),
GuildId = 99999,
GuildPid = spawn(fun() -> timer:sleep(5000) end),
ShardPid = spawn(fun() -> shard_stub_loop(GuildId, GuildPid) end),
ShardRef = erlang:monitor(process, ShardPid),
State = #{shards => #{0 => #{pid => ShardPid, ref => ShardRef}}, shard_count => 1},
guild_manager_cache:ensure_guild_pid_cache(),
try
From = {self(), make_ref()},
Result = guild_manager_router:forward_call_to_shard(
GuildId, {start_or_lookup, GuildId}, From, State
),
?assertMatch({noreply, _NewState}, Result),
?assert(process_liveness:is_alive(ShardPid))
after
ShardPid ! stop,
delete_table(guild_pid_cache)
end
end}.
cleanup_guild_from_cache_does_not_remove_new_pid_test() ->
delete_table(guild_pid_cache),
guild_manager_cache:ensure_guild_pid_cache(),
try
OldPid = spawn(fun() -> ok end),
timer:sleep(10),
NewPid = spawn(fun() -> timer:sleep(1000) end),
ets:insert(guild_pid_cache, {42, NewPid}),
guild_manager_cache:cleanup_guild_from_cache(OldPid),
[{42, FoundPid}] = ets:lookup(guild_pid_cache, 42),
?assertEqual(NewPid, FoundPid)
after
delete_table(guild_pid_cache)
end.
start_or_lookup_does_not_bypass_manager_owner_gate_test_() ->
{timeout, 10, fun() ->
delete_table(guild_pid_cache),
delete_table(guild_manager_shard_table),
guild_manager_cache:ensure_guild_pid_cache(),
guild_manager_cache:ensure_shard_table(),
GuildId = 101,
GuildPid = spawn(fun() -> timer:sleep(1000) end),
ShardPid = spawn(fun() -> shard_stub_loop(GuildId, GuildPid) end),
ets:insert(guild_manager_shard_table, {shard_count, 1}),
ets:insert(guild_manager_shard_table, {{shard_pid, 0}, ShardPid}),
try
?assertEqual({error, unavailable}, guild_manager:start_or_lookup(GuildId))
after
ShardPid ! stop,
delete_table(guild_manager_shard_table),
delete_table(guild_pid_cache)
end
end}.
delete_table(Name) ->
try ets:delete(Name) of
_ -> ok
catch
error:badarg -> ok
end.
shard_stub_loop(GuildId, GuildPid) ->
receive
stop ->
ok;
{'$gen_call', From, {start_or_lookup, GuildId}} ->
gen_server:reply(From, {ok, GuildPid}),
shard_stub_loop(GuildId, GuildPid);
{'$gen_call', From, {lookup, GuildId}} ->
gen_server:reply(From, {ok, GuildPid}),
shard_stub_loop(GuildId, GuildPid);
{'$gen_call', From, _Request} ->
gen_server:reply(From, {error, unsupported}),
shard_stub_loop(GuildId, GuildPid);
_ ->
shard_stub_loop(GuildId, GuildPid)
after infinity ->
ok
end.