test: refactor emqx_rpc unit tests

This commit is contained in:
Zaiming (Stone) Shi 2023-09-25 21:07:43 +02:00
parent 1f8985d09e
commit 6e8c73258f
2 changed files with 79 additions and 203 deletions

View File

@ -43,6 +43,10 @@
erpc_multicall/1
]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-compile(
{inline, [
rpc_node/1,
@ -75,15 +79,15 @@
-spec call(node(), module(), atom(), list()) -> call_result().
call(Node, Mod, Fun, Args) ->
filter_result(gen_rpc:call(rpc_node(Node), Mod, Fun, Args)).
maybe_badrpc(gen_rpc:call(rpc_node(Node), Mod, Fun, Args)).
-spec call(term(), node(), module(), atom(), list()) -> call_result().
call(Key, Node, Mod, Fun, Args) ->
filter_result(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args)).
maybe_badrpc(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args)).
-spec call(term(), node(), module(), atom(), list(), timeout()) -> call_result().
call(Key, Node, Mod, Fun, Args, Timeout) ->
filter_result(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args, Timeout)).
maybe_badrpc(gen_rpc:call(rpc_node({Key, Node}), Mod, Fun, Args, Timeout)).
-spec multicall([node()], module(), atom(), list()) -> multicall_result().
multicall(Nodes, Mod, Fun, Args) ->
@ -127,18 +131,15 @@ rpc_nodes([], Acc) ->
rpc_nodes([Node | Nodes], Acc) ->
rpc_nodes(Nodes, [rpc_node(Node) | Acc]).
filter_result({Error, Reason}) when
Error =:= badrpc; Error =:= badtcp
->
maybe_badrpc({Error, Reason}) when Error =:= badrpc; Error =:= badtcp ->
{badrpc, Reason};
filter_result(Delivery) ->
maybe_badrpc(Delivery) ->
Delivery.
max_client_num() ->
emqx:get_config([rpc, tcp_client_num], ?DefaultClientNum).
-spec unwrap_erpc(emqx_rpc:erpc(A) | [emqx_rpc:erpc(A)]) -> A | {error, _Err} | list().
unwrap_erpc(Res) when is_list(Res) ->
[unwrap_erpc(R) || R <- Res];
unwrap_erpc({ok, A}) ->
@ -151,3 +152,73 @@ unwrap_erpc({exit, Err}) ->
{error, Err};
unwrap_erpc({error, {erpc, Err}}) ->
{error, Err}.
-ifdef(TEST).
badrpc_call_test_() ->
application:ensure_all_started(gen_rpc),
Node = node(),
[
{"throw", fun() ->
?assertEqual(foo, call(Node, erlang, throw, [foo]))
end},
{"error", fun() ->
?assertMatch({badrpc, {'EXIT', {foo, _}}}, call(Node, erlang, error, [foo]))
end},
{"exit", fun() ->
?assertEqual({badrpc, {'EXIT', foo}}, call(Node, erlang, exit, [foo]))
end},
{"timeout", fun() ->
?assertEqual({badrpc, timeout}, call(key, Node, timer, sleep, [1000], 100))
end},
{"noconnection", fun() ->
%% mute crash report from gen_rpc
logger:set_primary_config(level, critical),
try
?assertEqual(
{badrpc, nxdomain}, call(key, 'no@such.node', foo, bar, [])
)
after
logger:set_primary_config(level, notice)
end
end}
].
multicall_test() ->
application:ensure_all_started(gen_rpc),
logger:set_primary_config(level, critical),
BadNode = 'no@such.node',
ThisNode = node(),
Nodes = [ThisNode, BadNode],
Call4 = fun(M, F, A) -> multicall(Nodes, M, F, A) end,
Call5 = fun(Key, M, F, A) -> multicall(Key, Nodes, M, F, A) end,
try
?assertMatch({[foo], [{BadNode, _}]}, Call4(erlang, throw, [foo])),
?assertMatch({[], [{ThisNode, _}, {BadNode, _}]}, Call4(erlang, error, [foo])),
?assertMatch({[], [{ThisNode, _}, {BadNode, _}]}, Call4(erlang, exit, [foo])),
?assertMatch({[], [{ThisNode, _}, {BadNode, _}]}, Call5(key, foo, bar, []))
after
logger:set_primary_config(level, notice)
end.
unwrap_erpc_test_() ->
Nodes = [node()],
MultiC = fun(M, F, A) -> unwrap_erpc(erpc:multicall(Nodes, M, F, A, 100)) end,
[
{"throw", fun() ->
?assertEqual([{error, foo}], MultiC(erlang, throw, [foo]))
end},
{"error", fun() ->
?assertEqual([{error, foo}], MultiC(erlang, error, [foo]))
end},
{"exit", fun() ->
?assertEqual([{error, {exception, foo}}], MultiC(erlang, exit, [foo]))
end},
{"noconnection", fun() ->
?assertEqual(
[{error, noconnection}], unwrap_erpc(erpc:multicall(['no@such.node'], foo, bar, []))
)
end}
].
-endif.

