Merge pull request #3012 from emqx/add_more_test_cases
Add more test cases
This commit is contained in:
commit
81a507c6fa
|
@ -181,12 +181,9 @@ log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
||||||
Type =:= standard_error ->
|
Type =:= standard_error ->
|
||||||
{Id, Level, console};
|
{Id, Level, console};
|
||||||
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
||||||
config := #{type := Type}}) ->
|
config := Config = #{type := file}}) ->
|
||||||
case Type of
|
{Id, Level, maps:get(file, Config, atom_to_list(Id))};
|
||||||
{file, Filename} -> {Id, Level, Filename};
|
|
||||||
{file, Filename, _Opts} -> {Id, Level, Filename};
|
|
||||||
_ -> {Id, Level, unknown}
|
|
||||||
end;
|
|
||||||
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
|
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
|
||||||
config := #{file := Filename}}) ->
|
config := #{file := Filename}}) ->
|
||||||
{Id, Level, Filename};
|
{Id, Level, Filename};
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:start_apps([]),
|
emqx_ct_helpers:start_apps([]),
|
||||||
Config.
|
Config.
|
||||||
|
@ -33,60 +32,20 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
|
t_stop_start(_) ->
|
||||||
t_start(_) ->
|
emqx:stop(),
|
||||||
error('TODO').
|
false = emqx:is_running(node()),
|
||||||
|
emqx:start(),
|
||||||
t_restart(_) ->
|
true = emqx:is_running(node()),
|
||||||
error('TODO').
|
ok = emqx:shutdown(),
|
||||||
|
false = emqx:is_running(node()),
|
||||||
t_stop(_) ->
|
ok = emqx:reboot(),
|
||||||
error('TODO').
|
true = emqx:is_running(node()),
|
||||||
|
ok = emqx:shutdown(for_test),
|
||||||
t_is_running(_) ->
|
false = emqx:is_running(node()).
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_subscribe(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_publish(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_unsubscribe(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_topics(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_subscribers(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_subscriptions(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_subscribed(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_hook(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_unhook(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_run_hook(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_run_fold_hook(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_shutdown(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_reboot(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
|
|
||||||
t_get_env(_) ->
|
t_get_env(_) ->
|
||||||
|
emqx:start(),
|
||||||
?assertEqual(undefined, emqx:get_env(undefined_key)),
|
?assertEqual(undefined, emqx:get_env(undefined_key)),
|
||||||
?assertEqual(default_value, emqx:get_env(undefined_key, default_value)),
|
?assertEqual(default_value, emqx:get_env(undefined_key, default_value)),
|
||||||
application:set_env(emqx, undefined_key, hello),
|
application:set_env(emqx, undefined_key, hello),
|
||||||
|
@ -95,23 +54,32 @@ t_get_env(_) ->
|
||||||
application:unset_env(emqx, undefined_key).
|
application:unset_env(emqx, undefined_key).
|
||||||
|
|
||||||
t_emqx_pubsub_api(_) ->
|
t_emqx_pubsub_api(_) ->
|
||||||
emqx:start(),
|
|
||||||
true = emqx:is_running(node()),
|
true = emqx:is_running(node()),
|
||||||
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
|
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:connect(C),
|
||||||
ClientId = <<"myclient">>,
|
ClientId = <<"myclient">>,
|
||||||
Topic = <<"mytopic">>,
|
|
||||||
Payload = <<"Hello World">>,
|
Payload = <<"Hello World">>,
|
||||||
|
Topic = <<"mytopic">>,
|
||||||
Topic1 = <<"mytopic1">>,
|
Topic1 = <<"mytopic1">>,
|
||||||
|
Topic2 = <<"mytopic2">>,
|
||||||
|
Topic3 = <<"mytopic3">>,
|
||||||
emqx:subscribe(Topic, ClientId),
|
emqx:subscribe(Topic, ClientId),
|
||||||
|
emqx:subscribe(Topic1, ClientId, #{qos => 1}),
|
||||||
|
emqx:subscribe(Topic2, ClientId, #{qos => 2}),
|
||||||
ct:sleep(100),
|
ct:sleep(100),
|
||||||
?assertEqual([Topic], emqx:topics()),
|
?assertEqual([Topic2, Topic1, Topic], emqx:topics()),
|
||||||
?assertEqual([self()], emqx:subscribers(Topic)),
|
?assertEqual([self()], emqx:subscribers(Topic)),
|
||||||
?assertEqual([{Topic,#{qos => 0,subid => ClientId}}], emqx:subscriptions(self())),
|
?assertEqual([self()], emqx:subscribers(Topic1)),
|
||||||
|
?assertEqual([self()], emqx:subscribers(Topic2)),
|
||||||
|
?assertEqual([{Topic,#{qos => 0,subid => ClientId}}, {Topic1,#{qos => 1,subid => ClientId}}, {Topic2,#{qos => 2,subid => ClientId}}], emqx:subscriptions(self())),
|
||||||
?assertEqual(true, emqx:subscribed(self(), Topic)),
|
?assertEqual(true, emqx:subscribed(self(), Topic)),
|
||||||
?assertEqual(true, emqx:subscribed(ClientId, Topic)),
|
?assertEqual(true, emqx:subscribed(ClientId, Topic)),
|
||||||
?assertEqual(false, emqx:subscribed(self(), Topic1)),
|
?assertEqual(true, emqx:subscribed(self(), Topic1)),
|
||||||
?assertEqual(false, emqx:subscribed(ClientId, Topic1)),
|
?assertEqual(true, emqx:subscribed(ClientId, Topic1)),
|
||||||
|
?assertEqual(true, emqx:subscribed(self(), Topic2)),
|
||||||
|
?assertEqual(true, emqx:subscribed(ClientId, Topic2)),
|
||||||
|
?assertEqual(false, emqx:subscribed(self(), Topic3)),
|
||||||
|
?assertEqual(false, emqx:subscribed(ClientId, Topic3)),
|
||||||
emqx:publish(emqx_message:make(Topic, Payload)),
|
emqx:publish(emqx_message:make(Topic, Payload)),
|
||||||
receive
|
receive
|
||||||
{deliver, Topic, #message{payload = Payload}} ->
|
{deliver, Topic, #message{payload = Payload}} ->
|
||||||
|
@ -119,26 +87,100 @@ t_emqx_pubsub_api(_) ->
|
||||||
after 100 ->
|
after 100 ->
|
||||||
ct:fail("no_message")
|
ct:fail("no_message")
|
||||||
end,
|
end,
|
||||||
|
emqx:publish(emqx_message:make(Topic1, Payload)),
|
||||||
|
receive
|
||||||
|
{deliver, Topic1, #message{payload = Payload}} ->
|
||||||
|
ok
|
||||||
|
after 100 ->
|
||||||
|
ct:fail("no_message")
|
||||||
|
end,
|
||||||
|
emqx:publish(emqx_message:make(Topic2, Payload)),
|
||||||
|
receive
|
||||||
|
{deliver, Topic2, #message{payload = Payload}} ->
|
||||||
|
ok
|
||||||
|
after 100 ->
|
||||||
|
ct:fail("no_message")
|
||||||
|
end,
|
||||||
emqx:unsubscribe(Topic),
|
emqx:unsubscribe(Topic),
|
||||||
|
emqx:unsubscribe(Topic1),
|
||||||
|
emqx:unsubscribe(Topic2),
|
||||||
ct:sleep(20),
|
ct:sleep(20),
|
||||||
?assertEqual([], emqx:topics()).
|
?assertEqual([], emqx:topics()).
|
||||||
|
|
||||||
t_emqx_hook_api(_) ->
|
t_hook_unhook(_) ->
|
||||||
InitArgs = ['arg2', 'arg3'],
|
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
|
||||||
emqx:hook('hook.run', fun run/3, InitArgs),
|
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, []),
|
||||||
ok = emqx:run_hook('hook.run', ['arg1']),
|
?assertEqual({error, already_exists},
|
||||||
emqx:unhook('hook.run', fun run/3),
|
emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, [])),
|
||||||
|
ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun1/1),
|
||||||
|
ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun2/1),
|
||||||
|
|
||||||
emqx:hook('hook.run_fold', fun add1/1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun8, []}, 8),
|
||||||
emqx:hook('hook.run_fold', fun add2/1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun2, []}, 2),
|
||||||
4 = emqx:run_fold_hook('hook.run_fold', [], 1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun10, []}, 10),
|
||||||
emqx:unhook('hook.run_fold', fun add1/1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun9, []}, 9),
|
||||||
emqx:unhook('hook.run_fold', fun add2/1).
|
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun2, []}),
|
||||||
|
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun8, []}),
|
||||||
|
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun9, []}),
|
||||||
|
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun10, []}).
|
||||||
|
|
||||||
run('arg1', 'arg2', 'arg3') ->
|
t_run_hook(_) ->
|
||||||
ok;
|
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun3/4, [init]),
|
||||||
run(_, _, _) ->
|
ok = emqx:hook(foldl_hook, {?MODULE, hook_fun3, [init]}),
|
||||||
ct:fail("no_match").
|
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun4/4, [init]),
|
||||||
|
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun5/4, [init]),
|
||||||
|
[r5,r4] = emqx:run_fold_hook(foldl_hook, [arg1, arg2], []),
|
||||||
|
[] = emqx:run_fold_hook(unknown_hook, [], []),
|
||||||
|
|
||||||
add1(N) -> {ok, N + 1}.
|
ok = emqx:hook(foldl_hook2, fun ?MODULE:hook_fun9/2),
|
||||||
add2(N) -> {ok, N + 2}.
|
ok = emqx:hook(foldl_hook2, {?MODULE, hook_fun10, []}),
|
||||||
|
[r9] = emqx:run_fold_hook(foldl_hook2, [arg], []),
|
||||||
|
|
||||||
|
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]),
|
||||||
|
{error, already_exists} = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]),
|
||||||
|
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun7/2, [initArg]),
|
||||||
|
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun8/2, [initArg]),
|
||||||
|
ok = emqx:run_hook(foreach_hook, [arg]),
|
||||||
|
|
||||||
|
ok = emqx:hook(foreach_filter1_hook, {?MODULE, hook_fun1, []}, {?MODULE, hook_filter1, []}, 0),
|
||||||
|
?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg])), %% filter passed
|
||||||
|
?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg1])), %% filter failed
|
||||||
|
|
||||||
|
ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2, []}, {?MODULE, hook_filter2, [init_arg]}),
|
||||||
|
ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2_1, []}, {?MODULE, hook_filter2_1, [init_arg]}),
|
||||||
|
?assertEqual(3, emqx:run_fold_hook(foldl_filter2_hook, [arg], 1)),
|
||||||
|
?assertEqual(2, emqx:run_fold_hook(foldl_filter2_hook, [arg1], 1)).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Hook fun
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
hook_fun1(arg) -> ok;
|
||||||
|
hook_fun1(_) -> error.
|
||||||
|
|
||||||
|
hook_fun2(arg) -> ok;
|
||||||
|
hook_fun2(_) -> error.
|
||||||
|
|
||||||
|
hook_fun2(_, Acc) -> {ok, Acc + 1}.
|
||||||
|
hook_fun2_1(_, Acc) -> {ok, Acc + 1}.
|
||||||
|
|
||||||
|
hook_fun3(arg1, arg2, _Acc, init) -> ok.
|
||||||
|
hook_fun4(arg1, arg2, Acc, init) -> {ok, [r4 | Acc]}.
|
||||||
|
hook_fun5(arg1, arg2, Acc, init) -> {ok, [r5 | Acc]}.
|
||||||
|
|
||||||
|
hook_fun6(arg, initArg) -> ok.
|
||||||
|
hook_fun7(arg, initArg) -> ok.
|
||||||
|
hook_fun8(arg, initArg) -> ok.
|
||||||
|
|
||||||
|
hook_fun9(arg, Acc) -> {stop, [r9 | Acc]}.
|
||||||
|
hook_fun10(arg, Acc) -> {stop, [r10 | Acc]}.
|
||||||
|
|
||||||
|
hook_filter1(arg) -> true;
|
||||||
|
hook_filter1(_) -> false.
|
||||||
|
|
||||||
|
hook_filter2(arg, _Acc, init_arg) -> true;
|
||||||
|
hook_filter2(_, _Acc, _IntArg) -> false.
|
||||||
|
|
||||||
|
hook_filter2_1(arg, _Acc, init_arg) -> true;
|
||||||
|
hook_filter2_1(arg1, _Acc, init_arg) -> true;
|
||||||
|
hook_filter2_1(_, _Acc, _IntArg) -> false.
|
|
@ -82,8 +82,18 @@ t_alarm_handler(_) ->
|
||||||
|
|
||||||
{ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4),
|
{ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4),
|
||||||
|
|
||||||
?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms()))
|
?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms())),
|
||||||
|
|
||||||
|
emqx_alarm_handler:mnesia(copy),
|
||||||
|
?assertEqual(true, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms(history))),
|
||||||
|
|
||||||
|
alarm_handler:clear_alarm(not_exist),
|
||||||
|
|
||||||
|
gen_event:start({local, alarm_handler_2}, []),
|
||||||
|
gen_event:add_handler(alarm_handler_2, emqx_alarm_handler, []),
|
||||||
|
?assertEqual({error,bad_query}, gen_event:call(alarm_handler_2, emqx_alarm_handler, bad_query)),
|
||||||
|
?assertEqual(ok, gen_event:notify(alarm_handler_2, ignored)),
|
||||||
|
gen_event:stop(alarm_handler_2)
|
||||||
end).
|
end).
|
||||||
|
|
||||||
with_connection(DoFun) ->
|
with_connection(DoFun) ->
|
||||||
|
|
|
@ -37,6 +37,7 @@ end_per_suite(_Config) ->
|
||||||
|
|
||||||
t_start_stop_listeners(_) ->
|
t_start_stop_listeners(_) ->
|
||||||
ok = emqx_listeners:start(),
|
ok = emqx_listeners:start(),
|
||||||
|
{error, _} = emqx_listeners:start_listener({ws,{"127.0.0.1", 8083}, []}),
|
||||||
ok = emqx_listeners:stop().
|
ok = emqx_listeners:stop().
|
||||||
|
|
||||||
t_restart_listeners(_) ->
|
t_restart_listeners(_) ->
|
||||||
|
@ -89,23 +90,4 @@ get_base_dir(Module) ->
|
||||||
|
|
||||||
get_base_dir() ->
|
get_base_dir() ->
|
||||||
get_base_dir(?MODULE).
|
get_base_dir(?MODULE).
|
||||||
|
|
||||||
|
|
||||||
t_start_listener(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_restart(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_restart_listener(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_stop_listener(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_stop(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_start(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
-define(LOGGER, emqx_logger).
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
init_per_testcase(_TestCase, Config) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
@ -30,47 +32,62 @@ end_per_testcase(_TestCase, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
t_debug(_) ->
|
t_debug(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:debug("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:debug("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:debug(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
t_info(_) ->
|
t_info(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:info("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:info("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:info(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
t_warning(_) ->
|
t_warning(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:warning("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:warning("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:warning(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
t_error(_) ->
|
t_error(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:error("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:error("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:error(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
t_critical(_) ->
|
t_critical(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:critical("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:critical("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:critical(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
t_set_proc_metadata(_) ->
|
t_set_proc_metadata(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:set_proc_metadata(#{pid => self()})).
|
||||||
|
|
||||||
t_get_primary_log_level(_) ->
|
t_primary_log_level(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:set_primary_log_level(debug)),
|
||||||
|
?assertEqual(debug, ?LOGGER:get_primary_log_level()).
|
||||||
t_set_primary_log_level(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_get_log_handlers(_) ->
|
t_get_log_handlers(_) ->
|
||||||
error('TODO').
|
ok = logger:add_handler(logger_std_h_for_test, logger_std_h, #{config => #{type => file, file => "logger_std_h_for_test"}}),
|
||||||
|
ok = logger:add_handler(logger_disk_log_h_for_test, logger_disk_log_h, #{config => #{file => "logger_disk_log_h_for_test"}}),
|
||||||
|
?assertMatch([_|_], ?LOGGER:get_log_handlers()).
|
||||||
|
|
||||||
t_get_log_handler(_) ->
|
t_get_log_handler(_) ->
|
||||||
error('TODO').
|
[{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(),
|
||||||
|
?assertMatch({HandlerId, _, _}, ?LOGGER:get_log_handler(HandlerId)).
|
||||||
|
|
||||||
t_set_log_handler_level(_) ->
|
t_set_log_handler_level(_) ->
|
||||||
error('TODO').
|
[{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(),
|
||||||
|
Level = debug,
|
||||||
|
?LOGGER:set_log_handler_level(HandlerId, Level),
|
||||||
|
?assertMatch({HandlerId, Level, _}, ?LOGGER:get_log_handler(HandlerId)).
|
||||||
|
|
||||||
t_set_log_level(_) ->
|
t_set_log_level(_) ->
|
||||||
error('TODO').
|
?assertMatch({error, _Error}, ?LOGGER:set_log_level(for_test)),
|
||||||
|
?assertEqual(ok, ?LOGGER:set_log_level(debug)).
|
||||||
|
|
||||||
t_parse_transform(_) ->
|
t_parse_transform(_) ->
|
||||||
error('TODO').
|
error('TODO').
|
||||||
|
|
||||||
t_set_metadata_peername(_) ->
|
t_set_metadata_peername(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")).
|
||||||
|
|
||||||
t_set_metadata_clientid(_) ->
|
t_set_metadata_clientid(_) ->
|
||||||
error('TODO').
|
?assertEqual(ok, ?LOGGER:set_metadata_clientid(<<>>)),
|
||||||
|
?assertEqual(ok, ?LOGGER:set_metadata_clientid("for_test")).
|
||||||
|
|
|
@ -82,17 +82,20 @@ t_check_publish(_) ->
|
||||||
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
|
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
|
||||||
ok = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>)),
|
ok = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>)),
|
||||||
ok = emqx_packet:check(#mqtt_packet_publish{packet_id = 1, topic_name = <<"t">>}),
|
ok = emqx_packet:check(#mqtt_packet_publish{packet_id = 1, topic_name = <<"t">>}),
|
||||||
|
ok = emqx_packet:check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias'=> 0}}),
|
||||||
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<>>, 1, #{}, <<"payload">>)),
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<>>, 1, #{}, <<"payload">>)),
|
||||||
{error, ?RC_TOPIC_NAME_INVALID} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"+/+">>, 1, #{}, <<"payload">>)),
|
{error, ?RC_TOPIC_NAME_INVALID} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"+/+">>, 1, #{}, <<"payload">>)),
|
||||||
{error, ?RC_TOPIC_ALIAS_INVALID} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Topic-Alias' => 0}, <<"payload">>)),
|
{error, ?RC_TOPIC_ALIAS_INVALID} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Topic-Alias' => 0}, <<"payload">>)),
|
||||||
%% TODO::
|
%% TODO::
|
||||||
%% {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
|
%% {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
|
||||||
ok = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
|
ok = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
|
||||||
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 0}, <<"payload">>)),
|
||||||
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"+/+">>}, <<"payload">>)).
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"+/+">>}, <<"payload">>)).
|
||||||
|
|
||||||
t_check_subscribe(_) ->
|
t_check_subscribe(_) ->
|
||||||
ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
|
ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
|
||||||
[{<<"topic">>, #{qos => ?QOS_0}}])),
|
[{<<"topic">>, #{qos => ?QOS_0}}])),
|
||||||
|
{error, ?RC_TOPIC_FILTER_INVALID} = emqx_packet:check(#mqtt_packet_subscribe{topic_filters = []}),
|
||||||
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} =
|
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} =
|
||||||
emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1},
|
emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1},
|
||||||
[{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])).
|
[{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])).
|
||||||
|
@ -104,7 +107,11 @@ t_check_unsubscribe(_) ->
|
||||||
t_check_connect(_) ->
|
t_check_connect(_) ->
|
||||||
Opts = #{max_clientid_len => 5, mqtt_retain_available => false},
|
Opts = #{max_clientid_len => 5, mqtt_retain_available => false},
|
||||||
ok = emqx_packet:check(#mqtt_packet_connect{}, Opts),
|
ok = emqx_packet:check(#mqtt_packet_connect{}, Opts),
|
||||||
ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{properties = #{'Receive-Maximum' => 1}}), Opts),
|
ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{clientid = <<1>>,
|
||||||
|
properties = #{'Receive-Maximum' => 1},
|
||||||
|
will_flag = true,
|
||||||
|
will_topic = <<"will_topic">>}
|
||||||
|
), Opts),
|
||||||
ConnPkt1 = #mqtt_packet_connect{proto_name = <<"MQIsdp">>,
|
ConnPkt1 = #mqtt_packet_connect{proto_name = <<"MQIsdp">>,
|
||||||
proto_ver = ?MQTT_PROTO_V5
|
proto_ver = ?MQTT_PROTO_V5
|
||||||
},
|
},
|
||||||
|
@ -137,7 +144,9 @@ t_check_connect(_) ->
|
||||||
properties = #{'Request-Problem-Information' => 2}}), Opts),
|
properties = #{'Request-Problem-Information' => 2}}), Opts),
|
||||||
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(
|
||||||
?CONNECT_PACKET(#mqtt_packet_connect{
|
?CONNECT_PACKET(#mqtt_packet_connect{
|
||||||
properties = #{'Receive-Maximum' => 0}}), Opts).
|
properties = #{'Receive-Maximum' => 0}}), Opts),
|
||||||
|
ConnPkt7 = #mqtt_packet_connect{clientid = <<>>, clean_start = false},
|
||||||
|
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt7, Opts).
|
||||||
|
|
||||||
t_from_to_message(_) ->
|
t_from_to_message(_) ->
|
||||||
ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>),
|
ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>),
|
||||||
|
@ -160,6 +169,7 @@ t_from_to_message(_) ->
|
||||||
}).
|
}).
|
||||||
|
|
||||||
t_will_msg(_) ->
|
t_will_msg(_) ->
|
||||||
|
?assertEqual(undefined, emqx_packet:will_msg(#mqtt_packet_connect{will_flag = false})),
|
||||||
Pkt = #mqtt_packet_connect{will_flag = true,
|
Pkt = #mqtt_packet_connect{will_flag = true,
|
||||||
clientid = <<"clientid">>,
|
clientid = <<"clientid">>,
|
||||||
username = "test",
|
username = "test",
|
||||||
|
@ -171,14 +181,30 @@ t_will_msg(_) ->
|
||||||
},
|
},
|
||||||
Msg = emqx_packet:will_msg(Pkt),
|
Msg = emqx_packet:will_msg(Pkt),
|
||||||
?assertEqual(<<"clientid">>, Msg#message.from),
|
?assertEqual(<<"clientid">>, Msg#message.from),
|
||||||
?assertEqual(<<"topic">>, Msg#message.topic).
|
?assertEqual(<<"topic">>, Msg#message.topic),
|
||||||
|
Pkt2 = #mqtt_packet_connect{will_flag = true,
|
||||||
|
clientid = <<"clientid">>,
|
||||||
|
username = "test",
|
||||||
|
will_retain = true,
|
||||||
|
will_qos = ?QOS_2,
|
||||||
|
will_topic = <<"topic">>,
|
||||||
|
will_props = undefined,
|
||||||
|
will_payload = <<"payload">>
|
||||||
|
},
|
||||||
|
Msg2 = emqx_packet:will_msg(Pkt2),
|
||||||
|
?assertEqual(<<"clientid">>, Msg2#message.from),
|
||||||
|
?assertEqual(<<"topic">>, Msg2#message.topic).
|
||||||
|
|
||||||
t_to_message(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
|
|
||||||
t_format(_) ->
|
t_format(_) ->
|
||||||
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]),
|
io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK, retain = true, dup = 0}, variable = undefined})]),
|
||||||
|
io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, variable = 1, payload = <<"payload">>})]),
|
||||||
|
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{will_flag = true,
|
||||||
|
will_retain = true,
|
||||||
|
will_qos = ?QOS_2,
|
||||||
|
will_topic = <<"topic">>,
|
||||||
|
will_props = undefined,
|
||||||
|
will_payload = <<"payload">>}))]),
|
||||||
|
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{password = password}))]),
|
||||||
io:format("~s", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]),
|
io:format("~s", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]),
|
||||||
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
|
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
|
||||||
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
|
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
|
||||||
|
@ -187,5 +213,6 @@ t_format(_) ->
|
||||||
io:format("~s", [emqx_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]))]),
|
io:format("~s", [emqx_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]))]),
|
||||||
io:format("~s", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]),
|
io:format("~s", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]),
|
||||||
io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
|
io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
|
||||||
io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]).
|
io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]),
|
||||||
|
io:format("~s", [emqx_packet:format(?DISCONNECT_PACKET(128))]).
|
||||||
|
|
||||||
|
|
|
@ -34,18 +34,6 @@ t_get_error_state(_) ->
|
||||||
Conns = emqx_stats:getstats(),
|
Conns = emqx_stats:getstats(),
|
||||||
?assertEqual([], Conns).
|
?assertEqual([], Conns).
|
||||||
|
|
||||||
t_statsfun(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_getstats(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_getstat(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_setstat(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_get_state(_) ->
|
t_get_state(_) ->
|
||||||
with_proc(fun() ->
|
with_proc(fun() ->
|
||||||
?assertEqual(undefined, emqx_stats:getstat('notExist')),
|
?assertEqual(undefined, emqx_stats:getstat('notExist')),
|
||||||
|
|
|
@ -229,7 +229,3 @@ bench(Case, Fun, Args) ->
|
||||||
]),
|
]),
|
||||||
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
|
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
|
||||||
[Case, Time/?N, Case, (?N * 1000000) div Time]).
|
[Case, Time/?N, Case, (?N * 1000000) div Time]).
|
||||||
|
|
||||||
|
|
||||||
t_match(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
|
@ -47,9 +47,12 @@ t_start_traces(_Config) ->
|
||||||
{error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
{error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
||||||
emqx_logger:set_log_level(debug),
|
emqx_logger:set_log_level(debug),
|
||||||
ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
||||||
ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.log"),
|
ok = emqx_tracer:start_trace({clientid, "client2"}, all, "tmp/client2.log"),
|
||||||
{error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client3">>}, bad_level, "tmp/client3.log"),
|
ok = emqx_tracer:start_trace({clientid, client3}, all, "tmp/client3.log"),
|
||||||
|
{error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client4">>}, bad_level, "tmp/client4.log"),
|
||||||
|
{error, {handler_not_added, {file_error,".",eisdir}}} = emqx_tracer:start_trace({clientid, <<"client5">>}, debug, "."),
|
||||||
ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"),
|
ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"),
|
||||||
|
ok = emqx_tracer:start_trace({topic, <<"b/#">>}, all, "tmp/topic_trace.log"),
|
||||||
ct:sleep(100),
|
ct:sleep(100),
|
||||||
|
|
||||||
%% Verify the tracing file exits
|
%% Verify the tracing file exits
|
||||||
|
@ -60,7 +63,9 @@ t_start_traces(_Config) ->
|
||||||
%% Get current traces
|
%% Get current traces
|
||||||
?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
|
?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
|
||||||
{{clientid,"client2"},{debug,"tmp/client2.log"}},
|
{{clientid,"client2"},{debug,"tmp/client2.log"}},
|
||||||
{{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()),
|
{{clientid,"client3"},{debug,"tmp/client3.log"}},
|
||||||
|
{{topic,"a/#"},{debug,"tmp/topic_trace.log"}},
|
||||||
|
{{topic,"b/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()),
|
||||||
|
|
||||||
%% set the overall log level to debug
|
%% set the overall log level to debug
|
||||||
emqx_logger:set_log_level(debug),
|
emqx_logger:set_log_level(debug),
|
||||||
|
@ -77,23 +82,11 @@ t_start_traces(_Config) ->
|
||||||
%% Stop tracing
|
%% Stop tracing
|
||||||
ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
|
ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
|
||||||
ok = emqx_tracer:stop_trace({clientid, <<"client2">>}),
|
ok = emqx_tracer:stop_trace({clientid, <<"client2">>}),
|
||||||
|
ok = emqx_tracer:stop_trace({clientid, <<"client3">>}),
|
||||||
ok = emqx_tracer:stop_trace({topic, <<"a/#">>}),
|
ok = emqx_tracer:stop_trace({topic, <<"a/#">>}),
|
||||||
|
ok = emqx_tracer:stop_trace({topic, <<"b/#">>}),
|
||||||
|
{error, _Reason} = emqx_tracer:stop_trace({topic, <<"c/#">>}),
|
||||||
emqtt:disconnect(T),
|
emqtt:disconnect(T),
|
||||||
|
|
||||||
emqx_logger:set_log_level(warning).
|
emqx_logger:set_log_level(warning).
|
||||||
|
|
||||||
|
|
||||||
t_start_trace(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_stop_trace(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_lookup_traces(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
t_trace(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
|
@ -39,18 +39,6 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
application:stop(sasl).
|
application:stop(sasl).
|
||||||
|
|
||||||
t_get_process_high_watermark(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_set_process_high_watermark(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_get_process_low_watermark(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_set_process_low_watermark(_) ->
|
|
||||||
error('TODO').
|
|
||||||
|
|
||||||
t_api(_) ->
|
t_api(_) ->
|
||||||
meck:new(alarm_handler, [passthrough, no_history]),
|
meck:new(alarm_handler, [passthrough, no_history]),
|
||||||
Tester = self(),
|
Tester = self(),
|
||||||
|
@ -70,8 +58,13 @@ t_api(_) ->
|
||||||
end),
|
end),
|
||||||
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
||||||
{ok, _} = emqx_vm_mon:start_link([{check_interval, 1},
|
{ok, _} = emqx_vm_mon:start_link([{check_interval, 1},
|
||||||
{process_high_watermark, 0},
|
{process_high_watermark, 0.8},
|
||||||
{process_low_watermark, 0.6}]),
|
{process_low_watermark, 0.75}]),
|
||||||
|
timer:sleep(emqx_vm_mon:get_check_interval() * 1000),
|
||||||
|
emqx_vm_mon:set_process_high_watermark(0.0),
|
||||||
|
emqx_vm_mon:set_process_low_watermark(0.6),
|
||||||
|
?assertEqual(0.0, emqx_vm_mon:get_process_high_watermark()),
|
||||||
|
?assertEqual(0.6, emqx_vm_mon:get_process_low_watermark()),
|
||||||
?WAIT({Ref, set_alarm, {too_many_processes, _Count}}, 2000),
|
?WAIT({Ref, set_alarm, {too_many_processes, _Count}}, 2000),
|
||||||
?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
||||||
emqx_vm_mon:set_process_high_watermark(0.8),
|
emqx_vm_mon:set_process_high_watermark(0.8),
|
||||||
|
@ -81,7 +74,10 @@ t_api(_) ->
|
||||||
?WAIT({Ref, clear_alarm, too_many_processes}, 3000),
|
?WAIT({Ref, clear_alarm, too_many_processes}, 3000),
|
||||||
?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
||||||
emqx_vm_mon:set_check_interval(20),
|
emqx_vm_mon:set_check_interval(20),
|
||||||
?assertEqual(20, emqx_vm_mon:get_check_interval())
|
?assertEqual(20, emqx_vm_mon:get_check_interval()),
|
||||||
|
?assertEqual(ignored, gen_server:call(emqx_vm_mon, ignored)),
|
||||||
|
?assertEqual(ok, gen_server:cast(emqx_vm_mon, ignored)),
|
||||||
|
?assertEqual(ignored, emqx_vm_mon ! ignored)
|
||||||
after
|
after
|
||||||
meck:unload(alarm_handler)
|
meck:unload(alarm_handler)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -100,3 +100,12 @@ t_uncovered_func(_) ->
|
||||||
ok = Pid ! ok,
|
ok = Pid ! ok,
|
||||||
emqx_zone:stop().
|
emqx_zone:stop().
|
||||||
|
|
||||||
|
t_frame_options(_) ->
|
||||||
|
?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:frame_options(zone)).
|
||||||
|
|
||||||
|
t_check_oom(_) ->
|
||||||
|
{ok, _} = emqx_zone:start_link(),
|
||||||
|
application:set_env(emqx, zones, [{zone, ?ENVS}]),
|
||||||
|
ok = emqx_zone:force_reload(),
|
||||||
|
?assertEqual(ok, emqx_zone:check_oom(zone, fun() -> ok end)),
|
||||||
|
emqx_zone:stop().
|
Loading…
Reference in New Issue