mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
171 lines
5.8 KiB
Erlang
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.
|