View File

@ -1,195 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(prop_emqx_rpc).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(NODENAME, 'test@127.0.0.1').
-define(ALL(Vars, Types, Exprs),
?SETUP(
fun() ->
State = do_setup(),
fun() -> do_teardown(State) end
end,
?FORALL(Vars, Types, Exprs)
)
).
%%--------------------------------------------------------------------
%% Properties
%%--------------------------------------------------------------------
prop_node() ->
?ALL(
Node0,
nodename(),
begin
Node = punch(Node0),
?assert(emqx_rpc:cast(Node, erlang, system_time, [])),
case emqx_rpc:call(Node, erlang, system_time, []) of
{badrpc, _Reason} -> true;
Delivery when is_integer(Delivery) -> true;
_Other -> false
end
end
).
prop_node_with_key() ->
?ALL(
{Node0, Key},
nodename_with_key(),
begin
Node = punch(Node0),
?assert(emqx_rpc:cast(Key, Node, erlang, system_time, [])),
case emqx_rpc:call(Key, Node, erlang, system_time, []) of
{badrpc, _Reason} -> true;
Delivery when is_integer(Delivery) -> true;
_Other -> false
end
end
).
prop_nodes() ->
?ALL(
Nodes0,
nodesname(),
begin
Nodes = punch(Nodes0),
case emqx_rpc:multicall(Nodes, erlang, system_time, []) of
{RealResults, RealBadNodes} when
is_list(RealResults);
is_list(RealBadNodes)
->
true;
_Other ->
false
end
end
).
prop_nodes_with_key() ->
?ALL(
{Nodes0, Key},
nodesname_with_key(),
begin
Nodes = punch(Nodes0),
case emqx_rpc:multicall(Key, Nodes, erlang, system_time, []) of
{RealResults, RealBadNodes} when
is_list(RealResults);
is_list(RealBadNodes)
->
true;
_Other ->
false
end
end
).
%%--------------------------------------------------------------------
%% Helper
%%--------------------------------------------------------------------
do_setup() ->
ensure_distributed_nodename(),
ok = logger:set_primary_config(#{level => warning}),
{ok, _Apps} = application:ensure_all_started(gen_rpc),
ok = application:set_env(gen_rpc, call_receive_timeout, 100),
ok = meck:new(gen_rpc, [passthrough, no_history]),
ok = meck:expect(
gen_rpc,
multicall,
fun(Nodes, Mod, Fun, Args) ->
gen_rpc:multicall(Nodes, Mod, Fun, Args, 100)
end
).
do_teardown(_) ->
ok = net_kernel:stop(),
ok = application:stop(gen_rpc),
ok = meck:unload(gen_rpc),
%% wait for tcp close
timer:sleep(2500).
ensure_distributed_nodename() ->
case net_kernel:start([?NODENAME]) of
{ok, _} ->
ok;
{error, {already_started, _}} ->
net_kernel:stop(),
net_kernel:start([?NODENAME]);
{error, {{shutdown, {_, _, {'EXIT', nodistribution}}}, _}} ->
%% start epmd first
spawn_link(fun() -> os:cmd("epmd") end),
timer:sleep(100),
net_kernel:start([?NODENAME])
end.
%%--------------------------------------------------------------------
%% Generator
%%--------------------------------------------------------------------
nodename() ->
?LET(
{NodePrefix, HostName},
{node_prefix(), hostname()},
begin
Node = NodePrefix ++ "@" ++ HostName,
list_to_atom(Node)
end
).
nodename_with_key() ->
?LET(
{NodePrefix, HostName, Key},
{node_prefix(), hostname(), choose(0, 10)},
begin
Node = NodePrefix ++ "@" ++ HostName,
{list_to_atom(Node), Key}
end
).
nodesname() ->
oneof([list(nodename()), [node()]]).
nodesname_with_key() ->
oneof([{list(nodename()), choose(0, 10)}, {[node()], 1}]).
node_prefix() ->
oneof(["emqxct", text_like()]).
text_like() ->
?SUCHTHAT(Text, list(range($a, $z)), (length(Text) =< 100 andalso length(Text) > 0)).
hostname() ->
oneof(["127.0.0.1", "localhost"]).
%%--------------------------------------------------------------------
%% Utils
%%--------------------------------------------------------------------
%% After running the props, the `node()` () is only able to return an
%% incorrect node name - `nonode@nohost`, But we want a distributed nodename
%% So, just translate the `nonode@nohost` to ?NODENAME
punch(Nodes) when is_list(Nodes) ->
lists:map(fun punch/1, Nodes);
punch('nonode@nohost') ->
%% Equal to ?NODENAME
node();
punch(GoodBoy) ->
GoodBoy.